deparsing utility commands

Started by Alvaro Herreraalmost 11 years ago68 messages
#1Alvaro Herrera
alvherre@2ndquadrant.com
38 attachment(s)

Hi,

This is a repost of the patch to add CREATE command deparsing support to
event triggers. It now supports not only CREATE but also ALTER and
other commands such as GRANT/REVOKE, COMMENT ON and SECURITY LABEL.
This patch series is broken up in a rather large number of patches, but
my intention would be to commit separately only the first 6 patches; the
remaining parts are split up only so that it's easy to see how deparsing
each patch is implemented, and would be committed as a single changeset
(but before that I need a bunch of extra fixes and additions to other
parts.)

One of the worries regarding this code is whether we will be able to
keep it up to date as the grammar changes. We have come up with a
testing harness for this that's pretty simple: we will have a new make
target in src/test/regress that creates a new pg_regress schedule file,
then run pg_regress with it. That schedule contains a "deparse
initialization" test, then the contents of serial_schedule, then a
final deparse test. The initialization test creates a table and an
event trigger, and stores all deparsed commands in the table; the final
deparse test is about executing those commands. (There are actually two
event triggers; the other one stores DROP commands for objects dropped,
using the sql_drop event.) The deparse_init.sql file is attached.

Just by storing the deparsed command in the table we're doing the first
level of deparse testing: make sure that all commands deparse to
something. Running the commands is the second level: make sure that the
commands generated by the deparsing step works correctly. (The third
level is making sure that each command generated by deparse creates an
object idential to the one generated by the original command. The plan
here is to run two databases through pg_dump and compare.)
One line of defense which I just tought about is that instead of
sprinkling EventTriggerStashCommand() calls all over ProcessUtilitySlow,
we should add only one at the bottom.

There are some issues remaining:

1. ruleutils.c seems to deparse temp schemas as pg_temp_X instead of
just pg_temp; so some commands like CREATE VIEW..AS SELECT FROM temp_table
fail in an ugly way. Maybe the solution is to tell ruleutils that the
temp schema is always visible; or maybe the solution is to tell it that
it's spelled pg_temp instead.

2. I need to complete support for all object types in
getObjectIdentityParts. The ones not implemented cause the generated
DROP commands to fail.

3. Some commands are still not supported by the deparsing code:
1 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE OPTIONS (...)
2 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE RESET
2 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE SET OPTIONS
2 + WARNING: state: XX000 errm: unimplemented deparse of CREATE FOREIGN TABLE
2 + WARNING: state: XX000 errm: unimplemented deparse of CREATE LANGUAGE
3 + WARNING: hash indexes are not WAL-logged and their use is discouraged
3 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS
3 + WARNING: state: XX000 errm: unimplemented deparse of CREATE TABLE AS EXECUTE
4 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE ALTER CONSTRAINT
4 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE TYPE USING
4 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TEXT SEARCH CONFIGURATION
4 + WARNING: state: XX000 errm: unimplemented deparse of ALTER USER MAPPING
5 + WARNING: state: XX000 errm: unimplemented deparse of CREATE OPERATOR CLASS
5 + WARNING: state: XX000 errm: unsupported deparse of ALTER FUNCTION SET
5 + WARNING: state: XX000 errm: unsupported deparse of CREATE SERVER ... OPTIONS
7 + WARNING: state: XX000 errm: unimplemented deparse of ALTER FOREIGN DATA WRAPPER
7 + WARNING: state: XX000 errm: unimplemented deparse of ALTER SERVER
8 + WARNING: state: XX000 errm: unimplemented deparse of ALTER POLICY
9 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE SET
10 + WARNING: state: XX000 errm: unimplemented deparse of CREATE FOREIGN DATA WRAPPER
11 + WARNING: state: XX000 errm: can't recreate text search configuration with mappings
12 + WARNING: state: XX000 errm: unimplemented deparse of CREATE USER MAPPING
14 + WARNING: state: XX000 errm: unimplemented deparse of ALTER OPERATOR FAMILY

I expect most of these are simple to implement. The only one that seems
to present a challenge is ALTER OPERATOR FAMILY: it needs to pass back
the OID of all the added/removed operator class members.

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

Attachments:

0017-deparse-Support-CREATE-DOMAIN.patchtext/x-diff; charset=us-asciiDownload
>From 668850e62c18e23c09f52c676829984b094dfd4b 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 17/42] deparse: Support CREATE DOMAIN

---
 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 6fe9c9b..a90ab3b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1656,6 +1656,59 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return range;
 }
 
+static ObjTree *
+deparse_CreateDomain(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *createDomain;
+	ObjTree	   *tmp;
+	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}T %{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_type(typForm->typbasetype, typForm->typtypmod));
+
+	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);
+
+	return createDomain;
+}
+
 /*
  * Return the given object type as a string.
  */
@@ -2752,7 +2805,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateDomainStmt:
-			command = NULL;
+			command = deparse_CreateDomain(objectId, parsetree);
 			break;
 
 		case T_CreateConversionStmt:
-- 
2.1.4

0018-deparse-Support-CREATE-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From bc1fea9c92ca23197a7a081299838848038e6502 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 18/42] deparse: Support CREATE FUNCTION

---
 src/backend/tcop/deparse_utility.c | 309 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  25 +++
 src/include/utils/ruleutils.h      |   1 +
 3 files changed, 334 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index a90ab3b..607607e 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"
@@ -1710,6 +1711,312 @@ 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 ObjTree *
+deparse_CreateFunction(Oid objectId, Node *parsetree)
+{
+	CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree;
+	ObjTree	   *createFunc;
+	ObjTree	   *sign;
+	ObjTree	   *tmp;
+	Datum		tmpdatum;
+	char	   *fmt;
+	char	   *definition;
+	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)
+			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(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(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}n", 1,
+										"cost", ObjTypeFloat,
+										procForm->procost));
+
+	tmp = new_objtree_VA("ROWS %{rows}n", 0);
+	if (procForm->prorows == 0)
+		append_bool_object(tmp, "present", false);
+	else
+		append_float_object(tmp, "rows", 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);
+
+	return createFunc;
+}
+
+/*
  * Return the given object type as a string.
  */
 static const char *
@@ -2768,7 +3075,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateFunctionStmt:
-			command = NULL;
+			command = deparse_CreateFunction(objectId, parsetree);
 			break;
 
 		case T_AlterFunctionStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8e66b96..886dc4f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9692,3 +9692,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 9989cdd..c3757de 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -42,6 +42,7 @@ extern List *set_deparse_context_planstate(List *dpcontext,
 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);
-- 
2.1.4

0019-deparse-Support-ALTER-TABLE.patchtext/x-diff; charset=us-asciiDownload
>From 66cefe56b288b956c424f6df7ace13d8b069f459 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 19/42] deparse: Support ALTER TABLE

---
 src/backend/commands/event_trigger.c | 137 +++++++++-
 src/backend/commands/tablecmds.c     |   7 +
 src/backend/tcop/deparse_utility.c   | 491 +++++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           |  23 +-
 src/include/commands/event_trigger.h |   5 +
 src/include/tcop/deparse_utility.h   |   8 +
 6 files changed, 669 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index b258d72..67094c8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -57,6 +57,7 @@ typedef struct EventTriggerQueryState
 	int			table_rewrite_reason;	/* AT_REWRITE reason */
 
 	MemoryContext cxt;
+	StashedCommand *curcmd;
 	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
@@ -1211,6 +1212,7 @@ EventTriggerBeginCompleteQuery(void)
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
+	state->curcmd = NULL;
 	state->stash = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
@@ -1567,6 +1569,132 @@ EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype,
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * 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 = creating_extension;
+
+	stashed->d.alterTable.objectId = InvalidOid;
+	stashed->d.alterTable.objtype = objtype;
+	stashed->d.alterTable.subcmds = NIL;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->curcmd = stashed;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+void
+EventTriggerComplexCmdSetOid(Oid objectId)
+{
+	/*
+	 * When processing an ALTER TYPE command, the given objectId is the one
+	 * found on pg_class for the composite type; we translate it to the
+	 * corresponding pg_type OID so that it can be properly be considered the
+	 * object's canonical objectId.
+	 */
+	if (currentEventTriggerState->curcmd->d.alterTable.objtype == OBJECT_TYPE)
+	{
+		HeapTuple	relTup;
+		Form_pg_class relForm;
+
+		relTup = SearchSysCache1(RELOID, objectId);
+		if (!HeapTupleIsValid(relTup))
+			elog(ERROR, "cache lookup failed for relation %u", objectId);
+		relForm = (Form_pg_class) GETSTRUCT(relTup);
+		Assert(relForm->relkind == RELKIND_COMPOSITE_TYPE);
+		objectId = relForm->reltype;
+		ReleaseSysCache(relTup);
+	}
+
+	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)
 {
@@ -1646,7 +1774,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
 			MemSet(nulls, 0, sizeof(nulls));
 
-			if (cmd->type == SCT_Simple)
+			if (cmd->type == SCT_Simple ||
+				cmd->type == SCT_AlterTable)
 			{
 				Oid			classId;
 				Oid			objId;
@@ -1662,6 +1791,12 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 					objId = cmd->d.simple.objectId;
 					objSubId = cmd->d.simple.objectSubId;
 				}
+				else if (cmd->type == SCT_AlterTable)
+				{
+					classId = get_objtype_catalog_oid(cmd->d.alterTable.objtype);
+					objId = cmd->d.alterTable.objectId;
+					objSubId = 0;
+				}
 
 				tag = CreateCommandTag(cmd->parsetree);
 				addr.classId = classId;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ca672ab..377e5dd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2779,6 +2779,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerComplexCmdSetOid(relid);
+
 	ATController(NULL, rel, cmds, recurse, lockmode);
 }
 
@@ -3655,6 +3657,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
@@ -9624,7 +9629,9 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		cmds = lappend(cmds, cmd);
 
+		EventTriggerComplexCmdStart((Node *) stmt, OBJECT_TABLE);
 		AlterTableInternal(lfirst_oid(l), cmds, false);
+		EventTriggerComplexCmdEnd();
 	}
 
 	return new_tablespaceoid;
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 607607e..ff3f684 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2956,6 +2956,494 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 	return alterEnum;
 }
 
+static ObjTree *
+deparse_AlterTableStmt(StashedCommand *cmd)
+{
+	ObjTree	   *alterTableStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	List	   *dpcontext;
+	Relation	rel;
+	List	   *subcmds = NIL;
+	ListCell   *cell;
+	char	   *fmtstr;
+
+	Assert(cmd->type == SCT_AlterTable);
+
+	rel = relation_open(cmd->d.alterTable.objectId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									cmd->d.alterTable.objectId);
+
+	fmtstr = psprintf("ALTER %s %%{identity}D %%{subcmds:, }s",
+					  stringify_objtype(cmd->d.alterTable.objtype));
+	alterTableStmt = new_objtree_VA(fmtstr, 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(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(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(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(tmp));
+				break;
+
+			case AT_SetStatistics:
+				{
+					Assert(IsA(subcmd->def, Integer));
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n",
+										 3, "type", ObjTypeString, "set statistics",
+										 "column", ObjTypeString, subcmd->name,
+										 "statistics", ObjTypeInteger,
+										 intVal((Value *) subcmd->def));
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_SetOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE SET OPTIONS");
+				break;
+
+			case AT_ResetOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE RESET OPTIONS");
+				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(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(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(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(tmp));
+				}
+				break;
+
+			case AT_AlterConstraint:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER CONSTRAINT");
+				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(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(tmp));
+				break;
+
+			case AT_AlterColumnType:
+				{
+					TupleDesc tupdesc = RelationGetDescr(rel);
+					Form_pg_attribute att = tupdesc->attrs[substashed->attnum - 1];
+					ColumnDef	   *def;
+
+					def = (ColumnDef *) subcmd->def;
+					Assert(IsA(def, ColumnDef));
+
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DATA TYPE %{datatype}T %{collation}s %{using}s",
+										 2, "type", ObjTypeString, "alter column type",
+										 "column", ObjTypeString, subcmd->name);
+					/* add the TYPE clause */
+					append_object_object(tmp, "datatype",
+										 new_objtree_for_type(att->atttypid,
+															  att->atttypmod));
+
+					/* add a COLLATE clause, if needed */
+					tmp2 = new_objtree_VA("COLLATE %{name}D", 0);
+					if (OidIsValid(att->attcollation))
+					{
+						ObjTree *collname;
+
+						collname = new_objtree_for_qualname_id(CollationRelationId,
+															   att->attcollation);
+						append_object_object(tmp2, "name", collname);
+					}
+					else
+						append_bool_object(tmp2, "present", false);
+					append_object_object(tmp, "collation", tmp2);
+
+					/*
+					 * Error out if the USING clause was used.  We cannot use
+					 * it directly here, because it needs to run through
+					 * transformExpr() before being usable for ruleutils.c, and
+					 * we're not in a position to transform it ourselves.  To
+					 * fix this problem, tablecmds.c needs to be modified to store
+					 * the transformed expression somewhere in the StashedATSubcmd.
+					 */
+					tmp2 = new_objtree_VA("USING %{expression}s", 0);
+					if (def->raw_default)
+						elog(ERROR, "unimplemented deparse of ALTER TABLE TYPE USING");
+					else
+						append_bool_object(tmp2, "present", false);
+					append_object_object(tmp, "using", tmp2);
+
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS");
+				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(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(tmp));
+				break;
+
+			case AT_DropCluster:
+				tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+									 "type", ObjTypeString, "set without cluster");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetLogged:
+				tmp = new_objtree_VA("SET LOGGED", 1,
+									 "type", ObjTypeString, "set logged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetUnLogged:
+				tmp = new_objtree_VA("SET UNLOGGED", 1,
+									 "type", ObjTypeString, "set unlogged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOids:
+				tmp = new_objtree_VA("SET WITH OIDS", 1,
+									 "type", ObjTypeString, "set with oids");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOids:
+				tmp = new_objtree_VA("SET WITHOUT OIDS", 1,
+									 "type", ObjTypeString, "set without oids");
+				subcmds = lappend(subcmds, new_object_object(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(tmp));
+				break;
+
+			case AT_SetRelOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE SET");
+				break;
+
+			case AT_ResetRelOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE RESET");
+				break;
+
+			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(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(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(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(tmp));
+				break;
+
+			case AT_EnableTrigAll:
+				tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "enable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigAll:
+				tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "disable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableTrigUser:
+				tmp = new_objtree_VA("ENABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "enable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigUser:
+				tmp = new_objtree_VA("DISABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "disable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableRule:
+				tmp = new_objtree_VA("ENABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableAlwaysRule:
+				tmp = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable always rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableReplicaRule:
+				tmp = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable replica rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRule:
+				tmp = new_objtree_VA("DISABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "disable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddInherit:
+				tmp = new_objtree_VA("ADD INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "add inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 substashed->oid));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropInherit:
+				tmp = new_objtree_VA("NO INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "drop inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 substashed->oid));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOf:
+				tmp = new_objtree_VA("ADD OF %{type_of}T",
+									 2, "type", ObjTypeString, "add of",
+									 "type_of", ObjTypeObject,
+									 new_objtree_for_type(substashed->oid, -1));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOf:
+				tmp = new_objtree_VA("NOT OF",
+									 1, "type", ObjTypeString, "not of");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				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(tmp));
+				break;
+
+			case AT_EnableRowSecurity:
+				tmp = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "enable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRowSecurity:
+				tmp = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "disable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_GenericOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE OPTIONS (...)");
+				break;
+
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+	}
+
+	heap_close(rel, AccessShareLock);
+
+	if (list_length(subcmds) == 0)
+		return NULL;
+
+	append_array_object(alterTableStmt, "subcmds", subcmds);
+	return alterTableStmt;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -3248,6 +3736,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_Simple:
 			tree = deparse_simple_command(cmd);
 			break;
+		case SCT_AlterTable:
+			tree = deparse_AlterTableStmt(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 2e21659..c09915d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1019,6 +1019,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)
 						{
@@ -1032,19 +1036,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,
@@ -1201,6 +1218,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,
@@ -1211,6 +1229,7 @@ ProcessUtilitySlow(Node *parsetree,
 									false); /* quiet */
 					EventTriggerStashCommand(objectId, 0, OBJECT_INDEX,
 											 InvalidOid, parsetree);
+					EventTriggerComplexCmdEnd();
 				}
 				break;
 
@@ -1299,8 +1318,10 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
+				EventTriggerComplexCmdStart(parsetree, OBJECT_VIEW);	/* XXX relkind? */
 				objectId = DefineView((ViewStmt *) parsetree, queryString);
 				EventTriggerStashCommand(objectId, 0, OBJECT_VIEW, InvalidOid, parsetree);
+				EventTriggerComplexCmdEnd();
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index c07f04c..cdce9e1 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -62,5 +62,10 @@ extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 
 extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
 						 ObjectType objtype, Oid secondaryOid, 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 bcc8611..8ebbf34 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -26,6 +26,7 @@
 typedef enum StashedCommandType
 {
 	SCT_Simple,
+	SCT_AlterTable
 } StashedCommandType;
 
 /*
@@ -53,6 +54,13 @@ typedef struct StashedCommand
 			ObjectType	objtype;
 			Oid			secondaryOid;
 		} simple;
+
+		struct AlterTableCommand
+		{
+			Oid		objectId;
+			ObjectType objtype;
+			List   *subcmds;
+		} alterTable;
 	} d;
 } StashedCommand;
 
-- 
2.1.4

0020-deparse-Support-CREATE-VIEW.patchtext/x-diff; charset=us-asciiDownload
>From ecfd258aa07a38bed9d4396d785975330efeaba0 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 14:34:00 -0300
Subject: [PATCH 20/42] deparse: Support CREATE VIEW

---
 src/backend/tcop/deparse_utility.c | 38 +++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  7 +++++++
 src/include/utils/ruleutils.h      |  1 +
 3 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index ff3f684..49a9ddd 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -740,6 +740,42 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
 }
 
 /*
+ * deparse_ViewStmt
+ *		deparse a ViewStmt
+ *
+ * Given a view OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_ViewStmt(Oid objectId, Node *parsetree)
+{
+	ViewStmt   *node = (ViewStmt *) parsetree;
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	Relation	relation;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s",
+							  2,
+							  "or_replace", ObjTypeString,
+							  node->replace ? "OR REPLACE" : "",
+							  "persistence", ObjTypeString,
+					  get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(viewStmt, "identity", tmp);
+
+	append_string_object(viewStmt, "query",
+						 pg_get_viewdef_internal(objectId));
+
+	relation_close(relation, AccessShareLock);
+
+	return viewStmt;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -3559,7 +3595,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-			command = NULL;
+			command = deparse_ViewStmt(objectId, parsetree);
 			break;
 
 		case T_CreateFunctionStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 886dc4f..c281c38 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -669,6 +669,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
  */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index c3757de..91b61f9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -32,6 +32,7 @@ extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
 					   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
 
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
-- 
2.1.4

0021-deparse-Support-CREATE-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From 28098adc265a25aa627cb7b3185835c49241371c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 14:34:29 -0300
Subject: [PATCH 21/42] deparse: Support CREATE OPERATOR FAMILY

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 49a9ddd..5586fc3 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"
@@ -2993,6 +2994,41 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateOpFamily(Oid objectId, Node *parsetree)
+{
+	HeapTuple   opfTup;
+	HeapTuple   amTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_am  amForm;
+	ObjTree	   *copfStmt;
+	ObjTree	   *tmp;
+
+	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));
+
+	ReleaseSysCache(amTup);
+	ReleaseSysCache(opfTup);
+
+	return copfStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -3652,7 +3688,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateOpFamilyStmt:
-			command = NULL;
+			command = deparse_CreateOpFamily(objectId, parsetree);
 			break;
 
 		case T_AlterOpFamilyStmt:
-- 
2.1.4

0022-deparse-Support-CREATE-CONVERSION.patchtext/x-diff; charset=us-asciiDownload
>From 8987530c8fc3b5bdd87e9e152f7097e7b8a1a192 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 22/42] deparse: Support CREATE CONVERSION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 5586fc3..faba3f7 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"
@@ -2994,6 +2996,41 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+	HeapTuple   conTup;
+	Relation	convrel;
+	Form_pg_conversion conForm;
+	ObjTree	   *ccStmt;
+
+	convrel = heap_open(ConversionRelationId, AccessShareLock);
+	conTup = get_catalog_object_by_oid(convrel, 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));
+
+	heap_close(convrel, AccessShareLock);
+
+	return ccStmt;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -3676,7 +3713,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateConversionStmt:
-			command = NULL;
+			command = deparse_CreateConversion(objectId, parsetree);
 			break;
 
 		case T_CreateCastStmt:
-- 
2.1.4

0023-deparse-Support-DefineStmt-commands.patchtext/x-diff; charset=utf-8Download
>From 965af28caaaa7401943e7672600f37d6697a9cca 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 23/42] deparse: Support DefineStmt commands

CREATE AGGREGATE
CREATE COLLATION
CREATE OPERATOR
CREATE TEXT SEARCH CONFIGURATION
CREATE TEXT SEARCH PARSER
CREATE TEXT SEARCH DICTIONARY
CREATE TEXT SEARCH TEMPLATE
CREATE TYPE
---
 src/backend/commands/typecmds.c    |   2 +-
 src/backend/tcop/deparse_utility.c | 855 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 855 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index b77e1b4..0564535 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -229,7 +229,7 @@ DefineType(List *names, List *parameters)
 		 * creating the shell type was all we're supposed to do.
 		 */
 		if (parameters == NIL)
-			return InvalidOid;
+			return typoid;
 	}
 	else
 	{
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index faba3f7..4008db2 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -47,6 +47,11 @@
 #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"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
@@ -667,6 +672,854 @@ 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(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(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(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(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(tmp));
+
+	if (agg->aggtransspace != 0)
+	{
+		tmp = new_objtree_VA("SSPACE=%{space}n", 1,
+							 "space", ObjTypeInteger,
+							 agg->aggtransspace);
+		list = lappend(list, new_object_object(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(tmp));
+	}
+
+	if (agg->aggfinalextra)
+	{
+		tmp = new_objtree_VA("FINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(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(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(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(tmp));
+	}
+
+	if (agg->aggmtransspace != 0)
+	{
+		tmp = new_objtree_VA("MSSPACE=%{space}n", 1,
+							 "space", ObjTypeInteger,
+							 agg->aggmtransspace);
+		list = lappend(list, new_object_object(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(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(tmp));
+	}
+
+	if (agg->aggmfinalextra)
+	{
+		tmp = new_objtree_VA("MFINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(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(tmp));
+	}
+
+	if (agg->aggkind == AGGKIND_HYPOTHETICAL)
+	{
+		tmp = new_objtree_VA("HYPOTHETICAL=true", 0);
+		list = lappend(list, new_object_object(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}D", 0);
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname(op->oprnamespace,
+													  NameStr(op->oprname)));
+		list = lappend(list, new_object_object(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;
+	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;
+	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(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(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(tmp));
+	}
+
+	if (OidIsValid(oprForm->oprcom))
+	{
+		tmp = new_objtree_VA("COMMUTATOR=%{oper}D", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprcom));
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	if (OidIsValid(oprForm->oprnegate))
+	{
+		tmp = new_objtree_VA("NEGATOR=%{oper}D", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprnegate));
+		list = lappend(list, new_object_object(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(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(tmp));
+	}
+
+	if (oprForm->oprcanmerge)
+		list = lappend(list, new_object_object(new_objtree_VA("MERGES", 0)));
+	if (oprForm->oprcanhash)
+		list = lappend(list, new_object_object(new_objtree_VA("HASHES", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(oprTup);
+
+	return stmt;
+}
+
+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;
+	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(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(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(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(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(tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+
+	return stmt;
+}
+
+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(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(tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tsdTup);
+
+	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(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(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tstTup);
+
+	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(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(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(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(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(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(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(tmp));
+	}
+
+	/* INTERNALLENGTH */
+	if (typForm->typlen == -1)
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=VARIABLE", 0);
+	}
+	else
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}n", 1,
+							 "typlen", ObjTypeInteger, typForm->typlen);
+	}
+	list = lappend(list, new_object_object(tmp));
+
+	/* PASSEDBYVALUE */
+	if (typForm->typbyval)
+		list = lappend(list, new_object_object(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(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(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(tmp));
+
+	/* PREFERRED */
+	if (typForm->typispreferred)
+		list = lappend(list, new_object_object(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(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(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(tmp));
+
+	/* COLLATABLE */
+	if (OidIsValid(typForm->typcollation))
+		list = lappend(list,
+					   new_object_object(new_objtree_VA("COLLATABLE=true", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(typTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt(Oid objectId, Node *parsetree)
+{
+	DefineStmt *define = (DefineStmt *) parsetree;
+	ObjTree	   *defStmt;
+
+	switch (define->kind)
+	{
+		case OBJECT_AGGREGATE:
+			defStmt = deparse_DefineStmt_Aggregate(objectId, define);
+			break;
+
+		case OBJECT_COLLATION:
+			defStmt = deparse_DefineStmt_Collation(objectId, define);
+			break;
+
+		case OBJECT_OPERATOR:
+			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;
+
+		case OBJECT_TSDICTIONARY:
+			defStmt = deparse_DefineStmt_TSDictionary(objectId, define);
+			break;
+
+		case OBJECT_TSTEMPLATE:
+			defStmt = deparse_DefineStmt_TSTemplate(objectId, define);
+			break;
+
+		case OBJECT_TYPE:
+			defStmt = deparse_DefineStmt_Type(objectId, define);
+			break;
+
+		default:
+			elog(ERROR, "unsupported object kind");
+			return NULL;
+	}
+
+	return defStmt;
+}
+
 /*
  * deparse_CreateExtensionStmt
  *		deparse a CreateExtensionStmt
@@ -3598,7 +4451,7 @@ deparse_simple_command(StashedCommand *cmd)
 
 			/* other local objects */
 		case T_DefineStmt:
-			command = NULL;
+			command = deparse_DefineStmt(objectId, parsetree);
 			break;
 
 		case T_IndexStmt:
-- 
2.1.4

0001-getObjectIdentity-Fix-opclass-opfamily-names.patchtext/x-diff; charset=us-asciiDownload
>From 68a47c527080f46c7e00f1da888937913e675da1 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:22:27 -0300
Subject: [PATCH 01/42] getObjectIdentity: Fix opclass/opfamily names

The original representation uses "opcname for amname", which is good
enough; but if we replace "for" with "using", we can apply the returned
identity directly in a DROP command, as in

DROP OPERATOR CLASS opcname USING amname

This slightly simplify code using the identities to programatically
execute commands on operator classes and families.

Note backwards-incompatible change:
This dates back to 9.3 when object identities were introduced by commit
f8348ea3, but we don't want to change the behavior of released branches
and so this is not backpatched.
---
 src/backend/catalog/objectaddress.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 2f40733..d899dd7 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3533,7 +3533,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				appendStringInfoString(&buffer,
 									   quote_qualified_identifier(schema,
 												 NameStr(opcForm->opcname)));
-				appendStringInfo(&buffer, " for %s",
+				appendStringInfo(&buffer, " USING %s",
 								 quote_identifier(NameStr(amForm->amname)));
 				if (objname)
 				{
@@ -4070,7 +4070,7 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **objname, List **objargs
 	amForm = (Form_pg_am) GETSTRUCT(amTup);
 
 	schema = get_namespace_name(opfForm->opfnamespace);
-	appendStringInfo(buffer, "%s for %s",
+	appendStringInfo(buffer, "%s USING %s",
 					 quote_qualified_identifier(schema,
 												NameStr(opfForm->opfname)),
 					 NameStr(amForm->amname));
-- 
2.1.4

0002-deparse-core-return-affected-attnum-in-RENAME.patchtext/x-diff; charset=us-asciiDownload
>From 11c7bad562dbf744681827044ac73d697a583ce9 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 16:34:50 -0300
Subject: [PATCH 02/42] deparse/core: return affected attnum in RENAME

The various ALTER OBJECT RENAME already return the OID of the affected
object, but when it is a column being renamed, we were lacking its
attnum to properly identify it for later processing.  This patch doesn't
change any user-visible behavior, but the attnum is available for later
patches.
---
 src/backend/commands/alter.c     |  7 +++++--
 src/backend/commands/tablecmds.c | 26 +++++++++++++++++---------
 src/backend/tcop/utility.c       |  4 ++--
 src/include/commands/alter.h     |  2 +-
 src/include/commands/tablecmds.h |  2 +-
 5 files changed, 26 insertions(+), 15 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 78b54b4..dfd57e0 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -299,9 +299,12 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 /*
  * Executes an ALTER OBJECT / RENAME TO statement.  Based on the object
  * type, the function appropriate to that type is executed.
+ *
+ * Return value is the OID of the renamed object.  The objectSubId, if any,
+ * is returned in objsubid.
  */
 Oid
-ExecRenameStmt(RenameStmt *stmt)
+ExecRenameStmt(RenameStmt *stmt, int *objsubid)
 {
 	switch (stmt->renameType)
 	{
@@ -331,7 +334,7 @@ ExecRenameStmt(RenameStmt *stmt)
 
 		case OBJECT_COLUMN:
 		case OBJECT_ATTRIBUTE:
-			return renameatt(stmt);
+			return renameatt(stmt, objsubid);
 
 		case OBJECT_RULE:
 			return RenameRewriteRule(stmt->relation, stmt->subname,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 66d5083..1ee2901 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2155,8 +2155,10 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 
 /*
  *		renameatt_internal		- workhorse for renameatt
+ *
+ * Return value is the column number of the attribute in the 'myrelid' relation.
  */
-static void
+static int
 renameatt_internal(Oid myrelid,
 				   const char *oldattname,
 				   const char *newattname,
@@ -2297,6 +2299,8 @@ renameatt_internal(Oid myrelid,
 	heap_close(attrelation, RowExclusiveLock);
 
 	relation_close(targetrelation, NoLock);		/* close rel but keep lock */
+
+	return attnum;
 }
 
 /*
@@ -2321,9 +2325,10 @@ RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid,
  *		renameatt		- changes the name of a attribute in a relation
  */
 Oid
-renameatt(RenameStmt *stmt)
+renameatt(RenameStmt *stmt, int *objsubid)
 {
 	Oid			relid;
+	int			attnum;
 
 	/* lock level taken here should match renameatt_internal */
 	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
@@ -2339,13 +2344,16 @@ renameatt(RenameStmt *stmt)
 		return InvalidOid;
 	}
 
-	renameatt_internal(relid,
-					   stmt->subname,	/* old att name */
-					   stmt->newname,	/* new att name */
-					   interpretInhOption(stmt->relation->inhOpt),		/* recursive? */
-					   false,	/* recursing? */
-					   0,		/* expected inhcount */
-					   stmt->behavior);
+	attnum =
+		renameatt_internal(relid,
+						   stmt->subname,	/* old att name */
+						   stmt->newname,	/* new att name */
+						   interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+						   false,	/* recursing? */
+						   0,		/* expected inhcount */
+						   stmt->behavior);
+	if (objsubid)
+		*objsubid = attnum;
 
 	/* This is an ALTER TABLE command so it's about the relid */
 	return relid;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3533cfa..88dcd47 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -805,7 +805,7 @@ standard_ProcessUtility(Node *parsetree,
 									   context, params,
 									   dest, completionTag);
 				else
-					ExecRenameStmt(stmt);
+					ExecRenameStmt(stmt, NULL);
 			}
 			break;
 
@@ -1304,7 +1304,7 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree);
+				ExecRenameStmt((RenameStmt *) parsetree, NULL);
 				break;
 
 			case T_AlterObjectSchemaStmt:
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index 14d24f1..0382037 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -18,7 +18,7 @@
 #include "nodes/parsenodes.h"
 #include "utils/relcache.h"
 
-extern Oid	ExecRenameStmt(RenameStmt *stmt);
+extern Oid	ExecRenameStmt(RenameStmt *stmt, int *objsubid);
 
 extern Oid	ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
 extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index a55e8d4..c6e75e4 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -53,7 +53,7 @@ extern void ExecuteTruncate(TruncateStmt *stmt);
 
 extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass);
 
-extern Oid	renameatt(RenameStmt *stmt);
+extern Oid	renameatt(RenameStmt *stmt, int *attnum);
 
 extern Oid	RenameConstraint(RenameStmt *stmt);
 
-- 
2.1.4

0003-deparse-core-event-triggers-support-GRANT-REVOKE.patchtext/x-diff; charset=us-asciiDownload
>From 20717a7a42bb8e7cf569e311b5f4ba31e88efa1d 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 03/42] deparse/core: event triggers support GRANT/REVOKE

Have triggers on events ddl_command_start and ddl_command_end be invoked
on execution of GRANT and REVOKE.
---
 doc/src/sgml/event-trigger.sgml      | 17 ++++++++++++++++-
 src/backend/commands/event_trigger.c | 30 ++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           | 21 ++++++++++++++++-----
 src/include/commands/event_trigger.h |  1 +
 4 files changed, 63 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 156c463..ed53587 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -36,7 +36,8 @@
 
    <para>
      The <literal>ddl_command_start</> event occurs just before the
-     execution of a <literal>CREATE</>, <literal>ALTER</>, or <literal>DROP</>
+     execution of a <literal>CREATE</>, <literal>ALTER</>, <literal>DROP</>,
+     <literal>GRANT</> or <literal>REVOKE</>
      command.  No check whether the affected object exists or doesn't exist is
      performed before the event trigger fires.
      As an exception, however, this event does not occur for
@@ -716,6 +717,13 @@
         <entry align="center"><literal>-</literal></entry>
        </row>
        <row>
+        <entry align="left"><literal>GRANT</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+       </row>
+       <row>
         <entry align="left"><literal>IMPORT FOREIGN SCHEMA</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -723,6 +731,13 @@
         <entry align="center"><literal>-</literal></entry>
        </row>
        <row>
+        <entry align="left"><literal>REVOKE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+       </row>
+       <row>
         <entry align="left"><literal>SELECT INTO</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index a33a5ad..2eb6282 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -267,6 +267,8 @@ 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, "GRANT") == 0 ||
+		pg_strcasecmp(tag, "REVOKE") == 0 ||
 		pg_strcasecmp(tag, "DROP OWNED") == 0 ||
 		pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0)
 		return EVENT_TRIGGER_COMMAND_TAG_OK;
@@ -1149,6 +1151,34 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 	return true;
 }
 
+bool
+EventTriggerSupportsGrantObjectType(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_DATABASE:
+		case ACL_OBJECT_TABLESPACE:
+			/* no support for global objects */
+			return false;
+
+		case ACL_OBJECT_COLUMN:
+		case ACL_OBJECT_RELATION:
+		case ACL_OBJECT_SEQUENCE:
+		case ACL_OBJECT_DOMAIN:
+		case ACL_OBJECT_FDW:
+		case ACL_OBJECT_FOREIGN_SERVER:
+		case ACL_OBJECT_FUNCTION:
+		case ACL_OBJECT_LANGUAGE:
+		case ACL_OBJECT_LARGEOBJECT:
+		case ACL_OBJECT_NAMESPACE:
+		case ACL_OBJECT_TYPE:
+			return true;
+		default:
+			Assert(false);
+			return true;
+	}
+}
+
 /*
  * Prepare event trigger state for a new complete query to run, if necessary;
  * returns whether this was done.  If it was, EventTriggerEndCompleteQuery must
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 88dcd47..9539f4f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -548,11 +548,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);
@@ -783,6 +778,18 @@ standard_ProcessUtility(Node *parsetree,
 			 * in some cases, so we "fast path" them in the other cases.
 			 */
 
+		case T_GrantStmt:
+			{
+				GrantStmt  *stmt = (GrantStmt *) parsetree;
+
+				if (EventTriggerSupportsGrantObjectType(stmt->objtype))
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				else
+					ExecuteGrantStmt((GrantStmt *) parsetree);
+			}
+
 		case T_DropStmt:
 			{
 				DropStmt   *stmt = (DropStmt *) parsetree;
@@ -1315,6 +1322,10 @@ ProcessUtilitySlow(Node *parsetree,
 				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_GrantStmt:
+				ExecuteGrantStmt((GrantStmt *) parsetree);
+				break;
+
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
 				break;
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index e807e65..9ac9fc3 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -48,6 +48,7 @@ extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
 
 extern bool EventTriggerSupportsObjectType(ObjectType obtype);
 extern bool EventTriggerSupportsObjectClass(ObjectClass objclass);
+extern bool EventTriggerSupportsGrantObjectType(GrantObjectType objtype);
 extern void EventTriggerDDLCommandStart(Node *parsetree);
 extern void EventTriggerDDLCommandEnd(Node *parsetree);
 extern void EventTriggerSQLDrop(Node *parsetree);
-- 
2.1.4

0004-deparse-core-event-triggers-support-COMMENT.patchtext/x-diff; charset=us-asciiDownload
>From 9e0ce5b3c4dae3bfcec04a516fc9c5f3102cbb0a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:44:44 -0300
Subject: [PATCH 04/42] deparse/core: event triggers support COMMENT

Have triggers on events ddl_command_start and ddl_command_end be invoked
on execution of COMMENT.
---
 doc/src/sgml/event-trigger.sgml      |  9 ++++++++-
 src/backend/commands/comment.c       |  5 ++++-
 src/backend/commands/event_trigger.c |  1 +
 src/backend/tcop/utility.c           | 21 +++++++++++++++++----
 src/include/commands/comment.h       |  2 +-
 5 files changed, 31 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index ed53587..58e0d9d 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -37,7 +37,7 @@
    <para>
      The <literal>ddl_command_start</> event occurs just before the
      execution of a <literal>CREATE</>, <literal>ALTER</>, <literal>DROP</>,
-     <literal>GRANT</> or <literal>REVOKE</>
+     <literal>COMMENT</>, <literal>GRANT</> or <literal>REVOKE</>
      command.  No check whether the affected object exists or doesn't exist is
      performed before the event trigger fires.
      As an exception, however, this event does not occur for
@@ -318,6 +318,13 @@
         <entry align="center"><literal>-</literal></entry>
        </row>
        <row>
+        <entry align="left"><literal>COMMENT</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+       </row>
+       <row>
         <entry align="left"><literal>CREATE CAST</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index ed1e46e..e6929a6 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -37,7 +37,7 @@
  * pg_description for the object specified by the given SQL command.
  */
 Oid
-CommentObject(CommentStmt *stmt)
+CommentObject(CommentStmt *stmt, uint32 *objectSubId)
 {
 	ObjectAddress address;
 	Relation	relation;
@@ -126,6 +126,9 @@ CommentObject(CommentStmt *stmt)
 	if (relation != NULL)
 		relation_close(relation, NoLock);
 
+	if (objectSubId)
+		*objectSubId = address.objectSubId;
+
 	return address.objectId;
 }
 
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 2eb6282..87b606c 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -267,6 +267,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, "COMMENT") == 0 ||
 		pg_strcasecmp(tag, "GRANT") == 0 ||
 		pg_strcasecmp(tag, "REVOKE") == 0 ||
 		pg_strcasecmp(tag, "DROP OWNED") == 0 ||
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 9539f4f..ab13bfd 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -513,10 +513,6 @@ standard_ProcessUtility(Node *parsetree,
 			ExecuteTruncate((TruncateStmt *) parsetree);
 			break;
 
-		case T_CommentStmt:
-			CommentObject((CommentStmt *) parsetree);
-			break;
-
 		case T_SecLabelStmt:
 			ExecSecLabelStmt((SecLabelStmt *) parsetree);
 			break;
@@ -842,6 +838,19 @@ standard_ProcessUtility(Node *parsetree,
 			}
 			break;
 
+		case T_CommentStmt:
+			{
+				CommentStmt *stmt = (CommentStmt *) parsetree;
+
+				if (EventTriggerSupportsObjectType(stmt->objtype))
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				else
+					CommentObject((CommentStmt *) parsetree, NULL);
+				break;
+			}
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(parsetree, queryString,
@@ -1322,6 +1331,10 @@ ProcessUtilitySlow(Node *parsetree,
 				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_CommentStmt:
+				CommentObject((CommentStmt *) parsetree, NULL);
+				break;
+
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
 				break;
diff --git a/src/include/commands/comment.h b/src/include/commands/comment.h
index 3d61b44..edc94d1 100644
--- a/src/include/commands/comment.h
+++ b/src/include/commands/comment.h
@@ -29,7 +29,7 @@
  *------------------------------------------------------------------
  */
 
-extern Oid	CommentObject(CommentStmt *stmt);
+extern Oid	CommentObject(CommentStmt *stmt, uint32 *objectSubId);
 
 extern void DeleteComments(Oid oid, Oid classoid, int32 subid);
 
-- 
2.1.4

0005-deparse-core-have-event-triggers-support-SECURITY-LA.patchtext/x-diff; charset=us-asciiDownload
>From fc85e88f91b109d72ef99822e2909808f005cc4e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 10 Feb 2015 17:35:15 -0300
Subject: [PATCH 05/42] deparse/core: have event triggers support SECURITY
 LABEL

Have triggers on events ddl_command_start and ddl_command_end be invoked
on execution of SECURITY LABEL.

Author: Andres Freund
---
 doc/src/sgml/event-trigger.sgml      |  8 ++++++++
 src/backend/commands/event_trigger.c |  3 ++-
 src/backend/tcop/utility.c           | 25 +++++++++++++++++++++----
 3 files changed, 31 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 58e0d9d..4472400 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -37,6 +37,7 @@
    <para>
      The <literal>ddl_command_start</> event occurs just before the
      execution of a <literal>CREATE</>, <literal>ALTER</>, <literal>DROP</>,
+     <literal>SECURITY LABEL</>,
      <literal>COMMENT</>, <literal>GRANT</> or <literal>REVOKE</>
      command.  No check whether the affected object exists or doesn't exist is
      performed before the event trigger fires.
@@ -745,6 +746,13 @@
         <entry align="center"><literal>-</literal></entry>
        </row>
        <row>
+        <entry align="left"><literal>SECURITY LABEL</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+       </row>
+       <row>
         <entry align="left"><literal>SELECT INTO</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 87b606c..dcf5b98 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -271,7 +271,8 @@ check_ddl_tag(const char *tag)
 		pg_strcasecmp(tag, "GRANT") == 0 ||
 		pg_strcasecmp(tag, "REVOKE") == 0 ||
 		pg_strcasecmp(tag, "DROP OWNED") == 0 ||
-		pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0)
+		pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0 ||
+		pg_strcasecmp(tag, "SECURITY LABEL") == 0)
 		return EVENT_TRIGGER_COMMAND_TAG_OK;
 
 	/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ab13bfd..13d03c5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -513,10 +513,6 @@ standard_ProcessUtility(Node *parsetree,
 			ExecuteTruncate((TruncateStmt *) parsetree);
 			break;
 
-		case T_SecLabelStmt:
-			ExecSecLabelStmt((SecLabelStmt *) parsetree);
-			break;
-
 		case T_CopyStmt:
 			{
 				uint64		processed;
@@ -851,6 +847,19 @@ standard_ProcessUtility(Node *parsetree,
 				break;
 			}
 
+		case T_SecLabelStmt:
+			{
+				SecLabelStmt *stmt = (SecLabelStmt *) parsetree;
+
+				if (EventTriggerSupportsObjectType(stmt->objtype))
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				else
+					ExecSecLabelStmt(stmt);
+				break;
+			}
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(parsetree, queryString,
@@ -1355,6 +1364,14 @@ ProcessUtilitySlow(Node *parsetree,
 				AlterPolicy((AlterPolicyStmt *) parsetree);
 				break;
 
+			case T_SecLabelStmt:
+				{
+					SecLabelStmt *stmt = (SecLabelStmt *) parsetree;
+
+					ExecSecLabelStmt(stmt);
+				}
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(parsetree));
-- 
2.1.4

0006-deparse-core-have-ALTER-TABLE-return-table-OID-and-o.patchtext/x-diff; charset=us-asciiDownload
>From 347f36e346714c92ff673541da566d43025ebb6c 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 06/42] deparse/core: have ALTER TABLE return table OID and
 other data

Currently, most if not all CREATE commands return the OID of the created
object.  This is useful for event triggers to try to figure out exactly
what happened.  This patch extends this idea a bit further to cover
ALTER TABLE as well: auxilliary info such as a second OID or a column
number are also returned for each subcommand of the ALTER TABLE.  This
OID or attnum can later be used to determine, for instance, which
constraint was added by ALTER TABLE ADD CONSTRAINT, or which parent got
dropped from the inherit-from parents list by NO INHERIT.  This makes it
possible to decode with precision exactly what happened during execution
of any ALTER TABLE command.

There is no user-visible change here; this patch makes the info
available for later.
---
 src/backend/catalog/heap.c          |  40 +++--
 src/backend/catalog/index.c         |   7 +-
 src/backend/catalog/pg_constraint.c |   2 +
 src/backend/commands/tablecmds.c    | 307 ++++++++++++++++++++++++------------
 src/include/catalog/heap.h          |   3 +-
 src/include/catalog/index.h         |   2 +-
 6 files changed, 244 insertions(+), 117 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 17f7266..0247da6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -97,7 +97,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,
@@ -1844,7 +1844,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)
 {
@@ -1949,6 +1949,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	 */
 	InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
 								  RelationGetRelid(rel), attnum, is_internal);
+
+	return attrdefOid;
 }
 
 /*
@@ -1956,8 +1958,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)
@@ -1967,6 +1971,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
+	Oid			constrOid;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2018,7 +2023,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 */
@@ -2049,11 +2054,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
@@ -2065,7 +2074,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 */
 
 	/*
@@ -2082,12 +2091,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:
@@ -2175,6 +2186,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,
@@ -2195,10 +2207,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;
@@ -2218,6 +2231,7 @@ AddRelationNewConstraints(Relation rel,
 	{
 		Constraint *cdef = (Constraint *) lfirst(cell);
 		char	   *ccname;
+		Oid			constrOid;
 
 		if (cdef->contype != CONSTR_CHECK)
 			continue;
@@ -2327,13 +2341,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 f85ed93..5773298 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1111,7 +1111,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
@@ -1128,7 +1129,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,
@@ -1316,6 +1317,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 77fe918..3c756f8 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 1ee2901..4b7b032 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -281,9 +281,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);
@@ -325,26 +325,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);
@@ -352,20 +352,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,
@@ -376,9 +376,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);
@@ -391,7 +391,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,
+static Oid ATExecClusterOn(Relation rel, const char *indexName,
 				LOCKMODE lockmode);
 static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
 static bool ATPrepChangePersistence(Relation rel, bool toLogged);
@@ -406,10 +406,10 @@ static void ATExecEnableDisableTrigger(Relation rel, char *trigname,
 static void ATExecEnableDisableRule(Relation rel, char *rulename,
 						char fires_when, LOCKMODE lockmode);
 static void ATPrepAddInherit(Relation child_rel);
-static void ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
-static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
+static Oid ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
+static Oid ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
-static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
+static Oid ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
 static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
 static void ATExecGenericOptions(Relation rel, List *options);
@@ -619,6 +619,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;
@@ -1732,6 +1733,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;
@@ -3398,78 +3400,95 @@ static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
+	AttrNumber	colno = InvalidAttrNumber;
+	Oid			newoid = InvalidOid;
+
+	/* temporary measure to silence unused-variable compiler warnings */
+	(void) colno;
+	(void) newoid;
+
 	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,
@@ -3482,10 +3501,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),
@@ -3493,7 +3514,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);
@@ -3582,19 +3603,20 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 
 		case AT_AddInherit:
-			ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
+			newoid = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
 			break;
 		case AT_DropInherit:
-			ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
+			newoid = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
 			break;
 		case AT_AddOf:
-			ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
+			newoid = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
 			break;
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
 		case AT_ReplicaIdentity:
-			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def,
+								  lockmode);
 			break;
 		case AT_EnableRowSecurity:
 			ATExecEnableRowSecurity(rel);
@@ -4595,7 +4617,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)
@@ -4680,7 +4702,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					  colDef->colname, RelationGetRelationName(rel))));
 
 			heap_close(attrdesc, RowExclusiveLock);
-			return;
+			return InvalidAttrNumber;
 		}
 	}
 
@@ -4926,6 +4948,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 		heap_close(childrel, NoLock);
 	}
+
+	return newattnum;
 }
 
 /*
@@ -5040,7 +5064,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;
@@ -5125,18 +5149,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)
 {
@@ -5180,18 +5208,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)
 {
@@ -5242,6 +5274,8 @@ ATExecColumnDefault(Relation rel, const char *colName,
 		AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
 								  false, true, false);
 	}
+
+	return attnum;
 }
 
 /*
@@ -5271,13 +5305,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);
@@ -5312,7 +5347,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\"",
@@ -5331,9 +5367,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)
 {
@@ -5341,6 +5379,7 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	HeapTuple	tuple,
 				newtuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 	Datum		datum,
 				newOptions;
 	bool		isnull;
@@ -5359,7 +5398,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\"",
@@ -5398,12 +5438,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;
@@ -5411,6 +5453,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);
@@ -5443,7 +5486,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\"",
@@ -5473,6 +5517,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return attnum;
 }
 
 
@@ -5697,8 +5743,10 @@ 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 void
+static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
@@ -5740,12 +5788,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)
 {
@@ -5755,6 +5805,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	IndexInfo  *indexInfo;
 	char	   *constraintName;
 	char		constraintType;
+	Oid			conOid;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(OidIsValid(index_oid));
@@ -5799,30 +5850,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));
 
 	/*
@@ -5833,9 +5888,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:
@@ -5866,17 +5922,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.
  *
@@ -5895,7 +5956,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)
@@ -5904,6 +5965,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)
@@ -5946,6 +6008,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 */
@@ -5961,7 +6030,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.
@@ -5969,7 +6038,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
@@ -6007,16 +6076,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)
 {
@@ -6415,6 +6487,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Close pk table, but keep lock until we've committed.
 	 */
 	heap_close(pkrel, NoLock);
+
+	return constrOid;
 }
 
 /*
@@ -6427,7 +6501,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)
 {
@@ -6438,6 +6512,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;
@@ -6497,6 +6572,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);
 
@@ -6544,6 +6621,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 /*
@@ -6554,7 +6633,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)
 {
@@ -6564,6 +6643,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);
 
@@ -6605,9 +6685,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;
 
 			/*
@@ -6622,7 +6703,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 conid);
+										 constrOid);
 			heap_close(refrel, NoLock);
 
 			/*
@@ -6703,6 +6784,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 
@@ -7789,7 +7872,7 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
 	}
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
@@ -8183,9 +8266,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 	/* Cleanup */
 	heap_freetuple(heapTup);
+
+	return attnum;
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnGenericOptions(Relation rel,
 								const char *colName,
 								List *options,
@@ -8204,9 +8289,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);
@@ -8233,7 +8319,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)));
@@ -8282,6 +8369,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	heap_close(attrel, RowExclusiveLock);
 
 	heap_freetuple(newtuple);
+
+	return attnum;
 }
 
 /*
@@ -8923,7 +9012,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;
@@ -8941,6 +9030,8 @@ ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 
 	/* And do the work */
 	mark_index_clustered(rel, indexOid, false);
+
+	return indexOid;
 }
 
 /*
@@ -9641,11 +9732,12 @@ ATPrepAddInherit(Relation child_rel)
 				 errmsg("cannot change inheritance of typed table")));
 }
 
-static void
+static Oid
 ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 {
 	Relation	parent_rel,
 				catalogRelation;
+	Oid			parent_oid;
 	SysScanDesc scan;
 	ScanKeyData key;
 	HeapTuple	inheritsTuple;
@@ -9765,11 +9857,15 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 							 inhseqno + 1,
 							 catalogRelation);
 
+	parent_oid = RelationGetRelid(parent_rel);
+
 	/* Now we're done with pg_inherits */
 	heap_close(catalogRelation, RowExclusiveLock);
 
 	/* keep our lock on the parent relation until commit */
 	heap_close(parent_rel, NoLock);
+
+	return parent_oid;
 }
 
 /*
@@ -10038,10 +10134,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
  * coninhcount and conislocal for inherited constraints are adjusted in
  * exactly the same way.
  */
-static void
+static Oid
 ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 {
 	Relation	parent_rel;
+	Oid			parent_oid;
 	Relation	catalogRelation;
 	SysScanDesc scan;
 	ScanKeyData key[3];
@@ -10210,6 +10307,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 		}
 	}
 
+	parent_oid = RelationGetRelid(parent_rel);
+
 	systable_endscan(scan);
 	heap_close(catalogRelation, RowExclusiveLock);
 
@@ -10228,6 +10327,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 
 	/* keep our lock on the parent relation until commit */
 	heap_close(parent_rel, NoLock);
+
+	return parent_oid;
 }
 
 /*
@@ -10285,8 +10386,10 @@ drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid)
  * TABLE OF.  All attname, atttypid, atttypmod and attcollation must match.  The
  * subject table must not have inheritance parents.  These restrictions ensure
  * that you cannot create a configuration impossible with CREATE TABLE OF alone.
+ *
+ * The OID of the type is returned.
  */
-static void
+static Oid
 ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 {
 	Oid			relid = RelationGetRelid(rel);
@@ -10415,6 +10518,8 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	heap_close(relationRelation, RowExclusiveLock);
 
 	ReleaseSysCache(typetuple);
+
+	return typeid;
 }
 
 /*
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index e5c204d..fe1ddd9 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 e7cc7a0..e22ef8f 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -63,7 +63,7 @@ extern Oid index_create(Relation heapRelation,
 			 bool is_internal,
 			 bool if_not_exists);
 
-extern void index_constraint_create(Relation heapRelation,
+extern Oid index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
 						const char *constraintName,
-- 
2.1.4

0007-deparse-infrastructure-needed-for-command-deparsing.patchtext/x-diff; charset=us-asciiDownload
>From d03a8edcefd8f0ea1c46b315c446f96c61146a85 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 24 Sep 2014 15:53:04 -0300
Subject: [PATCH 07/42] deparse: infrastructure needed for command deparsing

This patch introduces unused infrastructure for command deparsing.
There are three main parts:

1. A list (stash) of executed commands in Node format, stored in the
event trigger state.  At ddl_command_end, the stored items can be
extracted.  For now this only support "basic" commands (in particular
not ALTER TABLE or GRANT).  It's useful enough to cover all CREATE
command as well as many simple ALTER forms.

2. Support code to enable writing routines that convert the Node format
into a JSON blob.  This JSON representation allows extracting and
modifying individual parts of the command.

3. Code to convert the JSON blobs back into plain strings.

No actual routines to convert Node into JSON is provided by this patch;
that is left to later patches.  This split is only presented as is for
ease of review.
---
 src/backend/catalog/objectaddress.c  | 118 +++++
 src/backend/commands/event_trigger.c | 220 +++++++-
 src/backend/tcop/Makefile            |   2 +-
 src/backend/tcop/deparse_utility.c   | 957 +++++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           |   2 +
 src/backend/utils/adt/Makefile       |   2 +-
 src/backend/utils/adt/ddl_json.c     | 676 +++++++++++++++++++++++++
 src/backend/utils/adt/format_type.c  | 113 ++++-
 src/backend/utils/adt/jsonb.c        |   4 +-
 src/include/catalog/objectaddress.h  |   2 +
 src/include/catalog/pg_proc.h        |   5 +-
 src/include/commands/event_trigger.h |   3 +
 src/include/commands/extension.h     |   2 +-
 src/include/nodes/parsenodes.h       |   2 +
 src/include/tcop/deparse_utility.h   |  61 +++
 src/include/utils/builtins.h         |   7 +
 16 files changed, 2159 insertions(+), 17 deletions(-)
 create mode 100644 src/backend/tcop/deparse_utility.c
 create mode 100644 src/backend/utils/adt/ddl_json.c
 create mode 100644 src/include/tcop/deparse_utility.h

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d899dd7..700533c 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -849,6 +849,124 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 }
 
 /*
+ * Return the OID of the catalog corresponding to the given object type
+ */
+Oid
+get_objtype_catalog_oid(ObjectType objtype)
+{
+	Oid		catalog_id;
+
+	switch (objtype)
+	{
+		case OBJECT_INDEX:
+		case OBJECT_COMPOSITE:
+		case OBJECT_SEQUENCE:
+		case OBJECT_TABLE:
+		case OBJECT_VIEW:
+		case OBJECT_MATVIEW:
+		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_COLUMN:
+			catalog_id = RelationRelationId;
+			break;
+		case OBJECT_RULE:
+			catalog_id = RewriteRelationId;
+			break;
+		case OBJECT_TRIGGER:
+			catalog_id = TriggerRelationId;
+			break;
+		case OBJECT_DOMCONSTRAINT:
+		case OBJECT_TABCONSTRAINT:
+			catalog_id = ConstraintRelationId;
+			break;
+		case OBJECT_POLICY:
+			catalog_id = PolicyRelationId;
+			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 catalog_id;
+}
+
+/*
  * Find an ObjectAddress for a type of object that is identified by an
  * unqualified name.
  */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index dcf5b98..b258d72 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -25,16 +25,20 @@
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/trigger.h"
 #include "funcapi.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -53,6 +57,7 @@ typedef struct EventTriggerQueryState
 	int			table_rewrite_reason;	/* AT_REWRITE reason */
 
 	MemoryContext cxt;
+	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -71,6 +76,7 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
 	{"AGGREGATE", true},
 	{"CAST", true},
@@ -1060,6 +1066,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CAST:
 		case OBJECT_COLUMN:
 		case OBJECT_COLLATION:
+		case OBJECT_COMPOSITE:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFAULT:
 		case OBJECT_DOMAIN:
@@ -1088,6 +1095,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
+		case OBJECT_USER_MAPPING:
 		case OBJECT_VIEW:
 			return true;
 	}
@@ -1193,14 +1201,6 @@ EventTriggerBeginCompleteQuery(void)
 	EventTriggerQueryState *state;
 	MemoryContext cxt;
 
-	/*
-	 * Currently, sql_drop and table_rewrite events are the only reason to
-	 * have event trigger state at all; so if there are none, don't install
-	 * one.
-	 */
-	if (!trackDroppedObjectsNeeded())
-		return false;
-
 	cxt = AllocSetContextCreate(TopMemoryContext,
 								"event trigger state",
 								ALLOCSET_DEFAULT_MINSIZE,
@@ -1211,7 +1211,7 @@ EventTriggerBeginCompleteQuery(void)
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
-
+	state->stash = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
 
@@ -1536,3 +1536,205 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
 }
+
+/*
+ * EventTriggerStashCommand
+ * 		Save data about a simple DDL command that was just executed
+ */
+void
+EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype,
+						 Oid secondaryOid, Node *parsetree)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_Simple;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.simple.objectId = objectId;
+	stashed->d.simple.objtype = objtype;
+	stashed->d.simple.objectSubId = objectSubId;
+	stashed->d.simple.secondaryOid = secondaryOid;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+Datum
+pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_get_creation_commands()")));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->stash)
+	{
+		StashedCommand *cmd = lfirst(lc);
+		char	   *command;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid; in those cases, return an empty
+		 * command instead of trying to soldier on.
+		 *
+		 * One might think that a viable alternative would be to look up the
+		 * Oid of the existing object and run the deparse with that.  But since
+		 * the parse tree might be different from the one that created the
+		 * object in the first place, we might not end up in a consistent state
+		 * anyway.
+		 */
+		if (cmd->type == SCT_Simple &&
+			!OidIsValid(cmd->d.simple.objectId))
+			continue;
+
+		command = deparse_utility_command(cmd);
+
+		/*
+		 * Some parse trees return NULL when deparse is attempted; we don't
+		 * emit anything for them.
+		 */
+		if (command != NULL)
+		{
+			Datum		values[9];
+			bool		nulls[9];
+			ObjectAddress addr;
+			int			i = 0;
+
+			MemSet(nulls, 0, sizeof(nulls));
+
+			if (cmd->type == SCT_Simple)
+			{
+				Oid			classId;
+				Oid			objId;
+				uint32		objSubId;
+				const char *tag;
+				char	   *identity;
+				char	   *type;
+				char	   *schema = NULL;
+
+				if (cmd->type == SCT_Simple)
+				{
+					classId = get_objtype_catalog_oid(cmd->d.simple.objtype);
+					objId = cmd->d.simple.objectId;
+					objSubId = cmd->d.simple.objectSubId;
+				}
+
+				tag = CreateCommandTag(cmd->parsetree);
+				addr.classId = classId;
+				addr.objectId = objId;
+				addr.objectSubId = objSubId;
+
+				type = getObjectTypeDescription(&addr);
+				identity = getObjectIdentity(&addr);
+
+				/*
+				 * Obtain schema name, if any ("pg_temp" if a temp object)
+				 */
+				if (is_objectclass_supported(addr.classId))
+				{
+					AttrNumber	nspAttnum;
+
+					nspAttnum = get_object_attnum_namespace(addr.classId);
+					if (nspAttnum != InvalidAttrNumber)
+					{
+						Relation	catalog;
+						HeapTuple	objtup;
+						Oid			schema_oid;
+						bool		isnull;
+
+						catalog = heap_open(addr.classId, AccessShareLock);
+						objtup = get_catalog_object_by_oid(catalog,
+														   addr.objectId);
+						if (!HeapTupleIsValid(objtup))
+							elog(ERROR, "cache lookup failed for object %u/%u",
+								 addr.classId, addr.objectId);
+						schema_oid = heap_getattr(objtup, nspAttnum,
+												  RelationGetDescr(catalog), &isnull);
+						if (isnull)
+							elog(ERROR, "invalid null namespace in object %u/%u/%d",
+								 addr.classId, addr.objectId, addr.objectSubId);
+						if (isAnyTempNamespace(schema_oid))
+							schema = pstrdup("pg_temp");
+						else
+							schema = get_namespace_name(schema_oid);
+
+						heap_close(catalog, AccessShareLock);
+					}
+				}
+
+				/* classid */
+				values[i++] = ObjectIdGetDatum(addr.classId);
+				/* objid */
+				values[i++] = ObjectIdGetDatum(addr.objectId);
+				/* objsubid */
+				values[i++] = Int32GetDatum(addr.objectSubId);
+				/* command tag */
+				values[i++] = CStringGetTextDatum(tag);
+				/* object_type */
+				values[i++] = CStringGetTextDatum(type);
+				/* schema */
+				if (schema == NULL)
+					nulls[i++] = true;
+				else
+					values[i++] = CStringGetTextDatum(schema);
+				/* identity */
+				values[i++] = CStringGetTextDatum(identity);
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+			}
+
+			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+		}
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
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..8463321
--- /dev/null
+++ b/src/backend/tcop/deparse_utility.c
@@ -0,0 +1,957 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.c
+ *	  Functions to convert utility commands to machine-parseable
+ *	  representation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ *
+ * This is intended to provide JSON blobs representing DDL commands, which can
+ * later be re-processed into plain strings by well-defined sprintf-like
+ * expansion.  These JSON objects are intended to allow for machine-editing of
+ * the commands, by replacing certain nodes within the objects.
+ *
+ * Much of the information in the output blob actually comes from system
+ * catalogs, not from the command parse node, as it is impossible to reliably
+ * construct a fully-specified command (i.e. one not dependent on search_path
+ * etc) looking only at the parse node.
+ *
+ * IDENTIFICATION
+ *	  src/backend/tcop/deparse_utility.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/heap.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "lib/ilist.h"
+#include "lib/stringinfo.h"
+#include "nodes/makefuncs.h"
+#include "nodes/parsenodes.h"
+#include "parser/analyze.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/jsonb.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Before they are turned into JSONB representation, each command is
+ * represented as an object tree, using the structs below.
+ */
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeInteger,
+	ObjTypeFloat,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjTree
+{
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+
+	union
+	{
+		bool		boolean;
+		char	   *string;
+		int64		integer;
+		float8		flt;
+		ObjTree	   *object;
+		List	   *array;
+	} value;
+	slist_node	node;
+} ObjElem;
+
+static ObjElem *new_null_object(void);
+static ObjElem *new_bool_object(bool value);
+static ObjElem *new_string_object(char *value);
+static ObjElem *new_object_object(ObjTree *value);
+static ObjElem *new_array_object(List *array);
+static ObjElem *new_integer_object(int64 value);
+static ObjElem *new_float_object(float8 value);
+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);
+#ifdef NOT_USED
+static void append_integer_object(ObjTree *tree, char *name, int64 value);
+#endif
+static void append_float_object(ObjTree *tree, char *name, float8 value);
+static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
+static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+
+/*
+ * Allocate a new object tree to store parameter values.
+ */
+static ObjTree *
+new_objtree(void)
+{
+	ObjTree    *params;
+
+	params = palloc(sizeof(ObjTree));
+	params->numParams = 0;
+	slist_init(&params->params);
+
+	return params;
+}
+
+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one, a
+ * name (string), type (from the ObjType enum) and value must be supplied.  The
+ * value must match the type given; for instance, ObjTypeInteger requires an
+ * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of
+ * ObjElem), ObjTypeObject requires an ObjTree, and so on.  Each element type *
+ * must match the conversion specifier given in the format string, as described
+ * in pg_event_trigger_expand_command, q.v.
+ *
+ * Note we don't have the luxury of sprintf-like compiler warnings for
+ * malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree();
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		ObjTree    *value;
+		ObjType		type;
+		ObjElem	   *elem;
+		char	   *name;
+		char	   *strval;
+		bool		boolval;
+		List	   *list;
+		int64		number;
+		float8		fnumber;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/* Null params don't have a value (obviously) */
+		if (type == ObjTypeNull)
+		{
+			append_null_object(tree, name);
+			continue;
+		}
+
+		/*
+		 * For all other param types there must be a value in the varargs.
+		 * Fetch it and add the fully formed subobject into the main object.
+		 */
+		switch (type)
+		{
+			case ObjTypeBool:
+				boolval = va_arg(args, int);
+				elem = new_bool_object(boolval);
+				break;
+			case ObjTypeString:
+				strval = va_arg(args, char *);
+				elem = new_string_object(strval);
+				break;
+			case ObjTypeObject:
+				value = va_arg(args, ObjTree *);
+				elem = new_object_object(value);
+				break;
+			case ObjTypeArray:
+				list = va_arg(args, List *);
+				elem = new_array_object(list);
+				break;
+			case ObjTypeInteger:
+				number = va_arg(args, int64);
+				elem = new_integer_object(number);
+				break;
+			case ObjTypeFloat:
+				fnumber = va_arg(args, double);
+				elem = new_float_object(fnumber);
+				break;
+			default:
+				elog(ERROR, "invalid parameter type %d", type);
+		}
+
+		elem->name = name;
+		append_premade_object(tree, elem);
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/* Allocate a new parameter with a NULL value */
+static ObjElem *
+new_null_object(void)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = NULL;
+	param->objtype = ObjTypeNull;
+
+	return param;
+}
+
+/* Append a NULL object to a tree */
+static void
+append_null_object(ObjTree *tree, char *name)
+{
+	ObjElem    *param;
+
+	param = new_null_object();
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new boolean parameter */
+static ObjElem *
+new_bool_object(bool value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeBool;
+	param->value.boolean = value;
+
+	return param;
+}
+
+/* Append a boolean parameter to a tree */
+static void
+append_bool_object(ObjTree *tree, char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = new_bool_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new string object */
+static ObjElem *
+new_string_object(char *value)
+{
+	ObjElem    *param;
+
+	Assert(value);
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeString;
+	param->value.string = value;
+
+	return param;
+}
+
+/*
+ * Append a string parameter to a tree.
+ */
+static void
+append_string_object(ObjTree *tree, char *name, char *value)
+{
+	ObjElem	   *param;
+
+	Assert(name);
+	param = new_string_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+static ObjElem *
+new_integer_object(int64 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeInteger;
+	param->value.integer = value;
+
+	return param;
+}
+
+static ObjElem *
+new_float_object(float8 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeFloat;
+	param->value.flt = value;
+
+	return param;
+}
+
+#ifdef NOT_USED
+/*
+ * Append an int64 parameter to a tree.
+ */
+static void
+append_integer_object(ObjTree *tree, char *name, int64 value)
+{
+	ObjElem	   *param;
+
+	param = new_integer_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+#endif
+
+/*
+ * Append a float8 parameter to a tree.
+ */
+static void
+append_float_object(ObjTree *tree, char *name, float8 value)
+{
+	ObjElem	   *param;
+
+	param = new_float_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new object parameter */
+static ObjElem *
+new_object_object(ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeObject;
+	param->value.object = value;
+
+	return param;
+}
+
+/* Append an object parameter to a tree */
+static void
+append_object_object(ObjTree *tree, char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	Assert(name);
+	param = new_object_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new array parameter */
+static ObjElem *
+new_array_object(List *array)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeArray;
+	param->value.array = array;
+
+	return param;
+}
+
+/* Append an array parameter to a tree */
+static void
+append_array_object(ObjTree *tree, char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = new_array_object(array);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Append a preallocated parameter to a tree */
+static inline void
+append_premade_object(ObjTree *tree, ObjElem *elem)
+{
+	slist_push_head(&tree->params, &elem->node);
+	tree->numParams++;
+}
+
+/*
+ * Helper for objtree_to_jsonb: process an individual element from an object or
+ * an array into the output parse state.
+ */
+static void
+objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object,
+						 JsonbIteratorToken elem_token)
+{
+	ListCell   *cell;
+	JsonbValue	val;
+
+	switch (object->objtype)
+	{
+		case ObjTypeNull:
+			val.type = jbvNull;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeString:
+			val.type = jbvString;
+			val.val.string.len = strlen(object->value.string);
+			val.val.string.val = object->value.string;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeInteger:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeFloat:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(float8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeBool:
+			val.type = jbvBool;
+			val.val.boolean = object->value.boolean;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeObject:
+			/* recursively add the object into the existing parse state */
+			objtree_to_jsonb_rec(object->value.object, state);
+			break;
+
+		case ObjTypeArray:
+			pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+			foreach(cell, object->value.array)
+			{
+				ObjElem   *elem = lfirst(cell);
+
+				objtree_to_jsonb_element(state, elem, WJB_ELEM);
+			}
+			pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+			break;
+
+		default:
+			elog(ERROR, "unrecognized object type %d", object->objtype);
+			break;
+	}
+}
+
+/*
+ * Recursive helper for objtree_to_jsonb
+ */
+static JsonbValue *
+objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state)
+{
+	slist_iter	iter;
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	slist_foreach(iter, &tree->params)
+	{
+		ObjElem    *object = slist_container(ObjElem, node, iter.cur);
+		JsonbValue	key;
+
+		/* Push the key first */
+		key.type = jbvString;
+		key.val.string.len = strlen(object->name);
+		key.val.string.val = object->name;
+		pushJsonbValue(&state, WJB_KEY, &key);
+
+		/* Then process the value according to its type */
+		objtree_to_jsonb_element(state, object, WJB_VALUE);
+	}
+
+	return pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Create a JSONB representation from an ObjTree.
+ */
+static Jsonb *
+objtree_to_jsonb(ObjTree *tree)
+{
+	JsonbValue *value;
+
+	value = objtree_to_jsonb_rec(tree, NULL);
+	return JsonbValueToJsonb(value);
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{
+	ObjTree    *typeParam;
+	Oid			typnspid;
+	char	   *typnsp;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		is_array;
+
+	format_type_detailed(typeId, typmod,
+						 &typnspid, &typename, &typmodstr, &is_array);
+
+	if (!OidIsValid(typnspid))
+		typnsp = pstrdup("");
+	else if (isAnyTempNamespace(typnspid))
+		typnsp = pstrdup("pg_temp");
+	else
+		typnsp = get_namespace_name(typnspid);
+
+	/* We don't use new_objtree_VA here because types don't have a "fmt" */
+	typeParam = new_objtree();
+	append_string_object(typeParam, "schemaname", typnsp);
+	append_string_object(typeParam, "typename", typename);
+	append_string_object(typeParam, "typmod", typmodstr);
+	append_bool_object(typeParam, "is_array", is_array);
+
+	return typeParam;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements
+ *
+ * Elements "schemaname" and "objname" are set.  If the namespace OID
+ * corresponds to a temp schema, that's set to "pg_temp".
+ *
+ * The difference between those two element types is whether the objname will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static ObjTree *
+new_objtree_for_qualname(Oid nspid, char *name)
+{
+	ObjTree    *qualified;
+	char	   *namespace;
+
+	/*
+	 * We don't use new_objtree_VA here because these names don't have a "fmt"
+	 */
+	qualified = new_objtree();
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+	append_string_object(qualified, "schemaname", namespace);
+	append_string_object(qualified, "objname", pstrdup(name));
+
+	return qualified;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements, with the object specified
+ * by classId/objId
+ *
+ * Elements "schemaname" and "objname" are set.  If the object is a temporary
+ * object, the schema name is set to "pg_temp".
+ */
+static ObjTree *
+new_objtree_for_qualname_id(Oid classId, Oid objectId)
+{
+	ObjTree    *qualified;
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		objnsp;
+	Datum		objname;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	bool		isnull;
+
+	catalog = heap_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL namespace");
+	objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL name");
+
+	qualified = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+										 NameStr(*DatumGetName(objname)));
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function contains a large switch that mirrors that in
+ * ProcessUtilitySlow.  All cases covered there should also be covered here.
+ */
+static ObjTree *
+deparse_simple_command(StashedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+	ObjTree	   *command;
+
+	Assert(cmd->type == SCT_Simple);
+
+	parsetree = cmd->parsetree;
+	objectId = cmd->d.simple.objectId;
+
+	/* This switch needs to handle everything that ProcessUtilitySlow does */
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			command = NULL;
+			break;
+
+		case T_CreateStmt:
+			command = NULL;
+			break;
+
+		case T_CreateForeignTableStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTableStmt:
+		case T_AlterTableMoveAllStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterDomainStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+			/* other local objects */
+		case T_DefineStmt:
+			command = NULL;
+			break;
+
+		case T_IndexStmt:
+			command = NULL;
+			break;
+
+		case T_CreateExtensionStmt:
+			command = NULL;
+			break;
+
+		case T_AlterExtensionStmt:
+			command = NULL;
+			break;
+
+		case T_AlterExtensionContentsStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropUserMappingStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_ImportForeignSchemaStmt:
+			/* generated commands are stashed individually */
+			elog(ERROR, "unexpected command %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
+			command = NULL;
+			break;
+
+		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
+			command = NULL;
+			break;
+
+		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+			command = NULL;
+			break;
+
+		case T_AlterEnumStmt:
+			command = NULL;
+			break;
+
+		case T_ViewStmt:		/* CREATE VIEW */
+			command = NULL;
+			break;
+
+		case T_CreateFunctionStmt:
+			command = NULL;
+			break;
+
+		case T_AlterFunctionStmt:
+			command = NULL;
+			break;
+
+		case T_RuleStmt:
+			command = NULL;
+			break;
+
+		case T_CreateSeqStmt:
+			command = NULL;
+			break;
+
+		case T_AlterSeqStmt:
+			command = NULL;
+			break;
+
+		case T_CreateTableAsStmt:
+			/* XXX handle at least the CREATE MATVIEW case? */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_RefreshMatViewStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateTrigStmt:
+			command = NULL;
+			break;
+
+		case T_CreatePLangStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateDomainStmt:
+			command = NULL;
+			break;
+
+		case T_CreateConversionStmt:
+			command = NULL;
+			break;
+
+		case T_CreateCastStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpClassStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpFamilyStmt:
+			command = NULL;
+			break;
+
+		case T_AlterOpFamilyStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSDictionaryStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSConfigurationStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_RenameStmt:
+			command = NULL;
+			break;
+
+		case T_AlterObjectSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterOwnerStmt:
+			command = NULL;
+			break;
+
+		case T_CommentStmt:
+			command = NULL;
+			break;
+
+		case T_GrantStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropOwnedStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_AlterDefaultPrivilegesStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePolicyStmt:	/* CREATE POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterPolicyStmt:		/* ALTER POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_SecLabelStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		default:
+			command = NULL;
+			elog(LOG, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return command;
+}
+
+/*
+ * Given a utility command parsetree and the OID of the corresponding object,
+ * return a JSON representation of the command.
+ *
+ * The command is expanded fully, so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+char *
+deparse_utility_command(StashedCommand *cmd)
+{
+	OverrideSearchPath *overridePath;
+	MemoryContext	oldcxt;
+	MemoryContext	tmpcxt;
+	ObjTree		   *tree;
+	char		   *command;
+	StringInfoData  str;
+
+	/*
+	 * Allocate everything done by the deparsing routines into a temp context,
+	 * to avoid having to sprinkle them with memory handling code; but allocate
+	 * the output StringInfo before switching.
+	 */
+	initStringInfo(&str);
+	tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "deparse ctx",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * in order to obtain deparsed versions of expressions.  In such results,
+	 * we want all object names to be qualified, so that results are "portable"
+	 * to environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = false;
+	PushOverrideSearchPath(overridePath);
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			tree = deparse_simple_command(cmd);
+			break;
+		default:
+			elog(ERROR, "unexpected deparse node type %d", cmd->type);
+	}
+
+	PopOverrideSearchPath();
+
+	if (tree)
+	{
+		Jsonb *jsonb;
+
+		jsonb = objtree_to_jsonb(tree);
+		command = JsonbToCString(&str, &jsonb->root, 128);
+	}
+	else
+		command = NULL;
+
+	/*
+	 * Clean up.  Note that since we created the StringInfo in the caller's
+	 * context, the output string is not deleted here.
+	 */
+	MemoryContextSwitchTo(oldcxt);
+	MemoryContextDelete(tmpcxt);
+
+	return command;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 13d03c5..ee6799a 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -873,6 +873,8 @@ standard_ProcessUtility(Node *parsetree,
  * The "Slow" variant of ProcessUtility should only receive statements
  * supported by the event triggers facility.  Therefore, we always
  * perform the trigger support calls if the context allows it.
+ *
+ * See deparse_utility_command, which must be kept in sync with this.
  */
 static void
 ProcessUtilitySlow(Node *parsetree,
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 20e5ff1..110c6ee 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -18,7 +18,7 @@ endif
 # keep this list arranged alphabetically or it gets to be a mess
 OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
 	array_userfuncs.o arrayutils.o ascii.o bool.o \
-	cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \
+	cash.o char.o date.o datetime.o datum.o dbsize.o ddl_json.o domains.o \
 	encode.o enum.o float.o format_type.o formatting.o genfile.o \
 	geo_ops.o geo_selfuncs.o inet_cidr_ntop.o inet_net_pton.o int.o \
 	int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
diff --git a/src/backend/utils/adt/ddl_json.c b/src/backend/utils/adt/ddl_json.c
new file mode 100644
index 0000000..5b0326c
--- /dev/null
+++ b/src/backend/utils/adt/ddl_json.c
@@ -0,0 +1,676 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_json.c
+ *	  JSON code related to DDL command deparsing
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/ddl_json.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "lib/stringinfo.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+
+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	SpecNumber,
+	SpecStringLiteral,
+	SpecIdentifier
+} convSpecifier;
+
+typedef enum
+{
+	tv_absent,
+	tv_true,
+	tv_false
+} trivalue;
+
+static void expand_one_jsonb_element(StringInfo out, char *param,
+						 JsonbValue *jsonval, convSpecifier specifier,
+						 const char *fmt);
+static void expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep,
+				   convSpecifier specifier, const char *fmt);
+static void fmtstr_error_callback(void *arg);
+
+static trivalue
+find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	bool		result;
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+		return tv_absent;
+	if (value->type != jbvBool)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not of type boolean",
+						keyname)));
+	result = value->val.boolean ? tv_true : tv_false;
+	pfree(value);
+
+	return result;
+}
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvString, an error is raised.  If it doesn't
+ * exist, an error is raised if missing_ok; otherwise return NULL.
+ *
+ * If it exists and is a string, a freshly palloc'ed copy is returned.
+ *
+ * If *length is not NULL, it is set to the length of the string.
+ */
+static char *
+find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname,
+							  bool missing_ok, int *length)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	char	   *str;
+
+	/* XXX verify that this is an object, not an array */
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+	{
+		if (missing_ok)
+			return NULL;
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing element \"%s\" in json object", keyname)));
+	}
+
+	str = pnstrdup(value->val.string.val, value->val.string.len);
+	if (length)
+		*length = value->val.string.len;
+	pfree(value);
+	return str;
+}
+
+#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)
+
+/*
+ * Recursive helper for pg_event_trigger_expand_command
+ *
+ * Find the "fmt" element in the given container, and expand it into the
+ * provided StringInfo.
+ */
+static void
+expand_fmt_recursive(JsonbContainer *container, StringInfo out)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	int			len;
+
+	start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len);
+	end_ptr = start_ptr + len;
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{
+		convSpecifier specifier;
+		bool		is_array;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		is_array = false;
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			for (; cp < end_ptr;)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing conversion name in conversion specifier")));
+
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'L':
+				specifier = SpecStringLiteral;
+				break;
+			case 'T':
+				specifier = SpecTypename;
+				break;
+			case 'O':
+				specifier = SpecOperatorname;
+				break;
+			case 'n':
+				specifier = SpecNumber;
+				break;
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid conversion specifier \"%c\"", *cp)));
+		}
+
+		/*
+		 * Obtain the element to be expanded.
+		 */
+		key.type = jbvString;
+		key.val.string.val = param;
+		key.val.string.len = strlen(param);
+
+		value = findJsonbValueFromContainer(container, JB_FOBJECT, &key);
+
+		/* Validate that we got an array if the format string specified one. */
+
+		/* And finally print out the data */
+		if (is_array)
+			expand_jsonb_array(out, param, value, arraysep, specifier, start_ptr);
+		else
+			expand_one_jsonb_element(out, param, value, specifier, start_ptr);
+	}
+}
+
+/*
+ * Expand a json value as an identifier.  The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	Assert(jsonval->type == jbvString);
+
+	str = pnstrdup(jsonval->val.string.val,
+				   jsonval->val.string.len);
+	appendStringInfoString(buf, quote_identifier(str));
+	pfree(str);
+}
+
+/*
+ * Expand a json value as a dot-separated-name.  The value must be of type
+ * object and must contain elements "schemaname" (optional), "objname"
+ * (mandatory), "attrname" (optional).  Double quotes are added to each element
+ * as necessary, and dot separators where needed.
+ *
+ * One day we might need a "catalog" element as well, but no current use case
+ * needs that.
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+		pfree(str);
+	}
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfo(buf, "%s", quote_identifier(str));
+	pfree(str);
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"attrname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, ".%s", quote_identifier(str));
+		pfree(str);
+	}
+}
+
+/*
+ * expand a json value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename;
+	char	   *typmodstr;
+	trivalue	is_array;
+	char	   *array_decor;
+
+	/*
+	 * We omit schema-qualifying the output name if the schema element is
+	 * either the empty string or NULL; the difference between those two cases
+	 * is that in the latter we quote the type name, in the former we don't.
+	 * This allows for types with special typmod needs, such as interval and
+	 * timestamp (see format_type_detailed), while at the same time allowing
+	 * for the schema name to be omitted from type names that require quotes
+	 * but are to be obtained from a user schema.
+	 */
+
+	schema = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										   "schemaname", true, NULL);
+	typename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											 "typename", false, NULL);
+	typmodstr = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											  "typmod", true, NULL);
+	is_array = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+										   "is_array");
+	switch (is_array)
+	{
+		default:
+		case tv_absent:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("missing is_array element")));
+			break;
+		case tv_true:
+			array_decor = "[]";
+			break;
+		case tv_false:
+			array_decor = "";
+			break;
+	}
+
+	if (schema == NULL)
+		appendStringInfo(buf, "%s%s%s",
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else if (schema[0] == '\0')
+		appendStringInfo(buf, "%s%s%s",
+						 typename,
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else
+		appendStringInfo(buf, "%s.%s%s%s",
+						 quote_identifier(schema),
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+}
+
+/*
+ * Expand a json value as an operator name
+ */
+static void
+expand_jsonval_operator(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	/* schema might be NULL or empty */
+	if (str != NULL && str[0] != '\0')
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfoString(buf, str);
+}
+
+/*
+ * Expand a json value as a string.  The value must be of type string or of
+ * type object.  In the latter 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, JsonbValue *jsonval)
+{
+	if (jsonval->type == jbvString)
+	{
+		appendBinaryStringInfo(buf, jsonval->val.string.val,
+							   jsonval->val.string.len);
+	}
+	else if (jsonval->type == jbvBinary)
+	{
+		trivalue	present;
+
+		present = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											  "present");
+		/*
+		 * If "present" is set to false, this element expands to empty;
+		 * otherwise (either true or absent), fall through to expand "fmt".
+		 */
+		if (present == tv_false)
+			return;
+
+		expand_fmt_recursive(jsonval->val.binary.data, buf);
+	}
+}
+
+/*
+ * Expand a json value as a string literal
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval)
+{
+	char   *str;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int         dqnextchar = 0;
+
+	str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+
+	/* easy case: if there are no ' and no \, just use a single quote */
+	if (strchr(str, '\'') == NULL &&
+		strchr(str, '\\') == NULL)
+	{
+		appendStringInfo(buf, "'%s'", str);
+		pfree(str);
+		return;
+	}
+
+	/* Otherwise need to find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(str, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar %= sizeof(dqsuffixes) - 1;
+	}
+	/* add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data);
+	pfree(dqdelim.data);
+	pfree(str);
+}
+
+/*
+ * Expand a json value as an integer quantity
+ */
+static void
+expand_jsonval_number(StringInfo buf, JsonbValue *jsonval)
+{
+	char *strdatum;
+
+	strdatum = DatumGetCString(DirectFunctionCall1(numeric_out,
+												   NumericGetDatum(jsonval->val.numeric)));
+	appendStringInfoString(buf, strdatum);
+}
+
+/*
+ * Expand one json element into the output StringInfo according to the
+ * conversion specifier.  The element type is validated, and an error is raised
+ * if it doesn't match what we expect for the conversion specifier.
+ */
+static void
+expand_one_jsonb_element(StringInfo out, char *param, JsonbValue *jsonval,
+						 convSpecifier specifier, const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (!jsonval)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" not found", param)));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%I element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_identifier(out, jsonval);
+			break;
+
+		case SpecDottedName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%D element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_dottedname(out, jsonval);
+			break;
+
+		case SpecString:
+			if (jsonval->type != jbvString &&
+				jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string or object for %%s element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_string(out, jsonval);
+			break;
+
+		case SpecStringLiteral:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%L element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_strlit(out, jsonval);
+			break;
+
+		case SpecTypename:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%T element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_typename(out, jsonval);
+			break;
+
+		case SpecOperatorname:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%O element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_operator(out, jsonval);
+			break;
+
+		case SpecNumber:
+			if (jsonval->type != jbvNumeric)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON numeric for %%n element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_number(out, jsonval);
+			break;
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*
+ * Iterate on the elements of a JSON array, expanding each one into the output
+ * StringInfo per the given conversion specifier, separated by the given
+ * separator.
+ */
+static void
+expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep, convSpecifier specifier,
+				   const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+	JsonbContainer *container;
+	JsonbIterator  *it;
+	JsonbValue	v;
+	int			type;
+	bool		first = true;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (jsonarr->type != jbvBinary)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	container = jsonarr->val.binary.data;
+	if ((container->header & JB_FARRAY) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	it = JsonbIteratorInit(container);
+	while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+	{
+		switch (type)
+		{
+			case WJB_ELEM:
+				if (!first)
+					appendStringInfoString(out, arraysep);
+				first = false;
+				expand_one_jsonb_element(out, param, &v, specifier, NULL);
+				break;
+		}
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ * n		expand as a simple number (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.
+ *------
+ */
+Datum
+pg_event_trigger_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	Datum		d;
+	Jsonb	   *jsonb;
+	StringInfoData out;
+
+	initStringInfo(&out);
+	d = DirectFunctionCall1(jsonb_in,
+							PointerGetDatum(TextDatumGetCString(json)));
+	jsonb = (Jsonb *) DatumGetPointer(d);
+
+	expand_fmt_recursive(&jsonb->root, &out);
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(out.data));
+}
+
+/*
+ * Error context callback for JSON format string expansion.
+ *
+ * Possible improvement: indicate which element we're expanding, if applicable
+ */
+static void
+fmtstr_error_callback(void *arg)
+{
+	errcontext("while expanding format string \"%s\"", (char *) arg);
+
+}
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index fc816ce..dbf6b60 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
 	return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
@@ -323,6 +326,106 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
+/*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - is_array indicates whether []s must be added
+ *
+ * Also, we don't try to decode type names to their standard-mandated names,
+ * except in the cases of unusual typmod rules, as specified above.
+ *
+ * XXX there is a lot of code duplication between this routine and
+ * format_type_internal.  (One thing that doesn't quite match is the whole
+ * allow_invalid business.)
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *is_array)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID)
+	{
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPOID:
+			case TIMESTAMPTZOID:
+				/* the TZ part is added by typmod */
+				*typname = pstrdup("TIMESTAMP");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		*is_array = false;
+
+		ReleaseSysCache(tuple);
+		return;
+	}
+
+	/*
+	 * Check if it's a regular (variable length) array type.  As above,
+	 * fixed-length array types such as "name" shouldn't get deconstructed.
+	 */
+	array_base_type = typeform->typelem;
+
+	if (array_base_type != InvalidOid &&
+		typeform->typstorage != 'p')
+	{
+		/* Switch our attention to the array element type */
+		ReleaseSysCache(tuple);
+		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+		typeform = (Form_pg_type) GETSTRUCT(tuple);
+		type_oid = array_base_type;
+		*is_array = true;
+	}
+	else
+		*is_array = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
 
 /*
  * Add typmod decoration to the basic type name
@@ -338,7 +441,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,7 +453,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 6fbe283..a842ef2 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -416,7 +416,7 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 {
 	bool		first = true;
 	JsonbIterator *it;
-	int			type = 0;
+	JsonbIteratorToken type = WJB_DONE;
 	JsonbValue	v;
 	int			level = 0;
 	bool		redo_switch = false;
@@ -498,7 +498,7 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 				first = false;
 				break;
 			default:
-				elog(ERROR, "unknown flag of jsonb iterator");
+				elog(ERROR, "unknown jsonb iterator token type");
 		}
 	}
 
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6f4dbab..cf0e9c4 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 9edfdb8..4efcde0 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5082,12 +5082,15 @@ DESCR("peek at binary changes from replication slot");
 
 /* event triggers */
 DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,16,16,25,25,25,25,1009,1009}" "{o,o,o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, original, normal, object_type, schema_name, object_name, object_identity, address_names, address_args}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
-
 DESCR("list objects dropped by the current command");
 DATA(insert OID = 4566 (  pg_event_trigger_table_rewrite_oid	PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 26 "" "{26}" "{o}" "{oid}" _null_ pg_event_trigger_table_rewrite_oid _null_ _null_ _null_ ));
 DESCR("return Oid of the table getting rewritten");
 DATA(insert OID = 4567 (  pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ ));
 DESCR("return reason code for table getting rewritten");
+DATA(insert OID = 3590 (  pg_event_trigger_get_creation_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,114}" "{o,o,o,o,o,o,o,o,o}" "{classid,objid,objsubid,command_tag,object_type,schema,identity,in_extension,command}" _null_ pg_event_trigger_get_creation_commands _null_ _null_ _null_ ));
+DESCR("list JSON-formatted commands executed by the current command");
+DATA(insert OID = 3591 (  pg_event_trigger_expand_command PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 25 "114" _null_ _null_ _null_ _null_ pg_event_trigger_expand_command _null_ _null_ _null_ ));
+DESCR("format JSON command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 9ac9fc3..c07f04c 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -60,4 +60,7 @@ extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 							 bool original, bool normal);
 
+extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
+						 ObjectType objtype, Oid secondaryOid, Node *parsetree);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index a349d6a..4259072 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -23,7 +23,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b1dfa85..086bc82 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1209,6 +1209,7 @@ typedef enum ObjectType
 	OBJECT_CAST,
 	OBJECT_COLUMN,
 	OBJECT_COLLATION,
+	OBJECT_COMPOSITE,
 	OBJECT_CONVERSION,
 	OBJECT_DATABASE,
 	OBJECT_DEFAULT,
@@ -1241,6 +1242,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..bcc8611
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,61 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+#include "access/attnum.h"
+#include "nodes/nodes.h"
+
+/*
+ * Support for keeping track of a command to deparse.
+ *
+ * When a command is run, we collect some information about it for later
+ * deparsing; deparse_utility_command can later be used to obtain a usable
+ * representation of it.
+ */
+
+typedef enum StashedCommandType
+{
+	SCT_Simple,
+} StashedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct StashedATSubcmd
+{
+	AttrNumber		attnum;	/* affected column number */
+	Oid				oid;	/* affected constraint, default value or index */
+	Node		   *parsetree;
+} StashedATSubcmd;
+
+typedef struct StashedCommand
+{
+	StashedCommandType type;
+	bool		in_extension;
+	Node	   *parsetree;
+
+	union
+	{
+		struct SimpleCommand
+		{
+			Oid			objectId;
+			uint32		objectSubId;
+			ObjectType	objtype;
+			Oid			secondaryOid;
+		} simple;
+	} d;
+} StashedCommand;
+
+extern char *deparse_utility_command(StashedCommand *cmd);
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index bc4517d..470a325 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1079,6 +1079,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);
@@ -1210,6 +1213,10 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS);
+
+/* utils/adt/ddl_json.c */
+extern Datum pg_event_trigger_expand_command(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
-- 
2.1.4

0008-deparse-add-EventTriggerStashCommand-calls-to-Proces.patchtext/x-diff; charset=us-asciiDownload
>From 70b4174d3e365829ea53a7798b206fe4a33d841e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:50:37 -0300
Subject: [PATCH 08/42] deparse: add EventTriggerStashCommand() calls to
 ProcessUtilitySlow

These calls add each executed command to the event trigger command
stash.
---
 src/backend/commands/schemacmds.c |   9 ++
 src/backend/tcop/utility.c        | 260 ++++++++++++++++++++++++++------------
 2 files changed, 186 insertions(+), 83 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index a44dbf4..4238034 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, 0, OBJECT_SCHEMA, InvalidOid, (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/tcop/utility.c b/src/backend/tcop/utility.c
index ee6799a..2e21659 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -887,6 +887,8 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	Oid			objectId;
+	Oid			secondaryOid;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -905,6 +907,10 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * CreateSchemaCommand calls EventTriggerStashCommand
+				 * internally, for reasons explained there.
+				 */
 				break;
 
 			case T_CreateStmt:
@@ -912,7 +918,6 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					List	   *stmts;
 					ListCell   *l;
-					Oid			relOid;
 
 					/* Run parse analysis ... */
 					stmts = transformCreateStmt((CreateStmt *) parsetree,
@@ -929,9 +934,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, 0, OBJECT_TABLE,
+													 InvalidOid, stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -953,20 +960,27 @@ ProcessUtilitySlow(Node *parsetree,
 												   toast_options,
 												   true);
 
-							NewRelationCreateToastTable(relOid, toast_options);
+							NewRelationCreateToastTable(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, 0,
+													 OBJECT_FOREIGN_TABLE,
+													 InvalidOid, 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,
@@ -1055,36 +1069,44 @@ ProcessUtilitySlow(Node *parsetree,
 							 * Recursively alter column default for table and,
 							 * if requested, for descendants
 							 */
-							AlterDomainDefault(stmt->typeName,
-											   stmt->def);
+							objectId =
+								AlterDomainDefault(stmt->typeName,
+												   stmt->def);
 							break;
 						case 'N':		/* ALTER DOMAIN DROP NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   false);
+							objectId =
+								AlterDomainNotNull(stmt->typeName,
+												   false);
 							break;
 						case 'O':		/* ALTER DOMAIN SET NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   true);
+							objectId =
+								AlterDomainNotNull(stmt->typeName,
+												   true);
 							break;
 						case 'C':		/* ADD CONSTRAINT */
-							AlterDomainAddConstraint(stmt->typeName,
-													 stmt->def);
+							objectId =
+								AlterDomainAddConstraint(stmt->typeName,
+														 stmt->def);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
-							AlterDomainDropConstraint(stmt->typeName,
-													  stmt->name,
-													  stmt->behavior,
-													  stmt->missing_ok);
+							objectId =
+								AlterDomainDropConstraint(stmt->typeName,
+														  stmt->name,
+														  stmt->behavior,
+														  stmt->missing_ok);
 							break;
 						case 'V':		/* VALIDATE CONSTRAINT */
-							AlterDomainValidateConstraint(stmt->typeName,
-														  stmt->name);
+							objectId =
+								AlterDomainValidateConstraint(stmt->typeName,
+															  stmt->name);
 							break;
 						default:		/* oops */
 							elog(ERROR, "unrecognized alter domain type: %d",
 								 (int) stmt->subtype);
 							break;
 					}
+					EventTriggerStashCommand(objectId, 0, OBJECT_DOMAIN,
+											 secondaryOid, parsetree);
 				}
 				break;
 
@@ -1098,46 +1120,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, 0, stmt->kind, InvalidOid, parsetree);
 				}
 				break;
 
@@ -1172,50 +1201,66 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
-					DefineIndex(relid,	/* OID of heap relation */
-								stmt,
-								InvalidOid,		/* no predefined OID */
-								false,	/* is_alter_table */
-								true,	/* check_rights */
-								false,	/* skip_build */
-								false); /* quiet */
+					objectId =
+						DefineIndex(relid,	/* OID of heap relation */
+									stmt,
+									InvalidOid,		/* no predefined OID */
+									false,	/* is_alter_table */
+									true,	/* check_rights */
+									false,	/* skip_build */
+									false); /* quiet */
+					EventTriggerStashCommand(objectId, 0, OBJECT_INDEX,
+											 InvalidOid, parsetree);
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				objectId = CreateExtension((CreateExtensionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, InvalidOid, parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				objectId = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, InvalidOid, parsetree);
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
+				objectId = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, InvalidOid, parsetree);
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FDW, InvalidOid, parsetree);
 				break;
 
 			case T_AlterFdwStmt:
-				AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				objectId = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FDW, InvalidOid, parsetree);
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FOREIGN_SERVER,
+										 InvalidOid, parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
-				AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				objectId = AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FOREIGN_SERVER,
+										 InvalidOid, parsetree);
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_USER_MAPPING,
+										 InvalidOid, parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
-				AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				objectId = AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_USER_MAPPING,
+										 InvalidOid, parsetree);
 				break;
 
 			case T_DropUserMappingStmt:
@@ -1224,105 +1269,131 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_ImportForeignSchemaStmt:
 				ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+				/* commands are stashed by recursing */
 				break;
 
 			case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					objectId = DefineCompositeType(stmt->typevar,
+												   stmt->coldeflist);
+					EventTriggerStashCommand(objectId, 0, OBJECT_COMPOSITE,
+											 InvalidOid, parsetree);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				objectId = DefineEnum((CreateEnumStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, InvalidOid, parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				objectId = DefineRange((CreateRangeStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, InvalidOid, parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				objectId = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, InvalidOid, parsetree);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				objectId = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_VIEW, InvalidOid, parsetree);
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, InvalidOid, parsetree);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				objectId = AlterFunction((AlterFunctionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, InvalidOid, parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				objectId = DefineRule((RuleStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_RULE, InvalidOid, parsetree);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				objectId = DefineSequence((CreateSeqStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, InvalidOid, parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				objectId = AlterSequence((AlterSeqStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, InvalidOid, parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TABLE, InvalidOid, parsetree);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+				objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
 								   queryString, params, completionTag);
+				EventTriggerStashCommand(objectId, 0, OBJECT_MATVIEW, InvalidOid, parsetree);
 				break;
 
 			case T_CreateTrigStmt:
-				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
-									 InvalidOid, InvalidOid, InvalidOid,
-									 InvalidOid, false);
+				objectId = CreateTrigger((CreateTrigStmt *) parsetree,
+										 queryString, InvalidOid, InvalidOid,
+										 InvalidOid, InvalidOid, false);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TRIGGER, InvalidOid, parsetree);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_LANGUAGE, InvalidOid, parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				objectId = DefineDomain((CreateDomainStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_DOMAIN, InvalidOid, parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				objectId = CreateConversionCommand((CreateConversionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_CONVERSION, InvalidOid, parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				objectId = CreateCast((CreateCastStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_CAST, InvalidOid, parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				objectId = DefineOpClass((CreateOpClassStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPCLASS, InvalidOid, parsetree);
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPFAMILY, InvalidOid, parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
-				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				objectId = AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPFAMILY, InvalidOid, parsetree);
 				break;
 
 			case T_AlterTSDictionaryStmt:
-				AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				objectId = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TSDICTIONARY, InvalidOid, parsetree);
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				objectId = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TSCONFIGURATION, InvalidOid, parsetree);
 				break;
 
 			case T_AlterTableMoveAllStmt:
+				/* commands are stashed in AlterTableMoveAll */
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
 				break;
 
@@ -1331,23 +1402,44 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree, NULL);
+				{
+					RenameStmt *stmt = (RenameStmt *) parsetree;
+					int			objsubid = 0;
+
+					objectId = ExecRenameStmt(stmt, &objsubid);
+					EventTriggerStashCommand(objectId, objsubid,
+											 stmt->renameType, InvalidOid, parsetree);
+				}
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
+				objectId = ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0,
+										 ((AlterObjectSchemaStmt *) parsetree)->objectType,
+										 parsetree);
 				break;
 
 			case T_AlterOwnerStmt:
-				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				objectId = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0,
+										 ((AlterOwnerStmt *) parsetree)->objectType,
+										 InvalidOid, parsetree);
 				break;
 
 			case T_CommentStmt:
-				CommentObject((CommentStmt *) parsetree, NULL);
+				{
+					uint32		objsubid;
+
+					objectId = CommentObject((CommentStmt *) parsetree, &objsubid);
+					EventTriggerStashCommand(objectId, objsubid,
+											 ((CommentStmt *) parsetree)->objtype,
+											 InvalidOid, parsetree);
+				}
 				break;
 
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
+				/* commands are stashed in ExecGrantStmt_oids */
 				break;
 
 			case T_DropOwnedStmt:
@@ -1359,11 +1451,13 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
-				CreatePolicy((CreatePolicyStmt *) parsetree);
+				objectId = CreatePolicy((CreatePolicyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_POLICY, InvalidOid, parsetree);
 				break;
 
 			case T_AlterPolicyStmt:		/* ALTER POLICY */
-				AlterPolicy((AlterPolicyStmt *) parsetree);
+				objectId = AlterPolicy((AlterPolicyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_POLICY, InvalidOid, parsetree);
 				break;
 
 			case T_SecLabelStmt:
-- 
2.1.4

0009-deparse-Support-CREATE-TYPE-AS.patchtext/x-diff; charset=us-asciiDownload
>From dcb353c8c9bd93778a62719ff8bf32b9a419e16d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:54:00 -0300
Subject: [PATCH 09/42] deparse: Support CREATE TYPE AS

---
 src/backend/tcop/deparse_utility.c | 264 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  18 +++
 src/include/utils/ruleutils.h      |   3 +
 3 files changed, 284 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 8463321..becff4a 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -639,6 +639,268 @@ new_objtree_for_qualname_id(Oid classId, Oid objectId)
 }
 
 /*
+ * 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(Relation relation, List *dpcontext, bool composite,
+				  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);
+
+	/* Composite types use a slightly simpler format string */
+	if (composite)
+		column = new_objtree_VA("%{name}I %{coltype}T %{collation}s",
+								3,
+								"type", ObjTypeString, "column",
+								"name", ObjTypeString, coldef->colname,
+								"coltype", ObjTypeObject,
+								new_objtree_for_type(typid, typmod));
+	else
+		column = new_objtree_VA("%{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(typid, typmod));
+
+	tmp = new_objtree_VA("COLLATE %{name}D", 0);
+	if (OidIsValid(typcollation))
+	{
+		ObjTree *collname;
+
+		collname = new_objtree_for_qualname_id(CollationRelationId,
+											   typcollation);
+		append_object_object(tmp, "name", collname);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(column, "collation", tmp);
+
+	if (!composite)
+	{
+		/*
+		 * 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.  Therefore,
+		 * we scan the list of constraints attached to this column to determine
+		 * whether we need to emit anything.
+		 * (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("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(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("%{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(Relation relation, List *tableElements, List *dpcontext,
+					 bool typed, bool composite)
+{
+	List	   *elements = NIL;
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					ObjTree	   *tree;
+
+					tree = typed ?
+						deparse_ColumnDef_typed(relation, dpcontext,
+												(ColumnDef *) elt) :
+						deparse_ColumnDef(relation, dpcontext,
+										  composite, (ColumnDef *) elt);
+					if (tree != NULL)
+					{
+						ObjElem    *column;
+
+						column = new_object_object(tree);
+						elements = lappend(elements, column);
+					}
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+
+	return elements;
+}
+
+/*
+ * deparse_CompositeTypeStmt
+ *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
+{
+	CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree;
+	ObjTree	   *composite;
+	Relation	typerel = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	List	   *tableelts = NIL;
+
+	dpcontext = deparse_context_for(RelationGetRelationName(typerel),
+									objectId);
+
+	composite = new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)",
+							   0);
+	append_object_object(composite, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+
+	tableelts = deparseTableElements(typerel, node->coldeflist, dpcontext,
+									 false,		/* not typed */
+									 true);		/* composite type */
+
+	append_array_object(composite, "columns", tableelts);
+
+	heap_close(typerel, AccessShareLock);
+
+	return composite;
+}
+
+/*
  * Handle deparsing of simple commands.
  *
  * This function contains a large switch that mirrors that in
@@ -737,7 +999,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
-			command = NULL;
+			command = deparse_CompositeTypeStmt(objectId, parsetree);
 			break;
 
 		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 30cc3c7..bbbe3ac 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9352,3 +9352,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/utils/ruleutils.h b/src/include/utils/ruleutils.h
index fed9c7b..1673e3e 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -32,4 +32,7 @@ extern List *select_rtable_names_for_explain(List *rtable,
 								Bitmapset *rels_used);
 extern char *generate_collation_name(Oid collid);
 
+extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
+						 List *dpcontext);
+
 #endif	/* RULEUTILS_H */
-- 
2.1.4

0010-deparse-Support-CREATE-TYPE-AS-ENUM.patchtext/x-diff; charset=us-asciiDownload
>From 7943924f34c394e1e86062be87737905be158cd2 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:55:43 -0300
Subject: [PATCH 10/42] deparse: Support CREATE TYPE AS ENUM

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index becff4a..3bcb260 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -901,6 +901,38 @@ deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
 }
 
 /*
+ * deparse_CreateEnumStmt
+ *		Deparse a CreateEnumStmt (CREATE TYPE AS ENUM)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
+{
+	CreateEnumStmt *node = (CreateEnumStmt *) parsetree;
+	ObjTree	   *enumtype;
+	List	   *values;
+	ListCell   *cell;
+
+	enumtype = new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)",
+							  0);
+	append_object_object(enumtype, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	values = NIL;
+	foreach(cell, node->vals)
+	{
+		Value   *val = (Value *) lfirst(cell);
+
+		values = lappend(values, new_string_object(strVal(val)));
+	}
+	append_array_object(enumtype, "values", values);
+
+	return enumtype;
+}
+
+/*
  * Handle deparsing of simple commands.
  *
  * This function contains a large switch that mirrors that in
@@ -1003,7 +1035,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
-			command = NULL;
+			command = deparse_CreateEnumStmt(objectId, parsetree);
 			break;
 
 		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
-- 
2.1.4

0011-deparse-Support-CREATE-SCHEMA-TABLE-SEQUENCE-INDEX-T.patchtext/x-diff; charset=us-asciiDownload
>From 5d576ad6582d4980bbd78ae4e4e61f7d1d91926d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:57:48 -0300
Subject: [PATCH 11/42] deparse: Support CREATE
 SCHEMA/TABLE/SEQUENCE/INDEX/TRIGGER

---
 src/backend/commands/sequence.c    |  34 ++
 src/backend/commands/tablecmds.c   |   3 +-
 src/backend/tcop/deparse_utility.c | 956 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  | 355 ++++++++++++--
 src/include/commands/sequence.h    |   1 +
 src/include/utils/ruleutils.h      |  11 +-
 6 files changed, 1300 insertions(+), 60 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 622ccf7..acfa370 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,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/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4b7b032..31fd7ba 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8037,7 +8037,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/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 3bcb260..59ef0a1 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -639,6 +639,223 @@ new_objtree_for_qualname_id(Oid classId, Oid objectId)
 }
 
 /*
+ * 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_CreateTrigStmt
+ *		Deparse a CreateTrigStmt (CREATE TRIGGER)
+ *
+ * Given a trigger OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
+{
+	CreateTrigStmt *node = (CreateTrigStmt *) parsetree;
+	Relation	pg_trigger;
+	HeapTuple	trigTup;
+	Form_pg_trigger trigForm;
+	ObjTree	   *trigger;
+	ObjTree	   *tmp;
+	int			tgnargs;
+	List	   *list;
+	List	   *events;
+
+	pg_trigger = heap_open(TriggerRelationId, AccessShareLock);
+
+	trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+	trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+	/*
+	 * Some of the elements only make sense for CONSTRAINT TRIGGERs, but it
+	 * seems simpler to use a single fmt string for both kinds of triggers.
+	 */
+	trigger =
+		new_objtree_VA("CREATE %{constraint}s TRIGGER %{name}I %{time}s %{events: OR }s "
+					   "ON %{relation}D %{from_table}s %{constraint_attrs: }s "
+					   "FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE %{function}s",
+					   2,
+					   "name", ObjTypeString, node->trigname,
+					   "constraint", ObjTypeString,
+					   node->isconstraint ? "CONSTRAINT" : "");
+
+	if (node->timing == TRIGGER_TYPE_BEFORE)
+		append_string_object(trigger, "time", "BEFORE");
+	else if (node->timing == TRIGGER_TYPE_AFTER)
+		append_string_object(trigger, "time", "AFTER");
+	else if (node->timing == TRIGGER_TYPE_INSTEAD)
+		append_string_object(trigger, "time", "INSTEAD OF");
+	else
+		elog(ERROR, "unrecognized trigger timing value %d", node->timing);
+
+	/*
+	 * Decode the events that the trigger fires for.  The output is a list;
+	 * in most cases it will just be a string with the even name, but when
+	 * there's an UPDATE with a list of columns, we return a JSON object.
+	 */
+	events = NIL;
+	if (node->events & TRIGGER_TYPE_INSERT)
+		events = lappend(events, new_string_object("INSERT"));
+	if (node->events & TRIGGER_TYPE_DELETE)
+		events = lappend(events, new_string_object("DELETE"));
+	if (node->events & TRIGGER_TYPE_TRUNCATE)
+		events = lappend(events, new_string_object("TRUNCATE"));
+	if (node->events & TRIGGER_TYPE_UPDATE)
+	{
+		if (node->columns == NIL)
+		{
+			events = lappend(events, new_string_object("UPDATE"));
+		}
+		else
+		{
+			ObjTree	   *update;
+			ListCell   *cell;
+			List	   *cols = NIL;
+
+			/*
+			 * Currently only UPDATE OF can be objects in the output JSON, but
+			 * we add a "kind" element so that user code can distinguish
+			 * possible future new event types.
+			 */
+			update = new_objtree_VA("UPDATE OF %{columns:, }I",
+									1, "kind", ObjTypeString, "update_of");
+
+			foreach(cell, node->columns)
+			{
+				char   *colname = strVal(lfirst(cell));
+
+				cols = lappend(cols,
+							   new_string_object(colname));
+			}
+
+			append_array_object(update, "columns", cols);
+
+			events = lappend(events,
+							 new_object_object(update));
+		}
+	}
+	append_array_object(trigger, "events", events);
+
+	tmp = new_objtree_for_qualname_id(RelationRelationId,
+									  trigForm->tgrelid);
+	append_object_object(trigger, "relation", tmp);
+
+	tmp = new_objtree_VA("FROM %{relation}D", 0);
+	if (trigForm->tgconstrrelid)
+	{
+		ObjTree	   *rel;
+
+		rel = new_objtree_for_qualname_id(RelationRelationId,
+										  trigForm->tgconstrrelid);
+		append_object_object(tmp, "relation", rel);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "from_table", tmp);
+
+	list = NIL;
+	if (node->deferrable)
+		list = lappend(list,
+					   new_string_object("DEFERRABLE"));
+	if (node->initdeferred)
+		list = lappend(list,
+					   new_string_object("INITIALLY DEFERRED"));
+	append_array_object(trigger, "constraint_attrs", list);
+
+	append_string_object(trigger, "for_each",
+						 node->row ? "ROW" : "STATEMENT");
+
+	tmp = new_objtree_VA("WHEN (%{clause}s)", 0);
+	if (node->whenClause)
+	{
+		Node	   *whenClause;
+		Datum		value;
+		bool		isnull;
+
+		value = fastgetattr(trigTup, Anum_pg_trigger_tgqual,
+							RelationGetDescr(pg_trigger), &isnull);
+		if (isnull)
+			elog(ERROR, "bogus NULL tgqual");
+
+		whenClause = stringToNode(TextDatumGetCString(value));
+		append_string_object(tmp, "clause",
+							 pg_get_trigger_whenclause(trigForm,
+													   whenClause,
+													   false));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "when", tmp);
+
+	tmp = new_objtree_VA("%{funcname}D(%{args:, }L)",
+						 1, "funcname", ObjTypeObject,
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 trigForm->tgfoid));
+	list = NIL;
+	tgnargs = trigForm->tgnargs;
+	if (tgnargs > 0)
+	{
+		bytea  *tgargs;
+		char   *argstr;
+		bool	isnull;
+		int		findx;
+		int		lentgargs;
+		char   *p;
+
+		tgargs = DatumGetByteaP(fastgetattr(trigTup,
+											Anum_pg_trigger_tgargs,
+											RelationGetDescr(pg_trigger),
+											&isnull));
+		if (isnull)
+			elog(ERROR, "invalid NULL tgargs");
+		argstr = (char *) VARDATA(tgargs);
+		lentgargs = VARSIZE_ANY_EXHDR(tgargs);
+
+		p = argstr;
+		for (findx = 0; findx < tgnargs; findx++)
+		{
+			size_t	tlen;
+
+			/* verify that the argument encoding is correct */
+			tlen = strlen(p);
+			if (p + tlen >= argstr + lentgargs)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid argument string (%s) for trigger \"%s\"",
+								argstr, NameStr(trigForm->tgname))));
+
+			list = lappend(list, new_string_object(p));
+
+			p += tlen + 1;
+		}
+	}
+
+	append_array_object(tmp, "args", list);		/* might be NIL */
+
+	append_object_object(trigger, "function", tmp);
+
+	heap_close(pg_trigger, AccessShareLock);
+
+	return trigger;
+}
+
+/*
  * deparse_ColumnDef
  *		Subroutine for CREATE TABLE deparsing
  *
@@ -865,6 +1082,334 @@ deparseTableElements(Relation relation, List *tableElements, List *dpcontext,
 }
 
 /*
+ * obtainConstraints
+ *		Subroutine for CREATE TABLE/CREATE DOMAIN deparsing
+ *
+ * Given a table OID or domain OID, obtain its constraints and append them to
+ * the given elements list.  The updated list is returned.
+ *
+ * This works for typed tables, regular tables, and domains.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static List *
+obtainConstraints(List *elements, Oid relationId, Oid domainId)
+{
+	Relation	conRel;
+	ScanKeyData key;
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	ObjTree    *tmp;
+
+	/* only one may be valid */
+	Assert(OidIsValid(relationId) ^ OidIsValid(domainId));
+
+	/*
+	 * scan pg_constraint to fetch all constraints linked to the given
+	 * relation.
+	 */
+	conRel = heap_open(ConstraintRelationId, AccessShareLock);
+	if (OidIsValid(relationId))
+	{
+		ScanKeyInit(&key,
+					Anum_pg_constraint_conrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relationId));
+		scan = systable_beginscan(conRel, ConstraintRelidIndexId,
+								  true, NULL, 1, &key);
+	}
+	else
+	{
+		Assert(OidIsValid(domainId));
+		ScanKeyInit(&key,
+					Anum_pg_constraint_contypid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(domainId));
+		scan = systable_beginscan(conRel, ConstraintTypidIndexId,
+								  true, NULL, 1, &key);
+	}
+
+	/*
+	 * 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:
+				continue;	/* not here */
+			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("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, new_object_object(tmp));
+	}
+
+	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 an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *list;
+	ListCell   *cell;
+	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 %{if_not_exists}s %{identity}D "
+			"OF %{of_type}T %{table_elements}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) %{inherits}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+
+	createStmt =
+		new_objtree_VA(fmtstr, 1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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(relation->rd_rel->reloftype, -1);
+		append_object_object(createStmt, "of_type", tmp);
+
+		tableelts = deparseTableElements(relation, node->tableElts, dpcontext,
+										 true,		/* typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+		if (tableelts == NIL)
+			tmp = new_objtree_VA("", 1,
+								 "present", ObjTypeBool, false);
+		else
+			tmp = new_objtree_VA("(%{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(relation, node->tableElts, dpcontext,
+										 false,		/* not typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+		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("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(RelationRelationId,
+													 formInh->inhparent);
+				parents = lappend(parents, new_object_object(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("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("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);
+
+	/*
+	 * WITH clause.  We always emit one, containing at least the OIDS option.
+	 * That way we don't depend on the default value for default_with_oids.
+	 * We can skip emitting other options if there don't appear in the parse
+	 * node.
+	 */
+	tmp = new_objtree_VA("oids=%{value}s", 2,
+						 "option", ObjTypeString, "oids",
+						 "value", ObjTypeString,
+						 relation->rd_rel->relhasoids ? "ON" : "OFF");
+	list = list_make1(new_object_object(tmp));
+	foreach(cell, node->options)
+	{
+		DefElem	*opt = (DefElem *) lfirst(cell);
+		char   *defname;
+		char   *value;
+
+		/* already handled above */
+		if (strcmp(opt->defname, "oids") == 0)
+			continue;
+
+		if (opt->defnamespace)
+			defname = psprintf("%s.%s", opt->defnamespace, opt->defname);
+		else
+			defname = opt->defname;
+
+		value = opt->arg ? defGetString(opt) :
+			defGetBoolean(opt) ? "TRUE" : "FALSE";
+		tmp = new_objtree_VA("%{option}s=%{value}s", 2,
+							 "option", ObjTypeString, defname,
+							 "value", ObjTypeString, value);
+		list = lappend(list, new_object_object(tmp));
+	}
+	append_array_object(createStmt, "with", list);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
+/*
  * deparse_CompositeTypeStmt
  *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
  *
@@ -932,6 +1477,405 @@ deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
 	return enumtype;
 }
 
+static inline ObjElem *
+deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->cache_value);
+	tmp = new_objtree_VA("CACHE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "cache",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Cycle(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA("%{no}s CYCLE",
+						 2,
+						 "clause", ObjTypeString, "cycle",
+						 "no", ObjTypeString,
+						 seqdata->is_cycled ? "" : "NO");
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_IncrementBy(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->increment_by);
+	tmp = new_objtree_VA("INCREMENT BY %{value}s",
+						 2,
+						 "clause", ObjTypeString, "increment_by",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Minvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->min_value);
+	tmp = new_objtree_VA("MINVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "minvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Maxvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->max_value);
+	tmp = new_objtree_VA("MAXVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "maxvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Startwith(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->start_value);
+	tmp = new_objtree_VA("START WITH %{value}s",
+						 2,
+						 "clause", ObjTypeString, "start",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Restart(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->last_value);
+	tmp = new_objtree_VA("RESTART %{value}s",
+						 2,
+						 "clause", ObjTypeString, "restart",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static ObjElem *
+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(RelationRelationId, ownerId);
+		append_string_object(tmp, "attrname", colname);
+		ownedby = new_objtree_VA("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("OWNED BY %{owner}D",
+								 3,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeNull,
+								 "present", ObjTypeBool, false);
+	return new_object_object(ownedby);
+}
+
+/*
+ * deparse_CreateSeqStmt
+ *		deparse a CreateSeqStmt
+ *
+ * Given a sequence OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *createSeq;
+	ObjTree    *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+
+	seqdata = get_sequence_values(objectId);
+
+	createSeq =
+		new_objtree_VA("CREATE %{persistence}s SEQUENCE %{identity}D "
+					   "%{definition: }s",
+					   1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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);
+
+	relation_close(relation, AccessShareLock);
+
+	return createSeq;
+}
+
+/*
+ * deparse_AlterSeqStmt
+ *		deparse an AlterSeqStmt
+ *
+ * Given a sequence OID and a parsetree that modified it, return an ObjTree
+ * representing the alter command.
+ */
+static ObjTree *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *alterSeq;
+	ObjTree	   *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+	ListCell   *cell;
+
+	seqdata = get_sequence_values(objectId);
+
+	alterSeq =
+		new_objtree_VA("ALTER SEQUENCE %{identity}D %{definition: }s", 0);
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(alterSeq, "identity", tmp);
+
+	foreach(cell, ((AlterSeqStmt *) parsetree)->options)
+	{
+		DefElem *elem = (DefElem *) lfirst(cell);
+		ObjElem *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") == 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);
+
+	relation_close(relation, AccessShareLock);
+
+	return alterSeq;
+}
+
+/*
+ * deparse_IndexStmt
+ *		deparse an IndexStmt
+ *
+ * Given an index OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ */
+static ObjTree *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+	IndexStmt  *node = (IndexStmt *) parsetree;
+	ObjTree    *indexStmt;
+	ObjTree    *tmp;
+	Relation	idxrel;
+	Relation	heaprel;
+	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);
+
+	pg_get_indexdef_detailed(objectId,
+							 &index_am, &definition, &reloptions,
+							 &tablespace, &whereClause);
+
+	indexStmt =
+		new_objtree_VA("CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   6,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "if_not_exists", ObjTypeString, node->if_not_exists ? "IF NOT EXISTS" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(heaprel->rd_rel->relnamespace,
+								   RelationGetRelationName(heaprel));
+	append_object_object(indexStmt, "table", tmp);
+
+	/* reloptions */
+	tmp = new_objtree_VA("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("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("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);
+
+	heap_close(idxrel, AccessShareLock);
+	heap_close(heaprel, AccessShareLock);
+
+	return indexStmt;
+}
+
+/*
+ * deparse_CreateSchemaStmt
+ *		deparse a CreateSchemaStmt
+ *
+ * Given a schema OID and the parsetree that created it, return an ObjTree
+ * 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 ObjTree *
+deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
+{
+	CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
+	ObjTree    *createSchema;
+	ObjTree    *auth;
+
+	createSchema =
+		new_objtree_VA("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("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);
+
+	return createSchema;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -954,11 +1898,11 @@ deparse_simple_command(StashedCommand *cmd)
 	switch (nodeTag(parsetree))
 	{
 		case T_CreateSchemaStmt:
-			command = NULL;
+			command = deparse_CreateSchemaStmt(objectId, parsetree);
 			break;
 
 		case T_CreateStmt:
-			command = NULL;
+			command = deparse_CreateStmt(objectId, parsetree);
 			break;
 
 		case T_CreateForeignTableStmt:
@@ -981,7 +1925,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_IndexStmt:
-			command = NULL;
+			command = deparse_IndexStmt(objectId, parsetree);
 			break;
 
 		case T_CreateExtensionStmt:
@@ -1063,11 +2007,11 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateSeqStmt:
-			command = NULL;
+			command = deparse_CreateSeqStmt(objectId, parsetree);
 			break;
 
 		case T_AlterSeqStmt:
-			command = NULL;
+			command = deparse_AlterSeqStmt(objectId, parsetree);
 			break;
 
 		case T_CreateTableAsStmt:
@@ -1080,7 +2024,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateTrigStmt:
-			command = NULL;
+			command = deparse_CreateTrigStmt(objectId, parsetree);
 			break;
 
 		case T_CreatePLangStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index bbbe3ac..541e076 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -821,59 +821,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char	   *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.ctes = NIL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
@@ -914,6 +867,63 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char		relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+	initStringInfo(&buf);
+
+	relkind = get_rel_relkind(trigrec->tgrelid);
+
+	/* Build minimal OLD and NEW RTEs for the rel */
+	oldrte = makeNode(RangeTblEntry);
+	oldrte->rtekind = RTE_RELATION;
+	oldrte->relid = trigrec->tgrelid;
+	oldrte->relkind = relkind;
+	oldrte->alias = makeAlias("old", NIL);
+	oldrte->eref = oldrte->alias;
+	oldrte->lateral = false;
+	oldrte->inh = false;
+	oldrte->inFromCl = true;
+
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_RELATION;
+	newrte->relid = trigrec->tgrelid;
+	newrte->relkind = relkind;
+	newrte->alias = makeAlias("new", NIL);
+	newrte->eref = newrte->alias;
+	newrte->lateral = false;
+	newrte->inh = false;
+	newrte->inFromCl = true;
+
+	/* Build two-element rtable */
+	memset(&dpns, 0, sizeof(dpns));
+	dpns.rtable = list_make2(oldrte, newrte);
+	dpns.ctes = NIL;
+	set_rtable_names(&dpns, NIL, NULL);
+	set_simple_column_names(&dpns);
+
+	/* Set up context with one-deep namespace stack */
+	context.buf = &buf;
+	context.namespaces = list_make1(&dpns);
+	context.windowClause = NIL;
+	context.windowTList = NIL;
+	context.varprefix = true;
+	context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+	context.wrapColumn = WRAP_COLUMN_DEFAULT;
+	context.indentLevel = PRETTYINDENT_STD;
+
+	get_rule_expr(whenClause, &context, false);
+
+	return buf.data;
+}
+
 /* ----------
  * get_indexdef			- Get the definition of an index
  *
@@ -977,6 +987,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,
@@ -1256,6 +1268,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
@@ -1290,9 +1541,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);
 }
 
 /*
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 1baf43d..1896036 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -71,6 +71,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/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 1673e3e..4447af9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,6 +13,7 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
@@ -20,8 +21,16 @@
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+						  Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 
-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);
-- 
2.1.4

0012-deparse-Support-CREATE-TYPE-AS-RANGE.patchtext/x-diff; charset=us-asciiDownload
>From 4e35ba6557a6d04539d69d75821c568f9efb09b5 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 12/42] deparse: Support CREATE TYPE AS RANGE

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 59ef0a1..0ba669d 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"
@@ -1477,6 +1479,104 @@ deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
 	return enumtype;
 }
 
+static ObjTree *
+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;
+
+	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(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(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(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(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(tmp));
+	}
+
+	append_array_object(range, "definition", definition);
+
+	systable_endscan(scan);
+	heap_close(pg_range, RowExclusiveLock);
+
+	return range;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -1983,7 +2083,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
-			command = NULL;
+			command = deparse_CreateRangeStmt(objectId, parsetree);
 			break;
 
 		case T_AlterEnumStmt:
-- 
2.1.4

0013-deparse-Support-CREATE-EXTENSION.patchtext/x-diff; charset=us-asciiDownload
>From 04dc40db971dc51c7e8c646cb5d822488da306f7 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 13/42] deparse: Support CREATE EXTENSION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 0ba669d..eef6bc1 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"
@@ -660,6 +661,81 @@ 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 ObjTree *
+deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
+{
+	CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *extStmt;
+	ObjTree	   *tmp;
+	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(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(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(tmp));
+
+	append_array_object(extStmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return extStmt;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -2029,7 +2105,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateExtensionStmt:
-			command = NULL;
+			command = deparse_CreateExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionStmt:
-- 
2.1.4

0014-deparse-Support-CREATE-RULE.patchtext/x-diff; charset=us-asciiDownload
>From f45a9141905e93b83d28809e357f95c7fa713fe7 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 14/42] deparse: Support CREATE RULE

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index eef6bc1..34d3392 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"
@@ -2013,6 +2015,95 @@ deparse_IndexStmt(Oid objectId, Node *parsetree)
 	return indexStmt;
 }
 
+static ObjTree *
+deparse_RuleStmt(Oid objectId, Node *parsetree)
+{
+	RuleStmt *node = (RuleStmt *) parsetree;
+	ObjTree	   *ruleStmt;
+	ObjTree	   *tmp;
+	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(action));
+	}
+	append_array_object(ruleStmt, "actions", list);
+
+	systable_endscan(scan);
+	heap_close(pg_rewrite, AccessShareLock);
+
+	return ruleStmt;
+}
+
+
+
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -2179,7 +2270,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RuleStmt:
-			command = NULL;
+			command = deparse_RuleStmt(objectId, parsetree);
 			break;
 
 		case T_CreateSeqStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 541e076..8e66b96 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 4447af9..9989cdd 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -30,6 +30,8 @@ extern void pg_get_indexdef_detailed(Oid indexrelid,
 extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 						  Node *whenClause, bool pretty);
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+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);
-- 
2.1.4

0015-deparse-Support-ALTER-TYPE-ADD-VALUE-enums.patchtext/x-diff; charset=us-asciiDownload
>From 80236c8702d3a553d103fe70070c638ae3013235 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 15/42] deparse: Support ALTER TYPE / ADD VALUE (enums)

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 34d3392..37292e4 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2143,6 +2143,39 @@ deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
 	return createSchema;
 }
 
+static ObjTree *
+deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
+{
+	AlterEnumStmt *node = (AlterEnumStmt *) parsetree;
+	ObjTree	   *alterEnum;
+	ObjTree	   *tmp;
+
+	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);
+
+	return alterEnum;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -2254,7 +2287,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterEnumStmt:
-			command = NULL;
+			command = deparse_AlterEnumStmt(objectId, parsetree);
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-- 
2.1.4

0016-deparse-Support-for-ALTER-OBJECT-RENAME.patchtext/x-diff; charset=us-asciiDownload
>From 67ff742875bfadd182ae28e40e54476c9e4d220e 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 16/42] deparse: Support for ALTER <OBJECT> RENAME

It supports everything but functions, aggregates, operator classes and
operator families.
---
 src/backend/catalog/dependency.c   |   6 +-
 src/backend/commands/alter.c       |   4 +-
 src/backend/commands/tablecmds.c   |  22 ++
 src/backend/tcop/deparse_utility.c | 422 ++++++++++++++++++++++++++++++++++++-
 src/include/commands/tablecmds.h   |   2 +
 5 files changed, 452 insertions(+), 4 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index bacb242..25ff326 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2269,8 +2269,9 @@ free_object_addresses(ObjectAddresses *addrs)
 ObjectClass
 getObjectClass(const ObjectAddress *object)
 {
-	/* only pg_class entries can have nonzero objectSubId */
-	if (object->classId != RelationRelationId &&
+	/* only pg_class and pg_type entries can have nonzero objectSubId */
+	if ((object->classId != RelationRelationId &&
+		 object->classId != TypeRelationId) &&
 		object->objectSubId != 0)
 		elog(ERROR, "invalid non-zero objectSubId for object class %u",
 			 object->classId);
@@ -2285,6 +2286,7 @@ getObjectClass(const ObjectAddress *object)
 			return OCLASS_PROC;
 
 		case TypeRelationId:
+			/* caller must check objectSubId */
 			return OCLASS_TYPE;
 
 		case CastRelationId:
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index dfd57e0..4150104 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -333,9 +333,11 @@ ExecRenameStmt(RenameStmt *stmt, int *objsubid)
 			return RenameRelation(stmt);
 
 		case OBJECT_COLUMN:
-		case OBJECT_ATTRIBUTE:
 			return renameatt(stmt, objsubid);
 
+		case OBJECT_ATTRIBUTE:
+			return renameatt_type(stmt, objsubid);
+
 		case OBJECT_RULE:
 			return RenameRewriteRule(stmt->relation, stmt->subname,
 									 stmt->newname);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 31fd7ba..ca672ab 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2361,6 +2361,28 @@ renameatt(RenameStmt *stmt, int *objsubid)
 	return relid;
 }
 
+/*
+ * As above, for composite types; returned OID is that of the pg_type tuple,
+ * not the pg_class entry.
+ */
+Oid
+renameatt_type(RenameStmt *stmt, int *objsubid)
+{
+	Oid		relid;
+	Oid		typid;
+	HeapTuple classTup;
+
+	relid = renameatt(stmt, objsubid);
+
+	classTup = SearchSysCache1(RELOID, relid);
+	if (!HeapTupleIsValid(classTup))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	typid = ((Form_pg_class) GETSTRUCT(classTup))->reltype;
+	ReleaseSysCache(classTup);
+
+	return typid;
+}
 
 /*
  * same logic as renameatt_internal
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 37292e4..6fe9c9b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -126,6 +126,7 @@ static void append_integer_object(ObjTree *tree, char *name, int64 value);
 static void append_float_object(ObjTree *tree, char *name, float8 value);
 static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
 static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+static const char *stringify_objtype(ObjectType objtype);
 
 /*
  * Allocate a new object tree to store parameter values.
@@ -1655,6 +1656,425 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return range;
 }
 
+/*
+ * Return the given object type as a string.
+ */
+static const char *
+stringify_objtype(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_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 "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_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_USER_MAPPING:
+			return "USER MAPPING";
+		case OBJECT_VIEW:
+			return "VIEW";
+
+		default:
+			elog(ERROR, "unsupported objtype %d", objtype);
+	}
+}
+
+static ObjTree *
+deparse_RenameStmt(Oid objectId, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree	   *renameStmt;
+	char	   *fmtstr;
+	Relation	relation;
+	Oid			schemaId;
+
+	/*
+	 * 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:
+			relation = relation_open(objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME COLUMN %%{colname}I TO %%{newname}I",
+							  stringify_objtype(node->relationType));
+			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_ATTRIBUTE:
+			{
+				HeapTuple	typeTup;
+				Oid			schemaId;
+				char	   *typname;
+
+				typeTup = SearchSysCache1(TYPEOID, objectId);
+				if (!HeapTupleIsValid(typeTup))
+					elog(ERROR, "cache lookup failed for type %u", objectId);
+
+				schemaId = ((Form_pg_type) GETSTRUCT(typeTup))->typnamespace;
+				typname = NameStr(((Form_pg_type) GETSTRUCT(typeTup))->typname);
+
+				renameStmt =
+					new_objtree_VA("ALTER TYPE %{identity}D RENAME ATTRIBUTE %{attname}I TO %{newname}I",
+								   0);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname(schemaId,
+															  typname));
+				append_string_object(renameStmt, "attname", node->subname);
+				ReleaseSysCache(typeTup);
+			}
+			break;
+
+		case OBJECT_SCHEMA:
+			{
+				renameStmt =
+					new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I",
+								   0);
+				append_string_object(renameStmt, "identity", node->subname);
+			}
+			break;
+
+		case OBJECT_FDW:
+		case OBJECT_LANGUAGE:
+		case OBJECT_FOREIGN_SERVER:
+			{
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_string_object(renameStmt, "identity",
+									 strVal(linitial(node->object)));
+			}
+			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:
+			{
+				char	   *identity;
+				HeapTuple	objTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				Oid			classId = get_objtype_catalog_oid(node->renameType);
+				AttrNumber	Anum_namespace = get_object_attnum_namespace(classId);
+
+				catalog = relation_open(classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, objectId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				identity = psprintf("%s.%s", get_namespace_name(DatumGetObjectId(objnsp)),
+									strVal(llast(node->object)));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_string_object(renameStmt, "identity", identity);
+
+				relation_close(catalog, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_OPCLASS:
+		case OBJECT_OPFAMILY:
+			{
+				char	   *identity;
+				HeapTuple	objTup;
+				HeapTuple	amTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				Oid			classId = get_objtype_catalog_oid(node->renameType);
+				AttrNumber	Anum_namespace = get_object_attnum_namespace(classId);
+				Oid			amoid;
+
+				catalog = relation_open(classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, objectId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				if (node->renameType == OBJECT_OPCLASS)
+					amoid = ((Form_pg_opclass) GETSTRUCT(objTup))->opcmethod;
+				else
+					amoid = ((Form_pg_opfamily) GETSTRUCT(objTup))->opfmethod;
+				amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+				if (!HeapTupleIsValid(amTup))
+					elog(ERROR, "cache lookup failed for access method %u", amoid);
+
+				identity = psprintf("%s.%s", get_namespace_name(DatumGetObjectId(objnsp)),
+									strVal(llast(node->object)));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s USING %%{amname}I RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_string_object(renameStmt, "identity", identity);
+				append_string_object(renameStmt, "amname",
+									 pstrdup(NameStr(((Form_pg_am) GETSTRUCT(amTup))->amname)));
+
+				ReleaseSysCache(amTup);
+				relation_close(catalog, AccessShareLock);
+			}
+
+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			{
+				char	   *newident;
+				ObjectAddress objaddr;
+				const char	   *quoted_newname;
+				StringInfoData old_ident;
+				char	   *start;
+
+				/*
+				 * Generating a function/aggregate identity is altogether too
+				 * messy, so instead of doing it ourselves, we generate one for
+				 * the renamed object, then strip out the name and replace it
+				 * with the original name from the parse node.  This is so ugly
+				 * that we don't dare do it for any other object kind.
+				 */
+
+				objaddr.classId = get_objtype_catalog_oid(node->renameType);
+				objaddr.objectId = objectId;
+				objaddr.objectSubId = 0;
+				newident = getObjectIdentity(&objaddr);
+
+				quoted_newname = quote_identifier(node->newname);
+				start = strstr(newident, quoted_newname);
+				if (!start)
+					elog(ERROR, "could not find %s in %s", start, newident);
+				initStringInfo(&old_ident);
+				appendBinaryStringInfo(&old_ident, newident, start - newident);
+				appendStringInfoString(&old_ident,
+									   quote_identifier(strVal(llast(node->object))));
+				appendStringInfoString(&old_ident, start + strlen(quoted_newname));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 1,
+											"identity", ObjTypeString, old_ident.data);
+			}
+			break;
+
+		case OBJECT_TABCONSTRAINT:
+		case OBJECT_DOMCONSTRAINT:
+			{
+				HeapTuple		conTup;
+				Form_pg_constraint	constrForm;
+				ObjTree		   *ident;
+
+				conTup = SearchSysCache1(CONSTROID, objectId);
+				constrForm = (Form_pg_constraint) GETSTRUCT(conTup);
+
+				if (node->renameType == OBJECT_TABCONSTRAINT)
+				{
+					fmtstr = "ALTER TABLE %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(RelationRelationId,
+														constrForm->conrelid);
+				}
+				else
+				{
+					fmtstr = "ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(TypeRelationId,
+														constrForm->contypid);
+				}
+				renameStmt = new_objtree_VA(fmtstr, 2,
+											"conname", ObjTypeString, node->subname,
+											"identity", ObjTypeObject, ident);
+				ReleaseSysCache(conTup);
+			}
+			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;
+
+		case OBJECT_POLICY:
+			{
+				HeapTuple	polTup;
+				Form_pg_policy polForm;
+				Relation	pg_policy;
+				ScanKeyData	key;
+				SysScanDesc	scan;
+
+				pg_policy = relation_open(PolicyRelationId, AccessShareLock);
+				ScanKeyInit(&key, ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(objectId));
+				scan = systable_beginscan(pg_policy, PolicyOidIndexId, true,
+										  NULL, 1, &key);
+				polTup = systable_getnext(scan);
+				if (!HeapTupleIsValid(polTup))
+					elog(ERROR, "cache lookup failed for policy %u", objectId);
+				polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+				renameStmt = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I on %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "policyname", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 polForm->polrelid));
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+				systable_endscan(scan);
+				relation_close(pg_policy, AccessShareLock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	append_string_object(renameStmt, "newname", node->newname);
+
+	return renameStmt;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -2369,7 +2789,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RenameStmt:
-			command = NULL;
+			command = deparse_RenameStmt(objectId, parsetree);
 			break;
 
 		case T_AlterObjectSchemaStmt:
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index c6e75e4..d56f393 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -55,6 +55,8 @@ extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass);
 
 extern Oid	renameatt(RenameStmt *stmt, int *attnum);
 
+extern Oid	renameatt_type(RenameStmt *stmt, int *attnum);
+
 extern Oid	RenameConstraint(RenameStmt *stmt);
 
 extern Oid	RenameRelation(RenameStmt *stmt);
-- 
2.1.4

0024-deparse-support-ALTER-THING-OWNER-TO.patchtext/x-diff; charset=us-asciiDownload
>From 4de9050c55183b6c7f12a0d399b332ae14a97a35 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 24/42] deparse: support ALTER THING OWNER TO

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 4008db2..002c65b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3849,6 +3849,29 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+	ObjTree	   *ownerStmt;
+	ObjectAddress addr;
+	char	   *fmt;
+
+	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));
+
+	return ownerStmt;
+}
+
+static ObjTree *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
 	HeapTuple   conTup;
@@ -4607,7 +4630,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterOwnerStmt:
-			command = NULL;
+			command = deparse_AlterOwnerStmt(objectId, parsetree);
 			break;
 
 		case T_CommentStmt:
-- 
2.1.4

0025-deparse-Support-ALTER-EXTENSION-UPDATE-TO.patchtext/x-diff; charset=us-asciiDownload
>From 65b9e8703180f157cebdf64e186b7bbcec3c4388 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 25/42] deparse: Support ALTER EXTENSION / UPDATE TO

---
 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 002c65b..422b11c 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1595,6 +1595,48 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
 	return extStmt;
 }
 
+static ObjTree *
+deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
+{
+	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *stmt;
+	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);
+
+	return stmt;
+}
+
 /*
  * deparse_ViewStmt
  *		deparse a ViewStmt
@@ -4486,7 +4528,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterExtensionStmt:
-			command = NULL;
+			command = deparse_AlterExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionContentsStmt:
-- 
2.1.4

0026-deparse-Support-GRANT-REVOKE.patchtext/x-diff; charset=us-asciiDownload
>From d2b665e39d3dd5607611fbb4444d52367f543328 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 26/42] deparse: Support GRANT/REVOKE

---
 src/backend/catalog/aclchk.c         |  37 ++----
 src/backend/commands/event_trigger.c | 103 +++++++++++++++++
 src/backend/tcop/deparse_utility.c   | 216 +++++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           |   1 +
 src/include/commands/event_trigger.h |   2 +
 src/include/tcop/deparse_utility.h   |  11 +-
 src/include/utils/aclchk.h           |  45 ++++++++
 7 files changed, 388 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 1e3888e..9e79654 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,15 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	if (EventTriggerSupportsGrantObjectType(istmt->objtype))
+		EventTriggerStashGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 67094c8..d44cda2 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1695,6 +1695,85 @@ EventTriggerComplexCmdEnd(void)
 	currentEventTriggerState->curcmd = NULL;
 }
 
+static const char *
+stringify_grantobjtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+			return "COLUMN";
+		case ACL_OBJECT_RELATION:
+			return "TABLE";
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case ACL_OBJECT_DATABASE:
+			return "DATABASE";
+		case ACL_OBJECT_DOMAIN:
+			return "DOMAIN";
+		case ACL_OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case ACL_OBJECT_FOREIGN_SERVER:
+			return "FOREIGN SERVER";
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTION";
+		case ACL_OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case ACL_OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case ACL_OBJECT_NAMESPACE:
+			return "SCHEMA";
+		case ACL_OBJECT_TABLESPACE:
+			return "TABLESPACE";
+		case ACL_OBJECT_TYPE:
+			return "TYPE";
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
+
+/*
+ * 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 ... 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 = creating_extension;
+	stashed->d.grant.type = stringify_grantobjtype(istmt->objtype);
+	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)
 {
@@ -1863,6 +1942,30 @@ 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(cmd->d.grant.istmt->is_grant ?
+												  "GRANT" : "REVOKE");
+				/* object_type */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.type);
+				/* 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 422b11c..a432a38 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -32,17 +32,23 @@
 #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"
+#include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -3984,6 +3990,213 @@ deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_GrantStmt(StashedCommand *cmd)
+{
+	InternalGrant *istmt;
+	ObjTree	   *grantStmt;
+	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 = "FOREIGN 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(tmp));
+	}
+	else
+	{
+		list = NIL;
+
+		if (istmt->privileges & ACL_INSERT)
+			list = lappend(list, new_string_object("INSERT"));
+		if (istmt->privileges & ACL_SELECT)
+			list = lappend(list, new_string_object("SELECT"));
+		if (istmt->privileges & ACL_UPDATE)
+			list = lappend(list, new_string_object("UPDATE"));
+		if (istmt->privileges & ACL_DELETE)
+			list = lappend(list, new_string_object("DELETE"));
+		if (istmt->privileges & ACL_TRUNCATE)
+			list = lappend(list, new_string_object("TRUNCATE"));
+		if (istmt->privileges & ACL_REFERENCES)
+			list = lappend(list, new_string_object("REFERENCES"));
+		if (istmt->privileges & ACL_TRIGGER)
+			list = lappend(list, new_string_object("TRIGGER"));
+		if (istmt->privileges & ACL_EXECUTE)
+			list = lappend(list, new_string_object("EXECUTE"));
+		if (istmt->privileges & ACL_USAGE)
+			list = lappend(list, new_string_object("USAGE"));
+		if (istmt->privileges & ACL_CREATE)
+			list = lappend(list, new_string_object("CREATE"));
+		if (istmt->privileges & ACL_CREATE_TEMP)
+			list = lappend(list, new_string_object("TEMPORARY"));
+		if (istmt->privileges & ACL_CONNECT)
+			list = lappend(list, new_string_object("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(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(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(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(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", "");
+	}
+
+	return grantStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -4766,6 +4979,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterTable:
 			tree = deparse_AlterTableStmt(cmd);
 			break;
+		case SCT_Grant:
+			tree = deparse_GrantStmt(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c09915d..92bccf5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -781,6 +781,7 @@ standard_ProcessUtility(Node *parsetree,
 				else
 					ExecuteGrantStmt((GrantStmt *) parsetree);
 			}
+			break;
 
 		case T_DropStmt:
 			{
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index cdce9e1..60b590b 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
 {
@@ -62,6 +63,7 @@ extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 
 extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
 						 ObjectType objtype, Oid secondaryOid, 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 8ebbf34..c364603 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_Simple,
-	SCT_AlterTable
+	SCT_AlterTable,
+	SCT_Grant
 } StashedCommandType;
 
 /*
@@ -61,6 +64,12 @@ typedef struct StashedCommand
 			ObjectType objtype;
 			List   *subcmds;
 		} alterTable;
+
+		struct GrantCommand
+		{
+			InternalGrant *istmt;
+			const char *type;
+		} 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 */
-- 
2.1.4

0027-deparse-Support-ALTER-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From 5e6d387d583c9f434ed8e58107043b5f1e2f9e89 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 8 Aug 2014 14:55:47 +0200
Subject: [PATCH 27/42] deparse: Support ALTER FUNCTION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index a432a38..6110827 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2957,6 +2957,113 @@ deparse_CreateFunction(Oid objectId, Node *parsetree)
 }
 
 /*
+ * deparse_AlterFunctionStmt
+ *		Deparse a AlterFunctionStmt (ALTER FUNCTION)
+ *
+ * Given a function OID and the parsetree that created it, return the JSON
+ * blob representing the alter command.
+ *
+ * XXX this is missing the per-function custom-GUC thing.
+ */
+static ObjTree *
+deparse_AlterFunction(Oid objectId, Node *parsetree)
+{
+	AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree;
+	ObjTree	   *alterFunc;
+	ObjTree	   *sign;
+	HeapTuple	procTup;
+	Form_pg_proc procForm;
+	List	   *params;
+	List	   *elems = NIL;
+	ListCell   *cell;
+	int			i;
+
+	/* 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);
+
+	alterFunc = new_objtree_VA("ALTER FUNCTION %{signature}s %{definition: }s", 0);
+
+	sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0);
+
+	params = NIL;
+
+	/*
+	 * ALTER FUNCTION does not change signature so we can use catalog
+	 * to get input type Oids.
+	 */
+	for (i = 0; i < procForm->pronargs; i++)
+	{
+		ObjTree	   *tmp = new_objtree_VA("%{type}T", 0);
+
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(procForm->proargtypes.values[i], -1));
+		params = lappend(params, new_object_object(tmp));
+	}
+
+	append_array_object(sign, "arguments", params);
+	append_object_object(sign, "identity",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 objectId));
+	append_object_object(alterFunc, "signature", sign);
+
+	foreach(cell, node->actions)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "volatility") == 0)
+		{
+			tmp = new_objtree_VA(strVal(defel->arg), 0);
+		}
+		else if (strcmp(defel->defname, "strict") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "RETURNS NULL ON NULL INPUT" :
+								 "CALLED ON NULL INPUT", 0);
+		}
+		else if (strcmp(defel->defname, "security") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "SECURITY DEFINER" : "SECURITY INVOKER", 0);
+		}
+		else if (strcmp(defel->defname, "leakproof") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "LEAKPROOF" : "", 0);
+		}
+		else if (strcmp(defel->defname, "cost") == 0)
+		{
+			tmp = new_objtree_VA("COST %{cost}n", 1,
+								 "cost", ObjTypeFloat,
+								 defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "rows") == 0)
+		{
+			tmp = new_objtree_VA("ROWS %{rows}n", 0);
+			if (defGetNumeric(defel) == 0)
+				append_bool_object(tmp, "present", false);
+			else
+				append_float_object(tmp, "rows",
+									 defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "set") == 0)
+			elog(ERROR, "unimplemented deparse of ALTER FUNCTION SET");
+
+		elems = lappend(elems, new_object_object(tmp));
+	}
+
+	append_array_object(alterFunc, "definition", elems);
+
+	ReleaseSysCache(procTup);
+
+	return alterFunc;
+}
+
+/*
  * Return the given object type as a string.
  */
 static const char *
@@ -4807,7 +4914,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterFunctionStmt:
-			command = NULL;
+			command = deparse_AlterFunction(objectId, parsetree);
 			break;
 
 		case T_RuleStmt:
-- 
2.1.4

0028-deparse-support-COMMENT-ON.patchtext/x-diff; charset=us-asciiDownload
>From 7332f3e927b82748398ae4563d23751d0b229499 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:45:04 -0300
Subject: [PATCH 28/42] deparse: support COMMENT ON

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 6110827..9d3ebba 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4026,6 +4026,96 @@ deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
 	return ownerStmt;
 }
 
+/*
+ * Deparse a CommentStmt when it pertains to a constraint.
+ */
+static ObjTree *
+deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	HeapTuple	constrTup;
+	Form_pg_constraint constrForm;
+	char	   *fmt;
+	ObjectAddress addr;
+
+	Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
+
+	if (node->comment)
+		fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}L",
+					   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	else
+		fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS NULL",
+					   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	comment = new_objtree_VA(fmt, 0);
+	if (node->comment)
+		append_string_object(comment, "comment", node->comment);
+
+	constrTup = SearchSysCache1(CONSTROID, objectId);
+	if (!HeapTupleIsValid(constrTup))
+		elog(ERROR, "cache lookup failed for constraint %u", objectId);
+	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
+
+	append_string_object(comment, "identity", pstrdup(NameStr(constrForm->conname)));
+
+	if (OidIsValid(constrForm->conrelid))
+	{
+		addr.classId = RelationRelationId;
+		addr.objectId = constrForm->conrelid;
+		addr.objectSubId = 0;
+	}
+	else
+	{
+		addr.classId = TypeRelationId;
+		addr.objectId = constrForm->contypid;
+		addr.objectSubId = 0;
+	}
+
+	append_string_object(comment, "parentobj",
+						 getObjectIdentity(&addr));
+
+	ReleaseSysCache(constrTup);
+
+	return comment;
+}
+
+static ObjTree *
+deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	ObjectAddress addr;
+	char	   *fmt;
+
+	/*
+	 * Constraints are sufficiently different that it is easier to handle them
+	 * separately.
+	 */
+	if (node->objtype == OBJECT_DOMCONSTRAINT ||
+		node->objtype == OBJECT_TABCONSTRAINT)
+		return deparse_CommentOnConstraintSmt(objectId, parsetree);
+
+	if (node->comment)
+		fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}L",
+					   stringify_objtype(node->objtype));
+	else
+		fmt = psprintf("COMMENT ON %s %%{identity}s IS NULL",
+					   stringify_objtype(node->objtype));
+
+	comment = new_objtree_VA(fmt, 0);
+	if (node->comment)
+		append_string_object(comment, "comment", node->comment);
+
+	addr.classId = get_objtype_catalog_oid(node->objtype);
+	addr.objectId = objectId;
+	addr.objectSubId = objectSubId;
+
+	append_string_object(comment, "identity",
+						 getObjectIdentity(&addr));
+
+	return comment;
+}
+
 static ObjTree *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
@@ -4996,7 +5086,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CommentStmt:
-			command = NULL;
+			command = deparse_CommentStmt(objectId, cmd->d.simple.objectSubId,
+										  parsetree);
 			break;
 
 		case T_GrantStmt:
-- 
2.1.4

0029-deparse-support-SECURITY-LABEL.patchtext/x-diff; charset=us-asciiDownload
>From b8a3640af3f06abb68a1e0b5a286a9ff7368a782 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 10 Feb 2015 17:35:51 -0300
Subject: [PATCH 29/42] deparse: support SECURITY LABEL

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 9d3ebba..bd0a4df 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4117,6 +4117,41 @@ deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_SecLabelStmt(Oid objectId, Oid objectSubId, Node *parsetree)
+{
+	SecLabelStmt *node = (SecLabelStmt *) parsetree;
+	ObjTree	   *label;
+	ObjectAddress addr;
+	char	   *fmt;
+
+	if (node->label)
+	{
+		fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}L",
+				   stringify_objtype(node->objtype));
+		label = new_objtree_VA(fmt, 0);
+
+		append_string_object(label, "label", node->label);
+	}
+	else
+	{
+		fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS NULL",
+				   stringify_objtype(node->objtype));
+		label = new_objtree_VA(fmt, 0);
+	}
+
+	append_string_object(label, "provider", node->provider);
+
+	addr.classId = get_objtype_catalog_oid(node->objtype);
+	addr.objectId = objectId;
+	addr.objectSubId = objectSubId;
+
+	append_string_object(label, "identity",
+						 getObjectIdentity(&addr));
+
+	return label;
+}
+
+static ObjTree *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
 	HeapTuple   conTup;
@@ -5113,7 +5148,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_SecLabelStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_SecLabelStmt(objectId, cmd->d.simple.objectSubId,
+										   parsetree);
 			break;
 
 		default:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 92bccf5..bec5231 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1486,7 +1486,8 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					SecLabelStmt *stmt = (SecLabelStmt *) parsetree;
 
-					ExecSecLabelStmt(stmt);
+					objectId = ExecSecLabelStmt(stmt);
+					EventTriggerStashCommand(objectId, 0, stmt->objtype, InvalidOid, parsetree);
 				}
 				break;
 
-- 
2.1.4

0030-deparse-support-ALTER-DOMAIN.patchtext/x-diff; charset=us-asciiDownload
>From d29332d1e91f9016278abfda90372e8118e47056 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 9 Feb 2015 15:38:07 -0300
Subject: [PATCH 30/42] deparse: support ALTER DOMAIN

---
 src/backend/commands/typecmds.c    | 70 +++++++++++++++++---------------
 src/backend/tcop/deparse_utility.c | 83 +++++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c         |  5 ++-
 src/backend/utils/adt/ruleutils.c  | 20 +++++++++
 src/include/commands/typecmds.h    |  2 +-
 src/include/utils/ruleutils.h      |  2 +
 6 files changed, 146 insertions(+), 36 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 0564535..0fd225c 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -108,7 +108,7 @@ static void checkEnumOwner(HeapTuple tup);
 static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
 					Oid baseTypeOid,
 					int typMod, Constraint *constr,
-					char *domainName);
+					char *domainName, Oid *constrOid);
 
 
 /*
@@ -1075,7 +1075,7 @@ DefineDomain(CreateDomainStmt *stmt)
 			case CONSTR_CHECK:
 				domainAddConstraint(domainoid, domainNamespace,
 									basetypeoid, basetypeMod,
-									constr, domainName);
+									constr, domainName, NULL);
 				break;
 
 				/* Other constraint types were fully processed above */
@@ -2462,7 +2462,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
  * Implements the ALTER DOMAIN .. ADD CONSTRAINT statement.
  */
 Oid
-AlterDomainAddConstraint(List *names, Node *newConstraint)
+AlterDomainAddConstraint(List *names, Node *newConstraint, Oid *constrOid)
 {
 	TypeName   *typename;
 	Oid			domainoid;
@@ -2546,7 +2546,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
 
 	ccbin = domainAddConstraint(domainoid, typTup->typnamespace,
 								typTup->typbasetype, typTup->typtypmod,
-								constr, NameStr(typTup->typname));
+								constr, NameStr(typTup->typname), constrOid);
 
 	/*
 	 * If requested to validate the constraint, test all values stored in the
@@ -2960,13 +2960,14 @@ checkDomainOwner(HeapTuple tup)
 static char *
 domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 					int typMod, Constraint *constr,
-					char *domainName)
+					char *domainName, Oid *constrOid)
 {
 	Node	   *expr;
 	char	   *ccsrc;
 	char	   *ccbin;
 	ParseState *pstate;
 	CoerceToDomainValue *domVal;
+	Oid			ccoid;
 
 	/*
 	 * Assign or validate constraint name
@@ -3045,34 +3046,37 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	/*
 	 * Store the constraint in pg_constraint
 	 */
-	CreateConstraintEntry(constr->conname,		/* Constraint Name */
-						  domainNamespace,		/* namespace */
-						  CONSTRAINT_CHECK,		/* Constraint Type */
-						  false,	/* Is Deferrable */
-						  false,	/* Is Deferred */
-						  !constr->skip_validation,		/* Is Validated */
-						  InvalidOid,	/* not a relation constraint */
-						  NULL,
-						  0,
-						  domainOid,	/* domain constraint */
-						  InvalidOid,	/* no associated index */
-						  InvalidOid,	/* Foreign key fields */
-						  NULL,
-						  NULL,
-						  NULL,
-						  NULL,
-						  0,
-						  ' ',
-						  ' ',
-						  ' ',
-						  NULL, /* not an exclusion constraint */
-						  expr, /* Tree form of check constraint */
-						  ccbin,	/* Binary form of check constraint */
-						  ccsrc,	/* Source form of check constraint */
-						  true, /* is local */
-						  0,	/* inhcount */
-						  false,	/* connoinherit */
-						  false);		/* is_internal */
+	ccoid =
+		CreateConstraintEntry(constr->conname,		/* Constraint Name */
+							  domainNamespace,		/* namespace */
+							  CONSTRAINT_CHECK,		/* Constraint Type */
+							  false,	/* Is Deferrable */
+							  false,	/* Is Deferred */
+							  !constr->skip_validation,		/* Is Validated */
+							  InvalidOid,	/* not a relation constraint */
+							  NULL,
+							  0,
+							  domainOid,	/* domain constraint */
+							  InvalidOid,	/* no associated index */
+							  InvalidOid,	/* Foreign key fields */
+							  NULL,
+							  NULL,
+							  NULL,
+							  NULL,
+							  0,
+							  ' ',
+							  ' ',
+							  ' ',
+							  NULL, /* not an exclusion constraint */
+							  expr, /* Tree form of check constraint */
+							  ccbin,	/* Binary form of check constraint */
+							  ccsrc,	/* Source form of check constraint */
+							  true, /* is local */
+							  0,	/* inhcount */
+							  false,	/* connoinherit */
+							  false);		/* is_internal */
+	if (constrOid)
+		*constrOid = ccoid;
 
 	/*
 	 * Return the compiled constraint expression so the calling routine can
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index bd0a4df..b5b6727 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1602,6 +1602,86 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_AlterDomainStmt(Oid objectId, Node *parsetree, Oid secondaryOid)
+{
+	AlterDomainStmt *node = (AlterDomainStmt *) parsetree;
+	HeapTuple	domTup;
+	Form_pg_type domForm;
+	ObjTree	   *alterDom;
+	char	   *fmt;
+
+	/* ALTER DOMAIN DROP CONSTRAINT is handled by the DROP support code */
+	if (node->subtype == 'X')
+		return NULL;
+
+	domTup = SearchSysCache1(TYPEOID, objectId);
+	if (!HeapTupleIsValid(domTup))
+		elog(ERROR, "cache lookup failed for domain with OID %u",
+			 objectId);
+	domForm = (Form_pg_type) GETSTRUCT(domTup);
+
+	switch (node->subtype)
+	{
+		case 'T':
+			/* SET DEFAULT / DROP DEFAULT */
+			if (node->def == NULL)
+				fmt = "ALTER DOMAIN %{identity}D DROP DEFAULT";
+			else
+				fmt = "ALTER DOMAIN %{identity}D SET DEFAULT %{default}s";
+			break;
+		case 'N':
+			/* DROP NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D DROP NOT NULL";
+			break;
+		case 'O':
+			/* SET NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D SET NOT NULL";
+			break;
+		case 'C':
+			/* ADD CONSTRAINT.  Only CHECK constraints are supported by domains */
+			fmt = "ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}I %{definition}s";
+			break;
+		case 'V':
+			/* VALIDATE CONSTRAINT */
+			fmt = "ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}I";
+			break;
+		default:
+			elog(ERROR, "invalid subtype %c", node->subtype);
+	}
+
+	alterDom = new_objtree_VA(fmt, 0);
+	append_object_object(alterDom, "identity",
+						 new_objtree_for_qualname(domForm->typnamespace,
+												  NameStr(domForm->typname)));
+
+	/*
+	 * Process subtype-specific options.  Validating a constraint
+	 * requires its name ...
+	 */
+	if (node->subtype == 'V')
+		append_string_object(alterDom, "constraint_name", node->name);
+
+	/* ... a new constraint has a name and definition ... */
+	if (node->subtype == 'C')
+	{
+		append_string_object(alterDom, "definition",
+							 pg_get_constraintdef_string(secondaryOid, false));
+		/* can't rely on node->name here; might not be defined */
+		append_string_object(alterDom, "constraint_name",
+							 get_constraint_name(secondaryOid));
+	}
+
+	/* ... and setting a default has a definition only. */
+	if (node->subtype == 'T' && node->def != NULL)
+		append_string_object(alterDom, "default", DomainGetDefault(domTup));
+
+	/* done */
+	ReleaseSysCache(domTup);
+
+	return alterDom;
+}
+
+static ObjTree *
 deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
 {
 	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
@@ -4956,7 +5036,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterDomainStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterDomainStmt(objectId, parsetree,
+											  cmd->d.simple.secondaryOid);
 			break;
 
 			/* other local objects */
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index bec5231..20d6565 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1075,6 +1075,8 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree;
 
+					secondaryOid = InvalidOid;
+
 					/*
 					 * Some or all of these functions are recursive to cover
 					 * inherited things, so permission checks are done there.
@@ -1104,7 +1106,8 @@ ProcessUtilitySlow(Node *parsetree,
 						case 'C':		/* ADD CONSTRAINT */
 							objectId =
 								AlterDomainAddConstraint(stmt->typeName,
-														 stmt->def);
+														 stmt->def,
+														 &secondaryOid);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
 							objectId =
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c281c38..2c4786f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9700,6 +9700,26 @@ RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
 	return defstr;
 }
 
+char *
+DomainGetDefault(HeapTuple domTup)
+{
+	Datum	def;
+	Node   *defval;
+	char   *defstr;
+	bool	isnull;
+
+	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+							 &isnull);
+	if (isnull)
+		elog(ERROR, "domain \"%s\" does not have a default value",
+			 NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+	defval = stringToNode(TextDatumGetCString(def));
+	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
+									   false, false, 0, 0);
+
+	return defstr;
+}
+
 /*
  * Return the defaults values of arguments to a function, as a list of
  * deparsed expressions.
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index e18a714..b38584c 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -32,7 +32,7 @@ extern Oid	AssignTypeArrayOid(void);
 
 extern Oid	AlterDomainDefault(List *names, Node *defaultRaw);
 extern Oid	AlterDomainNotNull(List *names, bool notNull);
-extern Oid	AlterDomainAddConstraint(List *names, Node *constr);
+extern Oid	AlterDomainAddConstraint(List *names, Node *constr, Oid *constrOid);
 extern Oid	AlterDomainValidateConstraint(List *names, char *constrName);
 extern Oid AlterDomainDropConstraint(List *names, const char *constrName,
 						  DropBehavior behavior, bool missing_ok);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 91b61f9..7b22ea0 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -48,4 +48,6 @@ extern List *FunctionGetDefaults(text *proargdefaults);
 extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
 						 List *dpcontext);
 
+extern char *DomainGetDefault(HeapTuple domTup);
+
 #endif	/* RULEUTILS_H */
-- 
2.1.4

0031-deparse-support-ALTER-.-SET-SCHEMA.patchtext/x-diff; charset=us-asciiDownload
>From 69674ea6ef2da763ccd27a3f408c0f5fe489fea1 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 9 Feb 2015 19:17:21 -0300
Subject: [PATCH 31/42] deparse: support ALTER ... SET SCHEMA

---
 src/backend/commands/alter.c       | 16 ++++++++------
 src/backend/commands/extension.c   |  6 +++++-
 src/backend/commands/tablecmds.c   |  5 ++++-
 src/backend/commands/typecmds.c    |  9 ++++++--
 src/backend/tcop/deparse_utility.c | 44 +++++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c         |  7 +++---
 src/include/commands/alter.h       |  3 ++-
 src/include/commands/extension.h   |  3 ++-
 src/include/commands/tablecmds.h   |  2 +-
 src/include/commands/typecmds.h    |  3 ++-
 10 files changed, 80 insertions(+), 18 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 4150104..943ddef 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -398,24 +398,24 @@ ExecRenameStmt(RenameStmt *stmt, int *objsubid)
  * type, the function appropriate to that type is executed.
  */
 Oid
-ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
+ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, Oid *oldschema)
 {
 	switch (stmt->objectType)
 	{
 		case OBJECT_EXTENSION:
-			return AlterExtensionNamespace(stmt->object, stmt->newschema);
+			return AlterExtensionNamespace(stmt->object, stmt->newschema, oldschema);
 
 		case OBJECT_FOREIGN_TABLE:
 		case OBJECT_SEQUENCE:
 		case OBJECT_TABLE:
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
-			return AlterTableNamespace(stmt);
+			return AlterTableNamespace(stmt, oldschema);
 
 		case OBJECT_DOMAIN:
 		case OBJECT_TYPE:
 			return AlterTypeNamespace(stmt->object, stmt->newschema,
-									  stmt->objectType);
+									  stmt->objectType, oldschema);
 
 			/* generic code path */
 		case OBJECT_AGGREGATE:
@@ -435,6 +435,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
 				Oid			classId;
 				Oid			nspOid;
 				ObjectAddress address;
+				Oid			oldNspOid;
 
 				address = get_object_address(stmt->objectType,
 											 stmt->object,
@@ -447,10 +448,13 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
 				catalog = heap_open(classId, RowExclusiveLock);
 				nspOid = LookupCreationNamespace(stmt->newschema);
 
-				AlterObjectNamespace_internal(catalog, address.objectId,
-											  nspOid);
+				oldNspOid = AlterObjectNamespace_internal(catalog, address.objectId,
+														  nspOid);
 				heap_close(catalog, RowExclusiveLock);
 
+				if (oldschema)
+					*oldschema = oldNspOid;
+
 				return address.objectId;
 			}
 			break;
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 3b95552..14be4cd 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2400,7 +2400,7 @@ extension_config_remove(Oid extensionoid, Oid tableoid)
  * Execute ALTER EXTENSION SET SCHEMA
  */
 Oid
-AlterExtensionNamespace(List *names, const char *newschema)
+AlterExtensionNamespace(List *names, const char *newschema, Oid *oldschema)
 {
 	char	   *extensionName;
 	Oid			extensionOid;
@@ -2557,6 +2557,10 @@ AlterExtensionNamespace(List *names, const char *newschema)
 							   get_namespace_name(oldNspOid))));
 	}
 
+	/* report old schema, if caller wants it */
+	if (oldschema)
+		*oldschema = oldNspOid;
+
 	systable_endscan(depScan);
 
 	relation_close(depRel, AccessShareLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 377e5dd..7634a7d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11057,7 +11057,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
  * Execute ALTER TABLE SET SCHEMA
  */
 Oid
-AlterTableNamespace(AlterObjectSchemaStmt *stmt)
+AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
 {
 	Relation	rel;
 	Oid			relid;
@@ -11109,6 +11109,9 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt)
 	AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
 	free_object_addresses(objsMoved);
 
+	if (oldschema)
+		*oldschema = oldNspOid;
+
 	/* close rel, but keep lock until commit */
 	relation_close(rel, NoLock);
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 0fd225c..8a1f6b5 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3508,11 +3508,13 @@ AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
  * Execute ALTER TYPE SET SCHEMA
  */
 Oid
-AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype)
+AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype,
+				   Oid *oldschema)
 {
 	TypeName   *typename;
 	Oid			typeOid;
 	Oid			nspOid;
+	Oid			oldNspOid;
 	ObjectAddresses *objsMoved;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
@@ -3530,9 +3532,12 @@ AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype)
 	nspOid = LookupCreationNamespace(newschema);
 
 	objsMoved = new_object_addresses();
-	AlterTypeNamespace_oid(typeOid, nspOid, objsMoved);
+	oldNspOid = AlterTypeNamespace_oid(typeOid, nspOid, objsMoved);
 	free_object_addresses(objsMoved);
 
+	if (oldschema)
+		*oldschema = oldNspOid;
+
 	return typeOid;
 }
 
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index b5b6727..a813f94 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4084,6 +4084,47 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_AlterObjectSchemaStmt(Oid objectId, Node *parsetree, Oid oldschema)
+{
+	AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree;
+	ObjTree	   *alterStmt;
+	ObjectAddress addr;
+	char	   *fmt;
+	char	   *identity;
+	char	   *newschema;
+	char	   *oldschname;
+	char	   *ident;
+
+	newschema = node->newschema;
+
+	fmt = psprintf("ALTER %s %%{identity}s SET SCHEMA %%{newschema}I",
+				   stringify_objtype(node->objectType));
+	alterStmt = new_objtree_VA(fmt, 0);
+	append_string_object(alterStmt, "newschema", newschema);
+
+	/*
+	 * Since the command has already taken place from the point of view of
+	 * catalogs, getObjectIdentity returns the object name with the already
+	 * changed schema.  The output of our deparsing must return the original
+	 * schema name however, so we chop the schema name off the identity string
+	 * and then prepend the quoted schema name.
+	 */
+	addr.classId = get_objtype_catalog_oid(node->objectType);
+	addr.objectId = objectId;
+	addr.objectSubId = 0;
+	identity = getObjectIdentity(&addr);
+	oldschname = get_namespace_name(oldschema);
+	if (!oldschname)
+		elog(ERROR, "cache lookup failed for schema with OID %u", oldschema);
+	ident = psprintf("%s%s",
+					 quote_identifier(oldschname),
+					 identity + strlen(quote_identifier(newschema)));
+	append_string_object(alterStmt, "identity", ident);
+
+	return alterStmt;
+}
+
+static ObjTree *
 deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
 {
 	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
@@ -5194,7 +5235,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterObjectSchemaStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterObjectSchemaStmt(objectId, parsetree,
+													cmd->d.simple.secondaryOid);
 			break;
 
 		case T_AlterOwnerStmt:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 20d6565..438ee9b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -818,7 +818,7 @@ standard_ProcessUtility(Node *parsetree,
 									   context, params,
 									   dest, completionTag);
 				else
-					ExecAlterObjectSchemaStmt(stmt);
+					ExecAlterObjectSchemaStmt(stmt, NULL);
 			}
 			break;
 
@@ -1438,10 +1438,11 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				objectId = ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
+				objectId = ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
+													 &secondaryOid);
 				EventTriggerStashCommand(objectId, 0,
 										 ((AlterObjectSchemaStmt *) parsetree)->objectType,
-										 parsetree);
+										 secondaryOid, parsetree);
 				break;
 
 			case T_AlterOwnerStmt:
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index 0382037..ecba1d7 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -20,7 +20,8 @@
 
 extern Oid	ExecRenameStmt(RenameStmt *stmt, int *objsubid);
 
-extern Oid	ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
+extern Oid	ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
+						  Oid *oldschema);
 extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 						 ObjectAddresses *objsMoved);
 
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 4259072..f678950 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -43,7 +43,8 @@ extern Oid	ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt);
 extern Oid	get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
 
-extern Oid	AlterExtensionNamespace(List *names, const char *newschema);
+extern Oid	AlterExtensionNamespace(List *names, const char *newschema,
+						Oid *oldschema);
 
 extern void AlterExtensionOwner_oid(Oid extensionOid, Oid newOwnerId);
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index d56f393..fa77235 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -37,7 +37,7 @@ extern void AlterTableInternal(Oid relid, List *cmds, bool recurse);
 
 extern Oid	AlterTableMoveAll(AlterTableMoveAllStmt *stmt);
 
-extern Oid	AlterTableNamespace(AlterObjectSchemaStmt *stmt);
+extern Oid	AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema);
 
 extern void AlterTableNamespaceInternal(Relation rel, Oid oldNspOid,
 							Oid nspOid, ObjectAddresses *objsMoved);
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index b38584c..ecc4551 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -45,7 +45,8 @@ extern Oid	RenameType(RenameStmt *stmt);
 extern Oid	AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype);
 extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
 					   bool hasDependEntry);
-extern Oid	AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype);
+extern Oid	AlterTypeNamespace(List *names, const char *newschema,
+				   ObjectType objecttype, Oid *oldschema);
 extern Oid	AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, ObjectAddresses *objsMoved);
 extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
 						   bool isImplicitArray,
-- 
2.1.4

0032-deparse-support-ALTER-EXTENSION-ADD-DROP.patchtext/x-diff; charset=us-asciiDownload
>From aefc63f845900445d04c60f4ce73d8f1a92c9d97 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 9 Feb 2015 20:41:51 -0300
Subject: [PATCH 32/42] deparse: support ALTER EXTENSION ADD/DROP

---
 src/backend/commands/extension.c   |  6 +++++-
 src/backend/tcop/deparse_utility.c | 27 ++++++++++++++++++++++++++-
 src/backend/tcop/utility.c         |  5 +++--
 src/include/commands/extension.h   |  3 ++-
 4 files changed, 36 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 14be4cd..4b28a8d 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2885,7 +2885,7 @@ ApplyExtensionUpdates(Oid extensionOid,
  * Execute ALTER EXTENSION ADD/DROP
  */
 Oid
-ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt)
+ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt, Oid *objectId)
 {
 	ObjectAddress extension;
 	ObjectAddress object;
@@ -2910,6 +2910,10 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt)
 	object = get_object_address(stmt->objtype, stmt->objname, stmt->objargs,
 								&relation, ShareUpdateExclusiveLock, false);
 
+	Assert(object.objectSubId == 0);
+	if (objectId)
+		*objectId = object.objectId;
+
 	/* Permission check: must own target object, too */
 	check_object_ownership(GetUserId(), stmt->objtype, object,
 						   stmt->objname, stmt->objargs, relation);
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index a813f94..cefea83 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1682,6 +1682,30 @@ deparse_AlterDomainStmt(Oid objectId, Node *parsetree, Oid secondaryOid)
 }
 
 static ObjTree *
+deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree, Oid secondaryOid)
+{
+	AlterExtensionContentsStmt *node = (AlterExtensionContentsStmt *) parsetree;
+	ObjTree	   *stmt;
+	char	   *fmt;
+	ObjectAddress addr;
+
+	Assert(node->action == +1 || node->action == -1);
+
+	fmt = psprintf("ALTER EXTENSION %%{extidentity}I %s %s %%{objidentity}s",
+				   node->action == +1 ? "ADD" : "DROP",
+				   stringify_objtype(node->objtype));
+	addr.classId = get_objtype_catalog_oid(node->objtype);
+	addr.objectId = secondaryOid;
+	addr.objectSubId = 0;
+
+	stmt = new_objtree_VA(fmt, 2, "extidentity", ObjTypeString, node->extname,
+						  "objidentity", ObjTypeString,
+						  getObjectIdentity(&addr));
+
+	return stmt;
+}
+
+static ObjTree *
 deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
 {
 	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
@@ -5099,7 +5123,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterExtensionContentsStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterExtensionContentsStmt(objectId, parsetree,
+														 cmd->d.simple.secondaryOid);
 			break;
 
 		case T_CreateFdwStmt:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 438ee9b..a06b901 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1248,8 +1248,9 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				objectId = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
-				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, InvalidOid, parsetree);
+				objectId = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+														  &secondaryOid);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, secondaryOid, parsetree);
 				break;
 
 			case T_CreateFdwStmt:
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index f678950..b3b73cc 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -38,7 +38,8 @@ extern Oid InsertExtensionTuple(const char *extName, Oid extOwner,
 
 extern Oid	ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
 
-extern Oid	ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt);
+extern Oid	ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
+							   Oid *objectId);
 
 extern Oid	get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
-- 
2.1.4

0033-deparse-support-CREATE-CAST.patchtext/x-diff; charset=us-asciiDownload
>From 7ea75c6fc7d52ad3546258238482292e0382be76 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 10 Feb 2015 14:09:51 -0300
Subject: [PATCH 33/42] deparse: support CREATE CAST

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index cefea83..93df2da 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_cast.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -4332,6 +4333,81 @@ deparse_CreateConversion(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateCastStmt(Oid objectId, Node *parsetree)
+{
+	CreateCastStmt *node = (CreateCastStmt *) parsetree;
+	Relation	castrel;
+	HeapTuple	castTup;
+	Form_pg_cast castForm;
+	ObjTree	   *createCast;
+	char	   *context;
+
+	castrel = heap_open(CastRelationId, AccessShareLock);
+	castTup = get_catalog_object_by_oid(castrel, objectId);
+	if (!HeapTupleIsValid(castTup))
+		elog(ERROR, "cache lookup failed for cast with OID %u", objectId);
+	castForm = (Form_pg_cast) GETSTRUCT(castTup);
+
+	createCast = new_objtree_VA("CREATE CAST (%{sourcetype}T AS %{targettype}T) %{mechanism}s %{context}s",
+								2, "sourcetype", ObjTypeObject,
+								new_objtree_for_type(castForm->castsource, -1),
+								"targettype", ObjTypeObject,
+								new_objtree_for_type(castForm->casttarget, -1));
+
+	if (node->inout)
+		append_string_object(createCast, "mechanism", "WITH INOUT");
+	else if (node->func == NULL)
+		append_string_object(createCast, "mechanism", "WITHOUT FUNCTION");
+	else
+	{
+		ObjTree	   *tmp;
+		StringInfoData func;
+		HeapTuple	funcTup;
+		Form_pg_proc funcForm;
+		int			i;
+
+		funcTup = SearchSysCache1(PROCOID, castForm->castfunc);
+		funcForm = (Form_pg_proc) GETSTRUCT(funcTup);
+
+		initStringInfo(&func);
+		appendStringInfo(&func, "%s(",
+						quote_qualified_identifier(get_namespace_name(funcForm->pronamespace),
+												   NameStr(funcForm->proname)));
+		for (i = 0; i < funcForm->pronargs; i++)
+			appendStringInfoString(&func,
+								   format_type_be_qualified(funcForm->proargtypes.values[i]));
+		appendStringInfoChar(&func, ')');
+
+		tmp = new_objtree_VA("WITH FUNCTION %{castfunction}s", 1,
+							 "castfunction", ObjTypeString, func.data);
+		append_object_object(createCast, "mechanism", tmp);
+
+		ReleaseSysCache(funcTup);
+	}
+
+	switch (node->context)
+	{
+		case COERCION_IMPLICIT:
+			context = "AS IMPLICIT";
+			break;
+		case COERCION_ASSIGNMENT:
+			context = "AS ASSIGNMENT";
+			break;
+		case COERCION_EXPLICIT:
+			context = "";
+			break;
+		default:
+			elog(ERROR, "invalid coercion code %c", node->context);
+			return NULL;	/* keep compiler quiet */
+	}
+	append_string_object(createCast, "context", context);
+
+	heap_close(castrel, AccessShareLock);
+
+	return createCast;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -5227,7 +5303,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateCastStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateCastStmt(objectId, parsetree);
 			break;
 
 		case T_CreateOpClassStmt:
-- 
2.1.4

0034-deparse-support-CREATE-TABLE-AS.patchtext/x-diff; charset=us-asciiDownload
>From ad16e4e03443f673fcd946fdd47ad67aa8531542 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:30:04 -0300
Subject: [PATCH 34/42] deparse: support CREATE TABLE AS

---
 src/backend/tcop/deparse_utility.c | 142 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  15 ++++
 src/include/utils/ruleutils.h      |   1 +
 3 files changed, 156 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 93df2da..2a0a2b9 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4034,7 +4034,146 @@ deparse_RuleStmt(Oid objectId, Node *parsetree)
 	return ruleStmt;
 }
 
+static ObjTree *
+deparse_CreateTableAsStmt(Oid objectId, Node *parsetree)
+{
+	CreateTableAsStmt *node = (CreateTableAsStmt *) parsetree;
+	ObjTree	   *createStmt;
+	ObjTree	   *tmp;
+	char	   *fmt;
+	List	   *list;
 
+	/*
+	 * Reject unsupported case right away.
+	 */
+	if (((Query *) (node->query))->commandType == CMD_UTILITY)
+		elog(ERROR, "unimplemented deparse of CREATE TABLE AS EXECUTE");
+
+	/*
+	 * Note that INSERT INTO is deparsed as CREATE TABLE AS.  They are
+	 * functionally equivalent synonyms so there is no harm from this.
+	 */
+	if (node->relkind == OBJECT_MATVIEW)
+		fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+	else
+		fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with}s %{on_commit}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+
+	createStmt =
+		new_objtree_VA(fmt, 2,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(node->into->rel->relpersistence),
+					   "if_not_exists", ObjTypeString,
+					   node->if_not_exists ? "IF NOT EXISTS" : "");
+	append_object_object(createStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+
+	/* Add an ON COMMIT clause.  CREATE MATERIALIZED VIEW doesn't have one */
+	if (node->relkind == OBJECT_TABLE)
+	{
+		tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0);
+		switch (node->into->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);
+	}
+
+	/* add a TABLESPACE clause */
+	tmp = new_objtree_VA("TABLESPACE %{tablespace}I", 0);
+	if (node->into->tableSpaceName)
+		append_string_object(tmp, "tablespace", node->into->tableSpaceName);
+	else
+	{
+		append_null_object(tmp, "tablespace");
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(createStmt, "tablespace", tmp);
+
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->into->skipData ? true : false);
+	append_object_object(createStmt, "with_no_data", tmp);
+
+	/* add the column list, if any */
+	if (node->into->colNames == NIL)
+		tmp = new_objtree_VA("", 1,
+							 "present", ObjTypeBool, false);
+	else
+	{
+		ListCell *cell;
+
+		list = NIL;
+		foreach (cell, node->into->colNames)
+			list = lappend(list, new_string_object(strVal(lfirst(cell))));
+
+		tmp = new_objtree_VA("(%{columns:, }I)", 1,
+							 "columns", ObjTypeArray, list);
+	}
+	append_object_object(createStmt, "columns", tmp);
+
+	/* add the query */
+	Assert(IsA(node->query, Query));
+	append_string_object(createStmt, "query",
+						 pg_get_createtableas_def((Query *) node->query));
+
+	/* add a WITH clause, but only if any options are set */
+	tmp = new_objtree_VA("WITH (%{with_options:, }s)", 0);
+	if (node->into->options == NIL)
+	{
+		append_null_object(tmp, "with_options");
+		append_bool_object(tmp, "present", false);
+	}
+	else
+	{
+		ListCell   *cell;
+
+		list = NIL;
+		foreach (cell, node->into->options)
+		{
+			DefElem *opt = (DefElem *) lfirst(cell);
+			char   *defname;
+			char   *value;
+			ObjTree *tmp2;
+
+			if (opt->defnamespace)
+				defname = psprintf("%s.%s", opt->defnamespace, opt->defname);
+			else
+				defname = opt->defname;
+
+			value = opt->arg ? defGetString(opt) :
+				defGetBoolean(opt) ? "TRUE" : "FALSE";
+			tmp2 = new_objtree_VA("%{option}s=%{value}s", 2,
+								  "option", ObjTypeString, defname,
+								  "value", ObjTypeString, value);
+			list = lappend(list, new_object_object(tmp2));
+		}
+		append_array_object(tmp, "with_options", list);
+	}
+	append_object_object(createStmt, "with", tmp);
+
+	return createStmt;
+}
 
 /*
  * deparse_CreateSchemaStmt
@@ -5278,8 +5417,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateTableAsStmt:
-			/* XXX handle at least the CREATE MATVIEW case? */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateTableAsStmt(objectId, parsetree);
 			break;
 
 		case T_RefreshMatViewStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2c4786f..ad7153d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -750,6 +750,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def	- Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * get_triggerdef			- Get the definition of a trigger
  * ----------
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 7b22ea0..2c020e9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -33,6 +33,7 @@ extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
 					   char **whereClause, List **actions);
 extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
 
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
-- 
2.1.4

0035-deparse-support-CREATE-POLICY.patchtext/x-diff; charset=us-asciiDownload
>From b1e3ac04467589fdc3a9e4136ceff0ba27d2eedb Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:30:49 -0300
Subject: [PATCH 35/42] deparse: support CREATE POLICY

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 2a0a2b9..cd600ff 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4402,6 +4402,91 @@ deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreatePolicyStmt(Oid objectId, Node *parsetree)
+{
+	CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree;
+	ObjTree	   *policy;
+	ObjTree	   *tmp;
+	Relation	polRel = heap_open(PolicyRelationId, AccessShareLock);
+	HeapTuple	polTup = get_catalog_object_by_oid(polRel, objectId);
+	Form_pg_policy polForm;
+	ListCell   *cell;
+	List	   *list;
+
+	if (!HeapTupleIsValid(polTup))
+		elog(ERROR, "cache lookup failed for policy %u", objectId);
+	polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+	policy = new_objtree_VA("CREATE POLICY %{identity}I ON %{table}D %{for_command}s "
+							"%{to_role}s %{using}s %{with_check}s", 1,
+							"identity", ObjTypeString, node->policy_name);
+
+	/* add the "ON table" clause */
+	append_object_object(policy, "table",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 polForm->polrelid));
+	/* add "FOR command" clause */
+	tmp = new_objtree_VA("FOR %{command}s", 1,
+						 "command", ObjTypeString, node->cmd);
+	append_object_object(policy, "for_command", tmp);
+
+	/* add the "TO role" clause. Always contains at least PUBLIC */
+	tmp = new_objtree_VA("TO %{role:, }I", 0);
+	list = NIL;
+	foreach (cell, node->roles)
+	{
+		list = lappend(list,
+					   new_string_object(strVal(lfirst(cell))));
+	}
+	append_array_object(tmp, "role", list);
+	append_object_object(policy, "to_role", tmp);
+
+	/* add the USING clause, if any */
+	tmp = new_objtree_VA("USING (%{expression}s)", 0);
+	if (node->qual)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polqual expression in policy %u", objectId);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policy, "using", tmp);
+
+	/* add the WITH CHECK clause, if any */
+	tmp = new_objtree_VA("WITH CHECK (%{expression}s)", 0);
+	if (node->with_check)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polwithcheck expression in policy %u", objectId);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policy, "with_check", tmp);
+
+	heap_close(polRel, AccessShareLock);
+
+	return policy;
+}
+
+static ObjTree *
 deparse_SecLabelStmt(Oid objectId, Oid objectSubId, Node *parsetree)
 {
 	SecLabelStmt *node = (SecLabelStmt *) parsetree;
@@ -5502,7 +5587,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreatePolicyStmt:	/* CREATE POLICY */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreatePolicyStmt(objectId, parsetree);
 			break;
 
 		case T_AlterPolicyStmt:		/* ALTER POLICY */
-- 
2.1.4

0036-deparse-support-CREATE-SERVER.patchtext/x-diff; charset=us-asciiDownload
>From 323b7e0a07a5467e7f8a6fe54769392eabbb639b Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:37:57 -0300
Subject: [PATCH 36/42] deparse: support CREATE SERVER

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index cd600ff..c4ec7c0 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1748,6 +1748,45 @@ deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
 	return stmt;
 }
 
+static ObjTree *
+deparse_CreateForeignServerStmt(Oid objectId, Node *parsetree)
+{
+	CreateForeignServerStmt *node = (CreateForeignServerStmt *) parsetree;
+	ObjTree	   *createServer;
+	ObjTree	   *tmp;
+
+	createServer = new_objtree_VA("CREATE SERVER %{identity}I %{type}s %{version}s "
+								  "FOREIGN DATA WRAPPER %{fdw}I %{options}s", 2,
+								  "identity", ObjTypeString, node->servername,
+								  "fdw",  ObjTypeString, node->fdwname);
+
+	/* add a TYPE clause, if any */
+	tmp = new_objtree_VA("TYPE %{type}L", 0);
+	if (node->servertype)
+		append_string_object(tmp, "type", node->servertype);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "type", tmp);
+
+	/* add a VERSION clause, if any */
+	tmp = new_objtree_VA("VERSION %{version}L", 0);
+	if (node->version)
+		append_string_object(tmp, "version", node->version);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "version", tmp);
+
+	/* add an OPTIONS clause, if any */
+	tmp = new_objtree_VA("OPTIONS (%{option:, }s)", 0);
+	if (node->options)
+		elog(ERROR, "unimplemented deparse of CREATE SERVER ... OPTIONS");
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "options", tmp);
+
+	return createServer;
+}
+
 /*
  * deparse_ViewStmt
  *		deparse a ViewStmt
@@ -5436,7 +5475,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateForeignServerStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateForeignServerStmt(objectId, parsetree);
 			break;
 
 		case T_AlterForeignServerStmt:
-- 
2.1.4

0037-deparse-support-REFRESH-MATERIALIZED-VIEW.patchtext/x-diff; charset=us-asciiDownload
>From 2ee2dad54fc9489c5ab5371d4e27cb6b23dacf06 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:38:28 -0300
Subject: [PATCH 37/42] deparse: support REFRESH MATERIALIZED VIEW

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index c4ec7c0..71859ec 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2021,6 +2021,31 @@ deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
 	return trigger;
 }
 
+static ObjTree *
+deparse_RefreshMatViewStmt(Oid objectId, Node *parsetree)
+{
+	RefreshMatViewStmt *node = (RefreshMatViewStmt *) parsetree;
+	ObjTree	   *refreshStmt;
+	ObjTree	   *tmp;
+
+	refreshStmt = new_objtree_VA("REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D "
+								 "%{with_no_data}s", 0);
+	/* add a CONCURRENTLY clause */
+	append_string_object(refreshStmt, "concurrently",
+						 node->concurrent ? "CONCURRENTLY" : "");
+	/* add the matview name */
+	append_object_object(refreshStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->skipData ? true : false);
+	append_object_object(refreshStmt, "with_no_data", tmp);
+
+	return refreshStmt;
+}
+
 /*
  * deparse_ColumnDef
  *		Subroutine for CREATE TABLE deparsing
@@ -5545,7 +5570,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RefreshMatViewStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RefreshMatViewStmt(objectId, parsetree);
 			break;
 
 		case T_CreateTrigStmt:
-- 
2.1.4

deparse_init.sqlapplication/x-sqlDownload
#2Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#1)
Re: deparsing utility commands

Hi,

On 2015-02-15 01:48:15 -0300, Alvaro Herrera wrote:

This is a repost of the patch to add CREATE command deparsing support to
event triggers. It now supports not only CREATE but also ALTER and
other commands such as GRANT/REVOKE, COMMENT ON and SECURITY LABEL.
This patch series is broken up in a rather large number of patches, but
my intention would be to commit separately only the first 6 patches; the
remaining parts are split up only so that it's easy to see how deparsing
each patch is implemented, and would be committed as a single changeset
(but before that I need a bunch of extra fixes and additions to other
parts.)

I think you should just go ahead and commit 0001, 0002, 0006. Those have
previously been discussed and seem like a good idea and uncontroversial
even if the rest of the series doesn't go in.

I think the GRANT/REVOKE, COMMENT ON and SECURITY LABEL changes are good
independently as well, but there previously have been raised some
concerns about shared objects. I think the answer in the patches which
is to raise events when affecting database local objects makes sense,
but others might disagree.

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

#3Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#1)
Re: deparsing utility commands

Alvaro,

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

This is a repost of the patch to add CREATE command deparsing support to
event triggers. It now supports not only CREATE but also ALTER and
other commands such as GRANT/REVOKE, COMMENT ON and SECURITY LABEL.
This patch series is broken up in a rather large number of patches, but
my intention would be to commit separately only the first 6 patches; the
remaining parts are split up only so that it's easy to see how deparsing
each patch is implemented, and would be committed as a single changeset
(but before that I need a bunch of extra fixes and additions to other
parts.)

I've started taking a look at this as the pgaudit bits depend on it and
noticed that the patch set implies there's 42 patches, but there were
only 37 attached..?

Thanks!

Stephen

#4Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#3)
Re: deparsing utility commands

Stephen Frost wrote:

I've started taking a look at this as the pgaudit bits depend on it and
noticed that the patch set implies there's 42 patches, but there were
only 37 attached..?

Ah, I didn't realize when I posted that the subject was counting those
extra patches. They are later patches that add the testing framework,
but since the tests don't pass currently, they are not usable yet.
Mostly they are about the running deparse_init.sql file that I posted
separately. I will post a real patch for that stuff later today to make
it clear what it is that we're talking about.

FWIW, one of Robert's main objections is that future hackers will forget
to add deparse support for new commands as they are added. In an
attempt to get this sorted out, I have modified the stuff in
ProcessUtilitySlow() so that instead of having one
EventTriggerStashCommand call for each node type, there is only one call
at the end of the function. That way, new cases in the big switch there
will automatically get something added to the stash, which should make
the test fail appropriately. (Things like adding a new clause to
existing commands will be tested by running pg_dump on the databases and
comparing.)

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#5Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#4)
Re: deparsing utility commands

Alvaro,

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Stephen Frost wrote:

I've started taking a look at this as the pgaudit bits depend on it and
noticed that the patch set implies there's 42 patches, but there were
only 37 attached..?

Ah, I didn't realize when I posted that the subject was counting those
extra patches. They are later patches that add the testing framework,
but since the tests don't pass currently, they are not usable yet.
Mostly they are about the running deparse_init.sql file that I posted
separately. I will post a real patch for that stuff later today to make
it clear what it is that we're talking about.

Oh, ok, no problem, just wanted to make sure things weren't missing.

FWIW, one of Robert's main objections is that future hackers will forget
to add deparse support for new commands as they are added. In an
attempt to get this sorted out, I have modified the stuff in
ProcessUtilitySlow() so that instead of having one
EventTriggerStashCommand call for each node type, there is only one call
at the end of the function. That way, new cases in the big switch there
will automatically get something added to the stash, which should make
the test fail appropriately. (Things like adding a new clause to
existing commands will be tested by running pg_dump on the databases and
comparing.)

Right, I've been following the discussion and fully agree with Robert
that we really need a way to make sure that future work doesn't forget
to address deparseing (or the various other bits, object classes, etc,
really). The approach you've outlined sounds interesting but I haven't
yet gotten to that bit of the code.

Thanks!

Stephen

#6Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andres Freund (#2)
Re: deparsing utility commands

Andres Freund wrote:

Hi,

On 2015-02-15 01:48:15 -0300, Alvaro Herrera wrote:

This is a repost of the patch to add CREATE command deparsing support to
event triggers. It now supports not only CREATE but also ALTER and
other commands such as GRANT/REVOKE, COMMENT ON and SECURITY LABEL.
This patch series is broken up in a rather large number of patches, but
my intention would be to commit separately only the first 6 patches; the
remaining parts are split up only so that it's easy to see how deparsing
each patch is implemented, and would be committed as a single changeset
(but before that I need a bunch of extra fixes and additions to other
parts.)

I think you should just go ahead and commit 0001, 0002, 0006. Those have
previously been discussed and seem like a good idea and uncontroversial
even if the rest of the series doesn't go in.

I have pushed 0001 (changed pg_identify_object for opclasses and
opfamilies to use USING instead of FOR) to master only, and backpatched
a fix for pg_conversion object identities, which were missing
schema-qualification.

Patch 0002 I think is good to go as well, AFAICT (have the various
RENAME commands return the OID and attnum of affected objects).

On 0006 (which is about having ALTER TABLE return the OID/attnum of the
affected object on each subcommand), I have a problem about the ALTER
TABLE ALTER COLUMN SET DATA TYPE USING subcommand. The problem with
that is that in order to fully deparse that subcommand we need to
deparse the expression of the USING clause. But in the parse node we
only have info about the untransformed expression, so it is not possible
to pass it through ruleutils directly; it needs to be run by
transformExpr() first. Doing that in the deparse code seems the wrong
solution, so I am toying with the idea of adding a "Node *" output
parameter for some ATExec* routines; the Prep step would insert the
transformed expression there, so that it is available at the deparse
stage. As far as I know, only the SET DATA TYPE USING form has this
issue: for other subcommands, the current code is simply grabbing the
expression from catalogs. Maybe it would be good to use that Node in
those cases as well and avoid catalog lookups, not sure.

I think the GRANT/REVOKE, COMMENT ON and SECURITY LABEL changes are good
independently as well, but there previously have been raised some
concerns about shared objects. I think the answer in the patches which
is to raise events when affecting database local objects makes sense,
but others might disagree.

Yes, I will push these unless somebody objects soon, as they seem
perfectly reasonable to me. The only troubling thing is that there is
no regression test for this kind of thing in event triggers (i.e. verify
which command tags get support and which don't), which seems odd to me.
Not these patches's fault, though, so I'm not considering adding any ATM.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#7Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#6)
Re: deparsing utility commands

Alvaro,

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Andres Freund wrote:

On 2015-02-15 01:48:15 -0300, Alvaro Herrera wrote:
I think you should just go ahead and commit 0001, 0002, 0006. Those have
previously been discussed and seem like a good idea and uncontroversial
even if the rest of the series doesn't go in.

I have pushed 0001 (changed pg_identify_object for opclasses and
opfamilies to use USING instead of FOR) to master only, and backpatched
a fix for pg_conversion object identities, which were missing
schema-qualification.

That looked fine to me.

Patch 0002 I think is good to go as well, AFAICT (have the various
RENAME commands return the OID and attnum of affected objects).

It's not a huge complaint, but it feels a bit awkward to me that
ExecRenameStmt is now returning one item and using an out variable for
the other when the two really go together (Oid and Object Sub ID, that
is). Further, the comment above ExecRenameStmt should make it clear
that it's safe to pass NULL into objsubid if you don't care about it.

The same probably goes for the COMMENT bits.

On 0006 (which is about having ALTER TABLE return the OID/attnum of the
affected object on each subcommand), I have a problem about the ALTER
TABLE ALTER COLUMN SET DATA TYPE USING subcommand. The problem with
that is that in order to fully deparse that subcommand we need to
deparse the expression of the USING clause. But in the parse node we
only have info about the untransformed expression, so it is not possible
to pass it through ruleutils directly; it needs to be run by
transformExpr() first. Doing that in the deparse code seems the wrong
solution, so I am toying with the idea of adding a "Node *" output
parameter for some ATExec* routines; the Prep step would insert the
transformed expression there, so that it is available at the deparse
stage. As far as I know, only the SET DATA TYPE USING form has this
issue: for other subcommands, the current code is simply grabbing the
expression from catalogs. Maybe it would be good to use that Node in
those cases as well and avoid catalog lookups, not sure.

I agree- I'm pretty sure we definitely don't want to run through
transformExpr again in the deparse code. I'm not a huge fan of adding a
Node* output parameter, but I havn't got any other great ideas about how
to address that.

I think the GRANT/REVOKE, COMMENT ON and SECURITY LABEL changes are good
independently as well, but there previously have been raised some
concerns about shared objects. I think the answer in the patches which
is to raise events when affecting database local objects makes sense,
but others might disagree.

Yes, I will push these unless somebody objects soon, as they seem
perfectly reasonable to me. The only troubling thing is that there is
no regression test for this kind of thing in event triggers (i.e. verify
which command tags get support and which don't), which seems odd to me.
Not these patches's fault, though, so I'm not considering adding any ATM.

Ugh. I dislike that when we say an event trigger will fire before
'GRANT' what we really mean is "GRANT when it's operating against a
local object". At the minimum we absolutely need to be very clear in
the documentation about that limitation. Perhaps there is something
already which reflects that, but I don't see anything in the patch
being added about that.

Still looking at the rest of the patches.

Thanks!

Stephen

#8Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#7)
Re: deparsing utility commands

Stephen,

Stephen Frost wrote:

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Patch 0002 I think is good to go as well, AFAICT (have the various
RENAME commands return the OID and attnum of affected objects).

It's not a huge complaint, but it feels a bit awkward to me that
ExecRenameStmt is now returning one item and using an out variable for
the other when the two really go together (Oid and Object Sub ID, that
is). Further, the comment above ExecRenameStmt should make it clear
that it's safe to pass NULL into objsubid if you don't care about it.

The same probably goes for the COMMENT bits.

Hmm, while I agree that it's a relatively minor point, it seems a fair
one. I think we could handle this by returning ObjectAddress rather
than Oid in ExecRenameStmt() and CommentObject(); then you have all the
bits you need in a single place. Furthermore, the function in another
patch EventTriggerStashCommand() instead of getting separately (ObjType,
objectId, objectSubId) could take a single argument of type
ObjectAddress.

Now, we probably don't want to hack *all* the utility commands to return
ObjectAddress instead of OID, because it many cases that's just not
going to be convenient (not to speak of the code churn); so I think for
most objtypes the ProcessUtilitySlow stanza would look like this:

case T_AlterTSConfigurationStmt:
objectId = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
objectType = OBJECT_TSCONFIGURATION;
break;

For ExecRenameStmt and CommentObject (and probably other cases such as
security labels) the stanza in ProcessUtilitySlow would be simpler:

case T_CommentStmt:
address = CommentObject((CommentStmt *) parsetree);
break;

and at the bottom of the loop we would transform the objid/type into
address for the cases that need it:

if (!commandStashed)
{
if (objectId != InvalidOid)
{
address.classId = get_objtype_catalog_oid(objectType);
address.objectId = objectId;
address.objectSubId = 0;
}
EventTriggerStashCommand(address, secondaryOid, parsetree);
}

On 0006 (which is about having ALTER TABLE return the OID/attnum of the
affected object on each subcommand), I have a problem about the ALTER
TABLE ALTER COLUMN SET DATA TYPE USING subcommand. The problem with
that is that in order to fully deparse that subcommand we need to
deparse the expression of the USING clause. But in the parse node we
only have info about the untransformed expression, so it is not possible
to pass it through ruleutils directly; it needs to be run by
transformExpr() first.

I agree- I'm pretty sure we definitely don't want to run through
transformExpr again in the deparse code. I'm not a huge fan of adding a
Node* output parameter, but I havn't got any other great ideas about how
to address that.

Yeah, my thoughts exactly :-(

I think the GRANT/REVOKE, COMMENT ON and SECURITY LABEL changes are good
independently as well, but there previously have been raised some
concerns about shared objects. I think the answer in the patches which
is to raise events when affecting database local objects makes sense,
but others might disagree.

Yes, I will push these unless somebody objects soon, as they seem
perfectly reasonable to me. The only troubling thing is that there is
no regression test for this kind of thing in event triggers (i.e. verify
which command tags get support and which don't), which seems odd to me.
Not these patches's fault, though, so I'm not considering adding any ATM.

Ugh. I dislike that when we say an event trigger will fire before
'GRANT' what we really mean is "GRANT when it's operating against a
local object". At the minimum we absolutely need to be very clear in
the documentation about that limitation. Perhaps there is something
already which reflects that, but I don't see anything in the patch
being added about that.

Hmm, good point, will give this some thought. I'm thinking perhaps we
can add a table of which object types are supported for generic commands
such as GRANT, COMMENT and SECURITY LABEL.

Still looking at the rest of the patches.

Great, thanks -- and thanks for the review so far.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#9Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#8)
Re: deparsing utility commands

Alvaro,

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Stephen Frost wrote:

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Patch 0002 I think is good to go as well, AFAICT (have the various
RENAME commands return the OID and attnum of affected objects).

It's not a huge complaint, but it feels a bit awkward to me that
ExecRenameStmt is now returning one item and using an out variable for
the other when the two really go together (Oid and Object Sub ID, that
is). Further, the comment above ExecRenameStmt should make it clear
that it's safe to pass NULL into objsubid if you don't care about it.

The same probably goes for the COMMENT bits.

Hmm, while I agree that it's a relatively minor point, it seems a fair
one. I think we could handle this by returning ObjectAddress rather
than Oid in ExecRenameStmt() and CommentObject(); then you have all the
bits you need in a single place. Furthermore, the function in another
patch EventTriggerStashCommand() instead of getting separately (ObjType,
objectId, objectSubId) could take a single argument of type
ObjectAddress.

Agreed, that thought occured to me as well while I was looking through
the other deparse patches and I agree that it makes sense.

Now, we probably don't want to hack *all* the utility commands to return
ObjectAddress instead of OID, because it many cases that's just not
going to be convenient (not to speak of the code churn); so I think for
most objtypes the ProcessUtilitySlow stanza would look like this:

case T_AlterTSConfigurationStmt:
objectId = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
objectType = OBJECT_TSCONFIGURATION;
break;

For ExecRenameStmt and CommentObject (and probably other cases such as
security labels) the stanza in ProcessUtilitySlow would be simpler:

case T_CommentStmt:
address = CommentObject((CommentStmt *) parsetree);
break;

and at the bottom of the loop we would transform the objid/type into
address for the cases that need it:

if (!commandStashed)
{
if (objectId != InvalidOid)
{
address.classId = get_objtype_catalog_oid(objectType);
address.objectId = objectId;
address.objectSubId = 0;
}
EventTriggerStashCommand(address, secondaryOid, parsetree);
}

That'd be fine with me, though for my 2c, I wouldn't object to changing
them all to return ObjectAddress either. I agree that it'd cause a fair
bit of code churn to do so, but there's a fair bit of code churn
happening here anyway (looking at what 0008 does to ProcessUtilitySlow
anyway).

Yes, I will push these unless somebody objects soon, as they seem
perfectly reasonable to me. The only troubling thing is that there is
no regression test for this kind of thing in event triggers (i.e. verify
which command tags get support and which don't), which seems odd to me.
Not these patches's fault, though, so I'm not considering adding any ATM.

Ugh. I dislike that when we say an event trigger will fire before
'GRANT' what we really mean is "GRANT when it's operating against a
local object". At the minimum we absolutely need to be very clear in
the documentation about that limitation. Perhaps there is something
already which reflects that, but I don't see anything in the patch
being added about that.

Hmm, good point, will give this some thought. I'm thinking perhaps we
can add a table of which object types are supported for generic commands
such as GRANT, COMMENT and SECURITY LABEL.

That sounds like a good idea.

Thanks!

Stephen

#10Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#9)
Re: deparsing utility commands

Now, we probably don't want to hack *all* the utility commands to return
ObjectAddress instead of OID, because it many cases that's just not
going to be convenient (not to speak of the code churn); so I think for
most objtypes the ProcessUtilitySlow stanza would look like this:

That'd be fine with me, though for my 2c, I wouldn't object to changing
them all to return ObjectAddress either. I agree that it'd cause a fair
bit of code churn to do so, but there's a fair bit of code churn
happening here anyway (looking at what 0008 does to ProcessUtilitySlow
anyway).

Well, that would make my life easier I think (even if it's a bit more
work), so unless there are objections I will do things this way. It's a
bit of a pity that Robert and Dimitri went to huge lengths to have these
functions return OID and we're now changing it all to ObjAddress
instead, but oh well.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#11Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#10)
Re: deparsing utility commands

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Now, we probably don't want to hack *all* the utility commands to return
ObjectAddress instead of OID, because it many cases that's just not
going to be convenient (not to speak of the code churn); so I think for
most objtypes the ProcessUtilitySlow stanza would look like this:

That'd be fine with me, though for my 2c, I wouldn't object to changing
them all to return ObjectAddress either. I agree that it'd cause a fair
bit of code churn to do so, but there's a fair bit of code churn
happening here anyway (looking at what 0008 does to ProcessUtilitySlow
anyway).

Well, that would make my life easier I think (even if it's a bit more
work), so unless there are objections I will do things this way. It's a
bit of a pity that Robert and Dimitri went to huge lengths to have these
functions return OID and we're now changing it all to ObjAddress
instead, but oh well.

Not sure that I see it as that huge a deal.. They're still returning an
Oid, it's just embedded in the ObjAddress to provide a complete
statement of what the object is.

btw, the hunk in 0026 which adds a 'break;' into standard_ProcessUtility
caught me by surprise. Looks like that 'break;' was missing from 0003
(for T_GrantStmt).

Thanks,

Stephen

#12Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#11)
1 attachment(s)
Re: deparsing utility commands

Stephen Frost wrote:

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Now, we probably don't want to hack *all* the utility commands to return
ObjectAddress instead of OID, because it many cases that's just not
going to be convenient (not to speak of the code churn); so I think for
most objtypes the ProcessUtilitySlow stanza would look like this:

That'd be fine with me, though for my 2c, I wouldn't object to changing
them all to return ObjectAddress either. I agree that it'd cause a fair
bit of code churn to do so, but there's a fair bit of code churn
happening here anyway (looking at what 0008 does to ProcessUtilitySlow
anyway).

Well, that would make my life easier I think (even if it's a bit more
work), so unless there are objections I will do things this way. It's a
bit of a pity that Robert and Dimitri went to huge lengths to have these
functions return OID and we're now changing it all to ObjAddress
instead, but oh well.

Not sure that I see it as that huge a deal.. They're still returning an
Oid, it's just embedded in the ObjAddress to provide a complete
statement of what the object is.

I've been looking at this idea some more. There's some churn, but it's
not that bad. For instance, here's the patch to have RENAME return
ObjectAddress rather than OIDs.

For instance, the get_objtype_catalog_id() function, provided in a later
patch in the series, will no longer be necessary; instead, each
execution code path in the src/backend/command code must set the correct
catalog ID in the ObjectAddress it returns. So the event triggers code
will have the catalog ID at the ready, without having to figure it out
from the ObjectType it gets from the parse node.

btw, the hunk in 0026 which adds a 'break;' into standard_ProcessUtility
caught me by surprise. Looks like that 'break;' was missing from 0003
(for T_GrantStmt).

Thanks for pointing this out -- that was broken rebase.

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

Attachments:

0001-Have-RENAME-routines-return-ObjectAddress-rather-tha.patchtext/x-diff; charset=us-asciiDownload
>From 69d0b62787c2a2a514f70247ccde98c4c43d70a0 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 19 Feb 2015 14:38:09 -0300
Subject: [PATCH] Have RENAME routines return ObjectAddress rather than OID

This lets them include an objectSubId when appropriate (i.e. when
renaming relation columns and composite type attributes), and
additionally they make the catalog OID available for further processing.
The object OID that was previously returned is still available in the
objectId field of the returned ObjectAddress.

This isn't terribly exciting in itself but it supports future event
trigger changes.

Discussion: 20150218213255.GC6717@tamriel.snowman.net
Reviewed-By: Stephen Frost
---
 src/backend/catalog/dependency.c    |  6 ++-
 src/backend/catalog/objectaddress.c |  6 +++
 src/backend/commands/alter.c        | 14 +++++--
 src/backend/commands/dbcommands.c   |  7 +++-
 src/backend/commands/policy.c       |  7 +++-
 src/backend/commands/schemacmds.c   |  7 +++-
 src/backend/commands/tablecmds.c    | 77 +++++++++++++++++++++++++++++--------
 src/backend/commands/tablespace.c   |  7 +++-
 src/backend/commands/trigger.c      |  7 +++-
 src/backend/commands/typecmds.c     |  6 ++-
 src/backend/commands/user.c         |  7 +++-
 src/backend/rewrite/rewriteDefine.c |  7 +++-
 src/include/catalog/objectaddress.h | 16 ++++++++
 src/include/commands/alter.h        |  3 +-
 src/include/commands/dbcommands.h   |  3 +-
 src/include/commands/policy.h       |  2 +-
 src/include/commands/schemacmds.h   |  2 +-
 src/include/commands/tablecmds.h    |  9 +++--
 src/include/commands/tablespace.h   |  3 +-
 src/include/commands/trigger.h      |  3 +-
 src/include/commands/typecmds.h     |  2 +-
 src/include/commands/user.h         |  2 +-
 src/include/rewrite/rewriteDefine.h |  3 +-
 23 files changed, 155 insertions(+), 51 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index bacb242..25ff326 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2269,8 +2269,9 @@ free_object_addresses(ObjectAddresses *addrs)
 ObjectClass
 getObjectClass(const ObjectAddress *object)
 {
-	/* only pg_class entries can have nonzero objectSubId */
-	if (object->classId != RelationRelationId &&
+	/* only pg_class and pg_type entries can have nonzero objectSubId */
+	if ((object->classId != RelationRelationId &&
+		 object->classId != TypeRelationId) &&
 		object->objectSubId != 0)
 		elog(ERROR, "invalid non-zero objectSubId for object class %u",
 			 object->classId);
@@ -2285,6 +2286,7 @@ getObjectClass(const ObjectAddress *object)
 			return OCLASS_PROC;
 
 		case TypeRelationId:
+			/* caller must check objectSubId */
 			return OCLASS_TYPE;
 
 		case CastRelationId:
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d899dd7..2bbc15d 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -531,6 +531,12 @@ ObjectTypeMap[] =
 	{ "policy", OBJECT_POLICY }
 };
 
+ObjectAddress InvalidObjectAddress =
+{
+	InvalidOid,
+	InvalidOid,
+	0
+};
 
 static ObjectAddress get_object_address_unqualified(ObjectType objtype,
 							   List *qualname, bool missing_ok);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 78b54b4..b2fb7b9 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -299,10 +299,14 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 /*
  * Executes an ALTER OBJECT / RENAME TO statement.  Based on the object
  * type, the function appropriate to that type is executed.
+ *
+ * Return value is the address of the renamed object.
  */
-Oid
+ObjectAddress
 ExecRenameStmt(RenameStmt *stmt)
 {
+	ObjectAddress	address;
+
 	switch (stmt->renameType)
 	{
 		case OBJECT_TABCONSTRAINT:
@@ -330,9 +334,11 @@ ExecRenameStmt(RenameStmt *stmt)
 			return RenameRelation(stmt);
 
 		case OBJECT_COLUMN:
-		case OBJECT_ATTRIBUTE:
 			return renameatt(stmt);
 
+		case OBJECT_ATTRIBUTE:
+			return renameatt_type(stmt);
+
 		case OBJECT_RULE:
 			return RenameRewriteRule(stmt->relation, stmt->subname,
 									 stmt->newname);
@@ -378,13 +384,13 @@ ExecRenameStmt(RenameStmt *stmt)
 										   stmt->newname);
 				heap_close(catalog, RowExclusiveLock);
 
-				return address.objectId;
+				return address;
 			}
 
 		default:
 			elog(ERROR, "unrecognized rename stmt type: %d",
 				 (int) stmt->renameType);
-			return InvalidOid;	/* keep compiler happy */
+			return InvalidObjectAddress;	/* keep compiler happy */
 	}
 }
 
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 5e66961..f8dde05 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -938,7 +938,7 @@ dropdb(const char *dbname, bool missing_ok)
 /*
  * Rename database
  */
-Oid
+ObjectAddress
 RenameDatabase(const char *oldname, const char *newname)
 {
 	Oid			db_id;
@@ -946,6 +946,7 @@ RenameDatabase(const char *oldname, const char *newname)
 	Relation	rel;
 	int			notherbackends;
 	int			npreparedxacts;
+	ObjectAddress address;
 
 	/*
 	 * Look up the target database's OID, and get exclusive lock on it. We
@@ -1013,12 +1014,14 @@ RenameDatabase(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(DatabaseRelationId, db_id, 0);
 
+	initObjectAddress(address, DatabaseRelationId, db_id);
+
 	/*
 	 * Close pg_database, but keep lock till commit.
 	 */
 	heap_close(rel, NoLock);
 
-	return db_id;
+	return address;
 }
 
 
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d98da0d..65ecf8f 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -837,7 +837,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
  * rename_policy -
  *   change the name of a policy on a relation
  */
-Oid
+ObjectAddress
 rename_policy(RenameStmt *stmt)
 {
 	Relation		pg_policy_rel;
@@ -847,6 +847,7 @@ rename_policy(RenameStmt *stmt)
 	ScanKeyData		skey[2];
 	SysScanDesc		sscan;
 	HeapTuple		policy_tuple;
+	ObjectAddress	address;
 
 	/* Get id of table.  Also handles permissions checks. */
 	table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
@@ -925,6 +926,8 @@ rename_policy(RenameStmt *stmt)
 	InvokeObjectPostAlterHook(PolicyRelationId,
 							  HeapTupleGetOid(policy_tuple), 0);
 
+	initObjectAddress(address, PolicyRelationId, opolid);
+
 	/*
 	 * Invalidate relation's relcache entry so that other backends (and
 	 * this one too!) are sent SI message to make them rebuild relcache
@@ -937,7 +940,7 @@ rename_policy(RenameStmt *stmt)
 	heap_close(pg_policy_rel, RowExclusiveLock);
 	relation_close(target_table, NoLock);
 
-	return opoloid;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index a44dbf4..f738f48 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -195,13 +195,14 @@ RemoveSchemaById(Oid schemaOid)
 /*
  * Rename schema
  */
-Oid
+ObjectAddress
 RenameSchema(const char *oldname, const char *newname)
 {
 	Oid			nspOid;
 	HeapTuple	tup;
 	Relation	rel;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	rel = heap_open(NamespaceRelationId, RowExclusiveLock);
 
@@ -243,10 +244,12 @@ RenameSchema(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(NamespaceRelationId, HeapTupleGetOid(tup), 0);
 
+	initObjectAddress(address, NamespaceRelationId, nspOid);
+
 	heap_close(rel, NoLock);
 	heap_freetuple(tup);
 
-	return nspOid;
+	return address;
 }
 
 void
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 66d5083..c07a689 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2155,8 +2155,10 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 
 /*
  *		renameatt_internal		- workhorse for renameatt
+ *
+ * Return value is the column number of the attribute in the 'myrelid' relation.
  */
-static void
+static int
 renameatt_internal(Oid myrelid,
 				   const char *oldattname,
 				   const char *newattname,
@@ -2297,6 +2299,8 @@ renameatt_internal(Oid myrelid,
 	heap_close(attrelation, RowExclusiveLock);
 
 	relation_close(targetrelation, NoLock);		/* close rel but keep lock */
+
+	return attnum;
 }
 
 /*
@@ -2320,10 +2324,12 @@ RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid,
 /*
  *		renameatt		- changes the name of a attribute in a relation
  */
-Oid
+ObjectAddress
 renameatt(RenameStmt *stmt)
 {
 	Oid			relid;
+	AttrNumber	attnum;
+	ObjectAddress address;
 
 	/* lock level taken here should match renameatt_internal */
 	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
@@ -2336,26 +2342,57 @@ renameatt(RenameStmt *stmt)
 		ereport(NOTICE,
 				(errmsg("relation \"%s\" does not exist, skipping",
 						stmt->relation->relname)));
-		return InvalidOid;
+		return InvalidObjectAddress;
 	}
 
-	renameatt_internal(relid,
-					   stmt->subname,	/* old att name */
-					   stmt->newname,	/* new att name */
-					   interpretInhOption(stmt->relation->inhOpt),		/* recursive? */
-					   false,	/* recursing? */
-					   0,		/* expected inhcount */
-					   stmt->behavior);
+	attnum =
+		renameatt_internal(relid,
+						   stmt->subname,	/* old att name */
+						   stmt->newname,	/* new att name */
+						   interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+						   false,	/* recursing? */
+						   0,		/* expected inhcount */
+						   stmt->behavior);
+
+	initObjectAddressSub(RelationRelationId relid, attnum);
 
 	/* This is an ALTER TABLE command so it's about the relid */
-	return relid;
+	return address;
 }
 
+/*
+ * As above, for composite types; returned ObjectAddress is that of the pg_type
+ * tuple, not the pg_class entry.
+ */
+ObjectAddress
+renameatt_type(RenameStmt *stmt)
+{
+	HeapTuple classTup;
+	ObjectAddress address;
+
+	/*
+	 * renameatt sets the address to the pg_class object; change it to
+	 * pg_type.
+	 */
+	address = renameatt(stmt);
+
+	classTup = SearchSysCache1(RELOID, address.objectId);
+	if (!HeapTupleIsValid(classTup))
+		elog(ERROR, "cache lookup failed for relation %u", address.objectId);
+
+	/* no initObjectAddress here, to avoid objectSubId clobber */
+	address.classId = TypeRelationId;
+	address.objectId = ((Form_pg_class) GETSTRUCT(classTup))->reltype;
+
+	ReleaseSysCache(classTup);
+
+	return address;
+}
 
 /*
  * same logic as renameatt_internal
  */
-static Oid
+static ObjectAddress
 rename_constraint_internal(Oid myrelid,
 						   Oid mytypid,
 						   const char *oldconname,
@@ -2368,6 +2405,7 @@ rename_constraint_internal(Oid myrelid,
 	Oid			constraintOid;
 	HeapTuple	tuple;
 	Form_pg_constraint con;
+	ObjectAddress	address;
 
 	AssertArg(!myrelid || !mytypid);
 
@@ -2443,15 +2481,17 @@ rename_constraint_internal(Oid myrelid,
 	else
 		RenameConstraintById(constraintOid, newconname);
 
+	initObjectAddress(address, ConstraintRelationId, constraintOid);
+
 	ReleaseSysCache(tuple);
 
 	if (targetrelation)
 		relation_close(targetrelation, NoLock); /* close rel but keep lock */
 
-	return constraintOid;
+	return address;
 }
 
-Oid
+ObjectAddress
 RenameConstraint(RenameStmt *stmt)
 {
 	Oid			relid = InvalidOid;
@@ -2494,10 +2534,11 @@ RenameConstraint(RenameStmt *stmt)
  * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE
  * RENAME
  */
-Oid
+ObjectAddress
 RenameRelation(RenameStmt *stmt)
 {
 	Oid			relid;
+	ObjectAddress address = {InvalidOid, InvalidOid, 0};
 
 	/*
 	 * Grab an exclusive lock on the target table, index, sequence, view,
@@ -2517,13 +2558,15 @@ RenameRelation(RenameStmt *stmt)
 		ereport(NOTICE,
 				(errmsg("relation \"%s\" does not exist, skipping",
 						stmt->relation->relname)));
-		return InvalidOid;
+		return address;
 	}
 
 	/* Do the work */
 	RenameRelationInternal(relid, stmt->newname, false);
 
-	return relid;
+	initObjectAddress(RelationRelationId, relid);
+
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index e098b9f..faf6197 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -846,7 +846,7 @@ directory_is_empty(const char *path)
 /*
  * Rename a tablespace
  */
-Oid
+ObjectAddress
 RenameTableSpace(const char *oldname, const char *newname)
 {
 	Oid			tspId;
@@ -856,6 +856,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	HeapTuple	tup;
 	HeapTuple	newtuple;
 	Form_pg_tablespace newform;
+	ObjectAddress address;
 
 	/* Search pg_tablespace */
 	rel = heap_open(TableSpaceRelationId, RowExclusiveLock);
@@ -912,9 +913,11 @@ RenameTableSpace(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(TableSpaceRelationId, tspId, 0);
 
+	initObjectAddress(TableSpaceRelationId, tspId);
+
 	heap_close(rel, NoLock);
 
-	return tspId;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 5c1c1be..ae2ed73 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1249,7 +1249,7 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
  *		modify tgname in trigger tuple
  *		update row in catalog
  */
-Oid
+ObjectAddress
 renametrig(RenameStmt *stmt)
 {
 	Oid			tgoid;
@@ -1259,6 +1259,7 @@ renametrig(RenameStmt *stmt)
 	SysScanDesc tgscan;
 	ScanKeyData key[2];
 	Oid			relid;
+	ObjectAddress address;
 
 	/*
 	 * Look up name, check permissions, and acquire lock (which we will NOT
@@ -1351,6 +1352,8 @@ renametrig(RenameStmt *stmt)
 						stmt->subname, RelationGetRelationName(targetrel))));
 	}
 
+	initObjectAddress(TriggerRelationId, tgoid);
+
 	systable_endscan(tgscan);
 
 	heap_close(tgrel, RowExclusiveLock);
@@ -1360,7 +1363,7 @@ renametrig(RenameStmt *stmt)
 	 */
 	relation_close(targetrel, NoLock);
 
-	return tgoid;
+	return address;
 }
 
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index b77e1b4..c786a43 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3205,7 +3205,7 @@ GetDomainConstraints(Oid typeOid)
 /*
  * Execute ALTER TYPE RENAME
  */
-Oid
+ObjectAddress
 RenameType(RenameStmt *stmt)
 {
 	List	   *names = stmt->object;
@@ -3215,6 +3215,7 @@ RenameType(RenameStmt *stmt)
 	Relation	rel;
 	HeapTuple	tup;
 	Form_pg_type typTup;
+	ObjectAddress address;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -3272,10 +3273,11 @@ RenameType(RenameStmt *stmt)
 		RenameTypeInternal(typeOid, newTypeName,
 						   typTup->typnamespace);
 
+	initObjectAddress(TypeRelationId, typeOid);
 	/* Clean up */
 	heap_close(rel, RowExclusiveLock);
 
-	return typeOid;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 2210eed..0d9aa40 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1114,7 +1114,7 @@ DropRole(DropRoleStmt *stmt)
 /*
  * Rename role
  */
-Oid
+ObjectAddress
 RenameRole(const char *oldname, const char *newname)
 {
 	HeapTuple	oldtuple,
@@ -1128,6 +1128,7 @@ RenameRole(const char *oldname, const char *newname)
 	bool		repl_repl[Natts_pg_authid];
 	int			i;
 	Oid			roleid;
+	ObjectAddress address;
 
 	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
 	dsc = RelationGetDescr(rel);
@@ -1216,6 +1217,8 @@ RenameRole(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
 
+	initObjectAddress(AuthIdRelationId, roleid);
+
 	ReleaseSysCache(oldtuple);
 
 	/*
@@ -1223,7 +1226,7 @@ RenameRole(const char *oldname, const char *newname)
 	 */
 	heap_close(rel, NoLock);
 
-	return roleid;
+	return address;
 }
 
 /*
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index eab4d73..314fe09 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -897,7 +897,7 @@ RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
 /*
  * Rename an existing rewrite rule.
  */
-Oid
+ObjectAddress
 RenameRewriteRule(RangeVar *relation, const char *oldName,
 				  const char *newName)
 {
@@ -907,6 +907,7 @@ RenameRewriteRule(RangeVar *relation, const char *oldName,
 	HeapTuple	ruletup;
 	Form_pg_rewrite ruleform;
 	Oid			ruleOid;
+	ObjectAddress address;
 
 	/*
 	 * Look up name, check permissions, and acquire lock (which we will NOT
@@ -969,10 +970,12 @@ RenameRewriteRule(RangeVar *relation, const char *oldName,
 	 */
 	CacheInvalidateRelcache(targetrel);
 
+	initObjectAddress(RewriteRelationId, ruleOid);
+
 	/*
 	 * Close rel, but keep exclusive lock!
 	 */
 	relation_close(targetrel, NoLock);
 
-	return ruleOid;
+	return address;
 }
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6f4dbab..d7298ca 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -28,6 +28,22 @@ typedef struct ObjectAddress
 	int32		objectSubId;	/* Subitem within object (eg column), or 0 */
 } ObjectAddress;
 
+extern ObjectAddress InvalidObjectAddress;
+
+#define initObjectAddress(addr, class_id, object_id) \
+	do { \
+		(addr).classId = (class_id); \
+		(addr).objectId = (object_id); \
+		(addr).objectSubId = 0; \
+	} while (0)
+
+#define initObjectAddressSub(addr, class_id, object_id, object_sub_id) \
+	do { \
+		(addr).classId = (class_id); \
+		(addr).objectId = (object_id); \
+		(addr).objectSubId = (object_sub_id); \
+	} while (0)
+
 extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
 				   List *objargs, Relation *relp,
 				   LOCKMODE lockmode, bool missing_ok);
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index 14d24f1..5df28d3 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -15,10 +15,11 @@
 #define ALTER_H
 
 #include "catalog/dependency.h"
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "utils/relcache.h"
 
-extern Oid	ExecRenameStmt(RenameStmt *stmt);
+extern ObjectAddress ExecRenameStmt(RenameStmt *stmt);
 
 extern Oid	ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
 extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index cb7cc0e..f3b2d09 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -15,6 +15,7 @@
 #define DBCOMMANDS_H
 
 #include "access/xlogreader.h"
+#include "catalog/objectaddress.h"
 #include "lib/stringinfo.h"
 #include "nodes/parsenodes.h"
 
@@ -55,7 +56,7 @@ typedef struct xl_dbase_drop_rec
 
 extern Oid	createdb(const CreatedbStmt *stmt);
 extern void dropdb(const char *dbname, bool missing_ok);
-extern Oid	RenameDatabase(const char *oldname, const char *newname);
+extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
 extern Oid	AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel);
 extern Oid	AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
 extern Oid	AlterDatabaseOwner(const char *dbname, Oid newOwnerId);
diff --git a/src/include/commands/policy.h b/src/include/commands/policy.h
index e911fcc..2b25bae 100644
--- a/src/include/commands/policy.h
+++ b/src/include/commands/policy.h
@@ -28,7 +28,7 @@ extern Oid AlterPolicy(AlterPolicyStmt *stmt);
 extern Oid get_relation_policy_oid(Oid relid, const char *policy_name,
 						bool missing_ok);
 
-extern Oid rename_policy(RenameStmt *stmt);
+extern ObjectAddress rename_policy(RenameStmt *stmt);
 
 
 #endif   /* POLICY_H */
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index d08fdd4..c83e003 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -22,7 +22,7 @@ extern Oid CreateSchemaCommand(CreateSchemaStmt *parsetree,
 
 extern void RemoveSchemaById(Oid schemaOid);
 
-extern Oid	RenameSchema(const char *oldname, const char *newname);
+extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern Oid	AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaOid, Oid newOwnerId);
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index a55e8d4..f529337 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -16,6 +16,7 @@
 
 #include "access/htup.h"
 #include "catalog/dependency.h"
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
@@ -53,11 +54,13 @@ extern void ExecuteTruncate(TruncateStmt *stmt);
 
 extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass);
 
-extern Oid	renameatt(RenameStmt *stmt);
+extern ObjectAddress renameatt(RenameStmt *stmt);
 
-extern Oid	RenameConstraint(RenameStmt *stmt);
+extern ObjectAddress renameatt_type(RenameStmt *stmt);
 
-extern Oid	RenameRelation(RenameStmt *stmt);
+extern ObjectAddress RenameConstraint(RenameStmt *stmt);
+
+extern ObjectAddress RenameRelation(RenameStmt *stmt);
 
 extern void RenameRelationInternal(Oid myrelid,
 					   const char *newrelname, bool is_internal);
diff --git a/src/include/commands/tablespace.h b/src/include/commands/tablespace.h
index e8b9bc4..d910804 100644
--- a/src/include/commands/tablespace.h
+++ b/src/include/commands/tablespace.h
@@ -15,6 +15,7 @@
 #define TABLESPACE_H
 
 #include "access/xlogreader.h"
+#include "catalog/objectaddress.h"
 #include "lib/stringinfo.h"
 #include "nodes/parsenodes.h"
 
@@ -42,7 +43,7 @@ typedef struct TableSpaceOpts
 
 extern Oid	CreateTableSpace(CreateTableSpaceStmt *stmt);
 extern void DropTableSpace(DropTableSpaceStmt *stmt);
-extern Oid	RenameTableSpace(const char *oldname, const char *newname);
+extern ObjectAddress RenameTableSpace(const char *oldname, const char *newname);
 extern Oid	AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt);
 
 extern void TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo);
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index d0c0dcc..e09ac29 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -13,6 +13,7 @@
 #ifndef TRIGGER_H
 #define TRIGGER_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 
@@ -115,7 +116,7 @@ extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 extern void RemoveTriggerById(Oid trigOid);
 extern Oid	get_trigger_oid(Oid relid, const char *name, bool missing_ok);
 
-extern Oid	renametrig(RenameStmt *stmt);
+extern ObjectAddress renametrig(RenameStmt *stmt);
 
 extern void EnableDisableTrigger(Relation rel, const char *tgname,
 					 char fires_when, bool skip_system);
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index e18a714..daeabfe 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -41,7 +41,7 @@ extern void checkDomainOwner(HeapTuple tup);
 
 extern List *GetDomainConstraints(Oid typeOid);
 
-extern Oid	RenameType(RenameStmt *stmt);
+extern ObjectAddress RenameType(RenameStmt *stmt);
 extern Oid	AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype);
 extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
 					   bool hasDependEntry);
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d766851..ab10138 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -27,7 +27,7 @@ extern Oid	AlterRole(AlterRoleStmt *stmt);
 extern Oid	AlterRoleSet(AlterRoleSetStmt *stmt);
 extern void DropRole(DropRoleStmt *stmt);
 extern void GrantRole(GrantRoleStmt *stmt);
-extern Oid	RenameRole(const char *oldname, const char *newname);
+extern ObjectAddress RenameRole(const char *oldname, const char *newname);
 extern void DropOwnedObjects(DropOwnedStmt *stmt);
 extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
 extern List *roleNamesToIds(List *memberNames);
diff --git a/src/include/rewrite/rewriteDefine.h b/src/include/rewrite/rewriteDefine.h
index d384552..c112b85 100644
--- a/src/include/rewrite/rewriteDefine.h
+++ b/src/include/rewrite/rewriteDefine.h
@@ -14,6 +14,7 @@
 #ifndef REWRITEDEFINE_H
 #define REWRITEDEFINE_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "utils/relcache.h"
 
@@ -32,7 +33,7 @@ extern Oid DefineQueryRewrite(char *rulename,
 				   bool replace,
 				   List *action);
 
-extern Oid RenameRewriteRule(RangeVar *relation, const char *oldName,
+extern ObjectAddress RenameRewriteRule(RangeVar *relation, const char *oldName,
 				  const char *newName);
 
 extern void setRuleCheckAsUser(Node *node, Oid userid);
-- 
2.1.4

#13Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#9)
1 attachment(s)
Re: deparsing utility commands

Stephen Frost wrote:

Yes, I will push these unless somebody objects soon, as they seem
perfectly reasonable to me. The only troubling thing is that there is
no regression test for this kind of thing in event triggers (i.e. verify
which command tags get support and which don't), which seems odd to me.
Not these patches's fault, though, so I'm not considering adding any ATM.

Ugh. I dislike that when we say an event trigger will fire before
'GRANT' what we really mean is "GRANT when it's operating against a
local object". At the minimum we absolutely need to be very clear in
the documentation about that limitation. Perhaps there is something
already which reflects that, but I don't see anything in the patch
being added about that.

Hmm, good point, will give this some thought. I'm thinking perhaps we
can add a table of which object types are supported for generic commands
such as GRANT, COMMENT and SECURITY LABEL.

That sounds like a good idea.

Here's a patch. I noticed that the introduction to event trigger
already says they only act on local objects:

The ddl_command_start event occurs just before the execution of
a CREATE, ALTER, DROP, SECURITY LABEL, COMMENT, GRANT or REVOKE
command. No check whether the affected object exists or doesn't
exist is performed before the event trigger fires. As an
exception, however, this event does not occur for DDL commands
targeting shared objects — databases, roles, and tablespaces —
or for commands targeting event triggers themselves.

So adding more text to the same effect would be repetitive. I added a
sixth column "Notes" to the table of supported command tags vs. events,
with the text "Only for local objects" next to the four commands being
added here.

I think it's fair to push this patch as is.

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

Attachments:

0001-Support-more-commands-in-event-triggers.patchtext/x-diff; charset=us-asciiDownload
>From b366da953a10dea2fc2ae3e172cf15ab10e1e7ad Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 19 Feb 2015 17:02:41 -0300
Subject: [PATCH] Support more commands in event triggers

COMMENT, SECURITY LABEL, and GRANT/REVOKE now also fire
ddl_command_start and ddl_command_end event triggers, when they operate
on database-local objects.

Reviewed-By: Michael Paquier, Andres Freund, Stephen Frost
---
 doc/src/sgml/event-trigger.sgml      | 125 ++++++++++++++++++++++++++++++++++-
 src/backend/commands/event_trigger.c |  34 +++++++++-
 src/backend/tcop/utility.c           |  68 +++++++++++++++----
 src/include/commands/event_trigger.h |   1 +
 4 files changed, 211 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 156c463..04353ea 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -36,7 +36,9 @@
 
    <para>
      The <literal>ddl_command_start</> event occurs just before the
-     execution of a <literal>CREATE</>, <literal>ALTER</>, or <literal>DROP</>
+     execution of a <literal>CREATE</>, <literal>ALTER</>, <literal>DROP</>,
+     <literal>SECURITY LABEL</>,
+     <literal>COMMENT</>, <literal>GRANT</> or <literal>REVOKE</>
      command.  No check whether the affected object exists or doesn't exist is
      performed before the event trigger fires.
      As an exception, however, this event does not occur for
@@ -123,14 +125,15 @@
 
    <table id="event-trigger-by-command-tag">
      <title>Event Trigger Support by Command Tag</title>
-     <tgroup cols="5">
+     <tgroup cols="6">
       <thead>
        <row>
-        <entry>command tag</entry>
+        <entry>Command Tag</entry>
         <entry><literal>ddl_command_start</literal></entry>
         <entry><literal>ddl_command_end</literal></entry>
         <entry><literal>sql_drop</literal></entry>
         <entry><literal>table_rewrite</literal></entry>
+        <entry>Notes</entry>
        </row>
       </thead>
       <tbody>
@@ -140,6 +143,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER COLLATION</literal></entry>
@@ -147,6 +151,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER CONVERSION</literal></entry>
@@ -154,6 +159,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER DOMAIN</literal></entry>
@@ -161,6 +167,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER EXTENSION</literal></entry>
@@ -168,6 +175,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER FOREIGN DATA WRAPPER</literal></entry>
@@ -175,6 +183,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER FOREIGN TABLE</literal></entry>
@@ -182,6 +191,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER FUNCTION</literal></entry>
@@ -189,6 +199,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER LANGUAGE</literal></entry>
@@ -196,6 +207,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER OPERATOR</literal></entry>
@@ -203,6 +215,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER OPERATOR CLASS</literal></entry>
@@ -210,6 +223,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER OPERATOR FAMILY</literal></entry>
@@ -217,6 +231,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER POLICY</literal></entry>
@@ -224,6 +239,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER SCHEMA</literal></entry>
@@ -231,6 +247,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER SEQUENCE</literal></entry>
@@ -238,6 +255,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER SERVER</literal></entry>
@@ -245,6 +263,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER TABLE</literal></entry>
@@ -252,6 +271,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER TEXT SEARCH CONFIGURATION</literal></entry>
@@ -259,6 +279,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER TEXT SEARCH DICTIONARY</literal></entry>
@@ -266,6 +287,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER TEXT SEARCH PARSER</literal></entry>
@@ -273,6 +295,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER TEXT SEARCH TEMPLATE</literal></entry>
@@ -280,6 +303,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER TRIGGER</literal></entry>
@@ -287,6 +311,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER TYPE</literal></entry>
@@ -294,6 +319,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER USER MAPPING</literal></entry>
@@ -301,6 +327,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>ALTER VIEW</literal></entry>
@@ -308,6 +335,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE AGGREGATE</literal></entry>
@@ -315,6 +343,15 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
+       </row>
+       <row>
+        <entry align="left"><literal>COMMENT</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center">Only for local objects</entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE CAST</literal></entry>
@@ -322,6 +359,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE COLLATION</literal></entry>
@@ -329,6 +367,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE CONVERSION</literal></entry>
@@ -336,6 +375,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE DOMAIN</literal></entry>
@@ -343,6 +383,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE EXTENSION</literal></entry>
@@ -350,6 +391,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE FOREIGN DATA WRAPPER</literal></entry>
@@ -357,6 +399,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE FOREIGN TABLE</literal></entry>
@@ -364,6 +407,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE FUNCTION</literal></entry>
@@ -371,6 +415,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE INDEX</literal></entry>
@@ -378,6 +423,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE LANGUAGE</literal></entry>
@@ -385,6 +431,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE OPERATOR</literal></entry>
@@ -392,6 +439,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE OPERATOR CLASS</literal></entry>
@@ -399,6 +447,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE OPERATOR FAMILY</literal></entry>
@@ -406,6 +455,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE POLICY</literal></entry>
@@ -413,6 +463,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE RULE</literal></entry>
@@ -420,6 +471,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE SCHEMA</literal></entry>
@@ -427,6 +479,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE SEQUENCE</literal></entry>
@@ -434,6 +487,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE SERVER</literal></entry>
@@ -441,6 +495,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE TABLE</literal></entry>
@@ -448,6 +503,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE TABLE AS</literal></entry>
@@ -455,6 +511,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE TEXT SEARCH CONFIGURATION</literal></entry>
@@ -462,6 +519,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE TEXT SEARCH DICTIONARY</literal></entry>
@@ -469,6 +527,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE TEXT SEARCH PARSER</literal></entry>
@@ -476,6 +535,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE TEXT SEARCH TEMPLATE</literal></entry>
@@ -483,6 +543,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE TRIGGER</literal></entry>
@@ -490,6 +551,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE TYPE</literal></entry>
@@ -504,6 +566,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>CREATE VIEW</literal></entry>
@@ -511,6 +574,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP AGGREGATE</literal></entry>
@@ -518,6 +582,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP CAST</literal></entry>
@@ -525,6 +590,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP COLLATION</literal></entry>
@@ -532,6 +598,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP CONVERSION</literal></entry>
@@ -539,6 +606,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP DOMAIN</literal></entry>
@@ -546,6 +614,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP EXTENSION</literal></entry>
@@ -553,6 +622,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP FOREIGN DATA WRAPPER</literal></entry>
@@ -560,6 +630,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP FOREIGN TABLE</literal></entry>
@@ -567,6 +638,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP FUNCTION</literal></entry>
@@ -574,6 +646,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP INDEX</literal></entry>
@@ -581,6 +654,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP LANGUAGE</literal></entry>
@@ -588,6 +662,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP OPERATOR</literal></entry>
@@ -595,6 +670,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP OPERATOR CLASS</literal></entry>
@@ -602,6 +678,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP OPERATOR FAMILY</literal></entry>
@@ -609,6 +686,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP OWNED</literal></entry>
@@ -616,6 +694,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP POLICY</literal></entry>
@@ -623,6 +702,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP RULE</literal></entry>
@@ -630,6 +710,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP SCHEMA</literal></entry>
@@ -637,6 +718,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP SEQUENCE</literal></entry>
@@ -644,6 +726,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP SERVER</literal></entry>
@@ -651,6 +734,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP TABLE</literal></entry>
@@ -658,6 +742,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP TEXT SEARCH CONFIGURATION</literal></entry>
@@ -665,6 +750,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP TEXT SEARCH DICTIONARY</literal></entry>
@@ -672,6 +758,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP TEXT SEARCH PARSER</literal></entry>
@@ -679,6 +766,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP TEXT SEARCH TEMPLATE</literal></entry>
@@ -686,6 +774,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP TRIGGER</literal></entry>
@@ -693,6 +782,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP TYPE</literal></entry>
@@ -700,6 +790,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP USER MAPPING</literal></entry>
@@ -707,6 +798,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
        <row>
         <entry align="left"><literal>DROP VIEW</literal></entry>
@@ -714,6 +806,15 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
+       </row>
+       <row>
+        <entry align="left"><literal>GRANT</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center">Only for local objects</entry>
        </row>
        <row>
         <entry align="left"><literal>IMPORT FOREIGN SCHEMA</literal></entry>
@@ -721,6 +822,23 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
+       </row>
+       <row>
+        <entry align="left"><literal>REVOKE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center">Only for local objects</entry>
+       </row>
+       <row>
+        <entry align="left"><literal>SECURITY LABEL</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center">Only for local objects</entry>
        </row>
        <row>
         <entry align="left"><literal>SELECT INTO</literal></entry>
@@ -728,6 +846,7 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
         <entry align="center"><literal>-</literal></entry>
+        <entry align="center"></entry>
        </row>
       </tbody>
      </tgroup>
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index a33a5ad..dcf5b98 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -267,8 +267,12 @@ 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, "COMMENT") == 0 ||
+		pg_strcasecmp(tag, "GRANT") == 0 ||
+		pg_strcasecmp(tag, "REVOKE") == 0 ||
 		pg_strcasecmp(tag, "DROP OWNED") == 0 ||
-		pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0)
+		pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0 ||
+		pg_strcasecmp(tag, "SECURITY LABEL") == 0)
 		return EVENT_TRIGGER_COMMAND_TAG_OK;
 
 	/*
@@ -1149,6 +1153,34 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 	return true;
 }
 
+bool
+EventTriggerSupportsGrantObjectType(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_DATABASE:
+		case ACL_OBJECT_TABLESPACE:
+			/* no support for global objects */
+			return false;
+
+		case ACL_OBJECT_COLUMN:
+		case ACL_OBJECT_RELATION:
+		case ACL_OBJECT_SEQUENCE:
+		case ACL_OBJECT_DOMAIN:
+		case ACL_OBJECT_FDW:
+		case ACL_OBJECT_FOREIGN_SERVER:
+		case ACL_OBJECT_FUNCTION:
+		case ACL_OBJECT_LANGUAGE:
+		case ACL_OBJECT_LARGEOBJECT:
+		case ACL_OBJECT_NAMESPACE:
+		case ACL_OBJECT_TYPE:
+			return true;
+		default:
+			Assert(false);
+			return true;
+	}
+}
+
 /*
  * Prepare event trigger state for a new complete query to run, if necessary;
  * returns whether this was done.  If it was, EventTriggerEndCompleteQuery must
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3533cfa..862dd7d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -513,14 +513,6 @@ standard_ProcessUtility(Node *parsetree,
 			ExecuteTruncate((TruncateStmt *) parsetree);
 			break;
 
-		case T_CommentStmt:
-			CommentObject((CommentStmt *) parsetree);
-			break;
-
-		case T_SecLabelStmt:
-			ExecSecLabelStmt((SecLabelStmt *) parsetree);
-			break;
-
 		case T_CopyStmt:
 			{
 				uint64		processed;
@@ -548,11 +540,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);
@@ -783,6 +770,19 @@ standard_ProcessUtility(Node *parsetree,
 			 * in some cases, so we "fast path" them in the other cases.
 			 */
 
+		case T_GrantStmt:
+			{
+				GrantStmt  *stmt = (GrantStmt *) parsetree;
+
+				if (EventTriggerSupportsGrantObjectType(stmt->objtype))
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				else
+					ExecuteGrantStmt((GrantStmt *) parsetree);
+			}
+			break;
+
 		case T_DropStmt:
 			{
 				DropStmt   *stmt = (DropStmt *) parsetree;
@@ -835,6 +835,32 @@ standard_ProcessUtility(Node *parsetree,
 			}
 			break;
 
+		case T_CommentStmt:
+			{
+				CommentStmt *stmt = (CommentStmt *) parsetree;
+
+				if (EventTriggerSupportsObjectType(stmt->objtype))
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				else
+					CommentObject((CommentStmt *) parsetree);
+				break;
+			}
+
+		case T_SecLabelStmt:
+			{
+				SecLabelStmt *stmt = (SecLabelStmt *) parsetree;
+
+				if (EventTriggerSupportsObjectType(stmt->objtype))
+					ProcessUtilitySlow(parsetree, queryString,
+									   context, params,
+									   dest, completionTag);
+				else
+					ExecSecLabelStmt(stmt);
+				break;
+			}
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(parsetree, queryString,
@@ -1315,6 +1341,14 @@ ProcessUtilitySlow(Node *parsetree,
 				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_CommentStmt:
+				CommentObject((CommentStmt *) parsetree, NULL);
+				break;
+
+			case T_GrantStmt:
+				ExecuteGrantStmt((GrantStmt *) parsetree);
+				break;
+
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
 				break;
@@ -1331,6 +1365,14 @@ ProcessUtilitySlow(Node *parsetree,
 				AlterPolicy((AlterPolicyStmt *) parsetree);
 				break;
 
+			case T_SecLabelStmt:
+				{
+					SecLabelStmt *stmt = (SecLabelStmt *) parsetree;
+
+					ExecSecLabelStmt(stmt);
+				}
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(parsetree));
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index e807e65..9ac9fc3 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -48,6 +48,7 @@ extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
 
 extern bool EventTriggerSupportsObjectType(ObjectType obtype);
 extern bool EventTriggerSupportsObjectClass(ObjectClass objclass);
+extern bool EventTriggerSupportsGrantObjectType(GrantObjectType objtype);
 extern void EventTriggerDDLCommandStart(Node *parsetree);
 extern void EventTriggerDDLCommandEnd(Node *parsetree);
 extern void EventTriggerSQLDrop(Node *parsetree);
-- 
2.1.4

#14Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#13)
Re: deparsing utility commands

On 2015-02-19 17:14:57 -0300, Alvaro Herrera wrote:

The ddl_command_start event occurs just before the execution of
a CREATE, ALTER, DROP, SECURITY LABEL, COMMENT, GRANT or REVOKE
command. No check whether the affected object exists or doesn't
exist is performed before the event trigger fires. As an
exception, however, this event does not occur for DDL commands
targeting shared objects — databases, roles, and tablespaces —
or for commands targeting event triggers themselves.

So adding more text to the same effect would be repetitive. I added a
sixth column "Notes" to the table of supported command tags vs. events,
with the text "Only for local objects" next to the four commands being
added here.

I think that's sufficient for now. We might want to expand the handling
of 'global objects' topic sometime in the future, but for now I think
what you have is clear enough.

I think it's fair to push this patch as is.

+1

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

#15Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#12)
Re: deparsing utility commands

On 2015-02-19 14:39:27 -0300, Alvaro Herrera wrote:

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d899dd7..2bbc15d 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -531,6 +531,12 @@ ObjectTypeMap[] =
{ "policy", OBJECT_POLICY }
};
+ObjectAddress InvalidObjectAddress =
+{
+	InvalidOid,
+	InvalidOid,
+	0
+};

Maybe mark it as a constant? Then it can live in some readonly section.

+extern ObjectAddress InvalidObjectAddress;
+
+#define initObjectAddress(addr, class_id, object_id) \
+	do { \
+		(addr).classId = (class_id); \
+		(addr).objectId = (object_id); \
+		(addr).objectSubId = 0; \
+	} while (0)
+
+#define initObjectAddressSub(addr, class_id, object_id, object_sub_id) \
+	do { \
+		(addr).classId = (class_id); \
+		(addr).objectId = (object_id); \
+		(addr).objectSubId = (object_sub_id); \
+	} while (0)
+

Maybe, based on some precedent, make those ObjectAddressSet(Sub)?() -
the init bit in initObjectAddress sounds to me like like it does more
than setting a couple member variables.

I'd also be inclined to make initObjectAddress use initObjectAddressSub,
but that's reaching the realm of pedantism ;)

To me the change sounds like a good idea - drop/rename are already
handled somewhat generically, and expanding that to the return value for
renames sounds like a natural progression to me.

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

#16Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#1)
3 attachment(s)
Re: deparsing utility commands

Hi,

On 2015-02-15 01:48:15 -0300, Alvaro Herrera wrote:

One line of defense which I just tought about is that instead of
sprinkling EventTriggerStashCommand() calls all over ProcessUtilitySlow,
we should add only one at the bottom.

Doesn't sound like a bad idea, but I'm not sure whether it's realistic
given that some commands do multiple EventTriggerStashCommand()s?

1. ruleutils.c seems to deparse temp schemas as pg_temp_X instead of
just pg_temp; so some commands like CREATE VIEW..AS SELECT FROM temp_table
fail in an ugly way. Maybe the solution is to tell ruleutils that the
temp schema is always visible; or maybe the solution is to tell it that
it's spelled pg_temp instead.

Hm. I'm not immediately following why that's happening for deparsing but
not for the normal get_viewdef stuff?

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

This patch introduces unused infrastructure for command deparsing.
There are three main parts:

+ "as of yet"?

1. A list (stash) of executed commands in Node format, stored in the
event trigger state. At ddl_command_end, the stored items can be
extracted. For now this only support "basic" commands (in particular
not ALTER TABLE or GRANT). It's useful enough to cover all CREATE
command as well as many simple ALTER forms.

seems outdated.

(yea, I know you want to fold the patch anyway).

+/* XXX merge this with ObjectTypeMap? */
static event_trigger_support_data event_trigger_support[] = {
{"AGGREGATE", true},
{"CAST", true},

Hm, I'd just drop the XXX for now.

@@ -1060,6 +1066,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_CAST:
case OBJECT_COLUMN:
case OBJECT_COLLATION:
+ case OBJECT_COMPOSITE:
case OBJECT_CONVERSION:
case OBJECT_DEFAULT:
case OBJECT_DOMAIN:
@@ -1088,6 +1095,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
case OBJECT_TYPE:
+ case OBJECT_USER_MAPPING:
case OBJECT_VIEW:
return true;
}

Should those two be broken out and just committed?

@@ -1193,14 +1201,6 @@ EventTriggerBeginCompleteQuery(void)
EventTriggerQueryState *state;
MemoryContext cxt;

-	/*
-	 * Currently, sql_drop and table_rewrite events are the only reason to
-	 * have event trigger state at all; so if there are none, don't install
-	 * one.
-	 */
-	if (!trackDroppedObjectsNeeded())
-		return false;
-
cxt = AllocSetContextCreate(TopMemoryContext,
"event trigger state",
ALLOCSET_DEFAULT_MINSIZE,
@@ -1211,7 +1211,7 @@ EventTriggerBeginCompleteQuery(void)
slist_init(&(state->SQLDropList));
state->in_sql_drop = false;
state->table_rewrite_oid = InvalidOid;
-
+	state->stash = NIL;
state->previous = currentEventTriggerState;
currentEventTriggerState = state;

Hm. I'm not clear why we don't check for other types of event triggers
in EventTriggerBeginCompleteQuery and skip the work otherwise. If we
just enable them all the time - which imo is ok given how they're split
of in the normal process utility - we should remove the boolean return
value from Begin/CompleteQuery and drop
* Note: it's an error to call this routine if EventTriggerBeginCompleteQuery
* returned false previously.
from EndCompleteQuery.

+/*
+ * EventTriggerStashCommand
+ * 		Save data about a simple DDL command that was just executed
+ */
+void
+EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype,
+						 Oid secondaryOid, Node *parsetree)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_Simple;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.simple.objectId = objectId;
+	stashed->d.simple.objtype = objtype;
+	stashed->d.simple.objectSubId = objectSubId;
+	stashed->d.simple.secondaryOid = secondaryOid;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}

It's a bit wierd that the drop list is managed using a slist, but the
stash isn't...

+Datum
+pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
+{
+	foreach(lc, currentEventTriggerState->stash)
+	{
+		command = deparse_utility_command(cmd);
+
+		/*
+		 * Some parse trees return NULL when deparse is attempted; we don't
+		 * emit anything for them.
+		 */
+		if (command != NULL)
+		{
+			Datum		values[9];
+			bool		nulls[9];
+			ObjectAddress addr;
+			int			i = 0;
+
+			MemSet(nulls, 0, sizeof(nulls));
+
+			if (cmd->type == SCT_Simple)
+			{
+				Oid			classId;
+				Oid			objId;
+				uint32		objSubId;
+				const char *tag;
+				char	   *identity;
+				char	   *type;
+				char	   *schema = NULL;
+
+				if (cmd->type == SCT_Simple)
+				{
+					classId = get_objtype_catalog_oid(cmd->d.simple.objtype);
+					objId = cmd->d.simple.objectId;
+					objSubId = cmd->d.simple.objectSubId;
+				}
+
+				tag = CreateCommandTag(cmd->parsetree);
+				addr.classId = classId;
+				addr.objectId = objId;
+				addr.objectSubId = objSubId;
+
+				type = getObjectTypeDescription(&addr);
+				identity = getObjectIdentity(&addr);
+
+				/*
+				 * Obtain schema name, if any ("pg_temp" if a temp object)
+				 */
+				if (is_objectclass_supported(addr.classId))
+				{
+					AttrNumber	nspAttnum;
+
+					nspAttnum = get_object_attnum_namespace(addr.classId);
+					if (nspAttnum != InvalidAttrNumber)
+					{
+						Relation	catalog;
+						HeapTuple	objtup;
+						Oid			schema_oid;
+						bool		isnull;
+
+						catalog = heap_open(addr.classId, AccessShareLock);
+						objtup = get_catalog_object_by_oid(catalog,
+														   addr.objectId);
+						if (!HeapTupleIsValid(objtup))
+							elog(ERROR, "cache lookup failed for object %u/%u",
+								 addr.classId, addr.objectId);
+						schema_oid = heap_getattr(objtup, nspAttnum,
+												  RelationGetDescr(catalog), &isnull);
+						if (isnull)
+							elog(ERROR, "invalid null namespace in object %u/%u/%d",
+								 addr.classId, addr.objectId, addr.objectSubId);
+						if (isAnyTempNamespace(schema_oid))
+							schema = pstrdup("pg_temp");
+						else
+							schema = get_namespace_name(schema_oid);
+
+						heap_close(catalog, AccessShareLock);
+					}
+				}

Hm. How do we get here for !is_objectclass_supported()?

+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one, a
+ * name (string), type (from the ObjType enum) and value must be supplied.  The
+ * value must match the type given; for instance, ObjTypeInteger requires an
+ * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of
+ * ObjElem), ObjTypeObject requires an ObjTree, and so on.  Each element type *
+ * must match the conversion specifier given in the format string, as described
+ * in pg_event_trigger_expand_command, q.v.
+ *
+ * Note we don't have the luxury of sprintf-like compiler warnings for
+ * malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree();
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		ObjTree    *value;
+		ObjType		type;
+		ObjElem	   *elem;
+		char	   *name;
+		char	   *strval;
+		bool		boolval;
+		List	   *list;
+		int64		number;
+		float8		fnumber;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/* Null params don't have a value (obviously) */
+		if (type == ObjTypeNull)
+		{
+			append_null_object(tree, name);
+			continue;
+		}
+
+		/*
+		 * For all other param types there must be a value in the varargs.
+		 * Fetch it and add the fully formed subobject into the main object.
+		 */
+		switch (type)
+		{
+			case ObjTypeBool:
+				boolval = va_arg(args, int);
+				elem = new_bool_object(boolval);
+				break;
+			case ObjTypeString:
+				strval = va_arg(args, char *);
+				elem = new_string_object(strval);
+				break;
+			case ObjTypeObject:
+				value = va_arg(args, ObjTree *);
+				elem = new_object_object(value);
+				break;
+			case ObjTypeArray:
+				list = va_arg(args, List *);
+				elem = new_array_object(list);
+				break;
+			case ObjTypeInteger:
+				number = va_arg(args, int64);
+				elem = new_integer_object(number);
+				break;
+			case ObjTypeFloat:
+				fnumber = va_arg(args, double);
+				elem = new_float_object(fnumber);
+				break;
+			default:
+				elog(ERROR, "invalid parameter type %d", type);
+		}
+
+		elem->name = name;
+		append_premade_object(tree, elem);
+	}
+
+	va_end(args);
+	return tree;
+}

Would look nicer if boolval et al weren't declared - I'd just pass the
va_arg() return value directly to new_*_object().

Attached as 0001.

Additionally I find the separate handling of ObjTypeNull not so
nice. 0002.

+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{

+ append_bool_object(typeParam, "is_array", is_array);

Maybe name that one typarray?

+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function contains a large switch that mirrors that in
+ * ProcessUtilitySlow.  All cases covered there should also be covered here.
+ */
+static ObjTree *
+deparse_simple_command(StashedCommand *cmd)
+{
+
+		case T_CommentStmt:
+			command = NULL;
+			break;
+
+		case T_GrantStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;

...

+		case T_SecLabelStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+	}
+
+	return command;
+}

It is not clear to me why some commands error out and other don't. It
makes sense to me to error out for things like GrantStmt that shouldn't
end up here, but... I guess that's just an artifact of the patch series
because you add the handling for the non elog()ing commands later?

I think this should use ereport() in some cases if we're not going to
support some commands for now.

+/*
+ * Given a utility command parsetree and the OID of the corresponding object,
+ * return a JSON representation of the command.
+ *
+ * The command is expanded fully, so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+char *
+deparse_utility_command(StashedCommand *cmd)
+{
+	OverrideSearchPath *overridePath;
+	MemoryContext	oldcxt;
+	MemoryContext	tmpcxt;
+	ObjTree		   *tree;
+	char		   *command;
+	StringInfoData  str;
+
+	/*
+	 * Allocate everything done by the deparsing routines into a temp context,
+	 * to avoid having to sprinkle them with memory handling code; but allocate
+	 * the output StringInfo before switching.
+	 */
+	initStringInfo(&str);
+	tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "deparse ctx",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * in order to obtain deparsed versions of expressions.  In such results,
+	 * we want all object names to be qualified, so that results are "portable"
+	 * to environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = false;
+	PushOverrideSearchPath(overridePath);

Ah, the 'addTemp = false' probably is the answer to my question above
about view deparsing adding a fully qualified pg_temp...

+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ * n		expand as a simple number (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.
+ *------

I think this documentation should at least be referred to from
comments in deparse_utility.c. I was wondering where it is.

diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 6fbe283..a842ef2 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -416,7 +416,7 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
{
bool		first = true;
JsonbIterator *it;
-	int			type = 0;
+	JsonbIteratorToken type = WJB_DONE;
JsonbValue	v;
int			level = 0;
bool		redo_switch = false;
@@ -498,7 +498,7 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
first = false;
break;
default:
-				elog(ERROR, "unknown flag of jsonb iterator");
+				elog(ERROR, "unknown jsonb iterator token type");
}
}

Huh?

I think this infrastructure pretty desperately requires some higher
level overview. Like how are we going from the node tree, to the object
tree, to jsonb, to actual DDL. I think I understand, but it took me a
while. Doesn't have to be long and super detailed...

From dcb353c8c9bd93778a62719ff8bf32b9a419e16d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org> Date: Thu, 25 Sep 2014
15:54:00 -0300 Subject: [PATCH 09/42] deparse: Support CREATE TYPE AS

+static ObjTree *
+deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
+				  ColumnDef *coldef)
+{
+	if (!composite)
+	{
+		/*
+		 * 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.  Therefore,
+		 * we scan the list of constraints attached to this column to determine
+		 * whether we need to emit anything.
+		 * (Fortunately, NOT NULL constraints cannot be table constraints.)
+		 */

Is the primary key bit really a problem? To me it sounds like it's
actually good to retain a NOT NULL in that case. Note that pg_dump
actually *does* emit a NOT NULL here, even if you drop the primary
key. Since the column behaves as NOT NULL and is dumped as such it seems
like a good idea to fully treat it as such.

deparse: Support CREATE SCHEMA/TABLE/SEQUENCE/INDEX/TRIGGER

+/*
+ * deparse_CreateTrigStmt
+ *		Deparse a CreateTrigStmt (CREATE TRIGGER)
+ *
+ * Given a trigger OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
+{
+	CreateTrigStmt *node = (CreateTrigStmt *) parsetree;
+	Relation	pg_trigger;
+	HeapTuple	trigTup;
+	Form_pg_trigger trigForm;
+	ObjTree	   *trigger;
+	ObjTree	   *tmp;
+	int			tgnargs;
+	List	   *list;
+	List	   *events;
+
+	pg_trigger = heap_open(TriggerRelationId, AccessShareLock);
+
+	trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+	trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+	/*
+	 * Some of the elements only make sense for CONSTRAINT TRIGGERs, but it
+	 * seems simpler to use a single fmt string for both kinds of triggers.
+	 */
+	trigger =
+		new_objtree_VA("CREATE %{constraint}s TRIGGER %{name}I %{time}s %{events: OR }s "
+					   "ON %{relation}D %{from_table}s %{constraint_attrs: }s "
+					   "FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE %{function}s",
+					   2,
+					   "name", ObjTypeString, node->trigname,
+					   "constraint", ObjTypeString,
+					   node->isconstraint ? "CONSTRAINT" : "");
+
+	if (node->timing == TRIGGER_TYPE_BEFORE)
+		append_string_object(trigger, "time", "BEFORE");
+	else if (node->timing == TRIGGER_TYPE_AFTER)
+		append_string_object(trigger, "time", "AFTER");
+	else if (node->timing == TRIGGER_TYPE_INSTEAD)
+		append_string_object(trigger, "time", "INSTEAD OF");
+	else
+		elog(ERROR, "unrecognized trigger timing value %d", node->timing);
+
+	/*
+	 * Decode the events that the trigger fires for.  The output is a list;
+	 * in most cases it will just be a string with the even name, but when
+	 * there's an UPDATE with a list of columns, we return a JSON object.
+	 */
+	events = NIL;
+	if (node->events & TRIGGER_TYPE_INSERT)
+		events = lappend(events, new_string_object("INSERT"));
+	if (node->events & TRIGGER_TYPE_DELETE)
+		events = lappend(events, new_string_object("DELETE"));
+	if (node->events & TRIGGER_TYPE_TRUNCATE)
+		events = lappend(events, new_string_object("TRUNCATE"));
+	if (node->events & TRIGGER_TYPE_UPDATE)
+	{
+		if (node->columns == NIL)
+		{
+			events = lappend(events, new_string_object("UPDATE"));
+		}
+		else
+		{
+			ObjTree	   *update;
+			ListCell   *cell;
+			List	   *cols = NIL;
+
+			/*
+			 * Currently only UPDATE OF can be objects in the output JSON, but
+			 * we add a "kind" element so that user code can distinguish
+			 * possible future new event types.
+			 */
+			update = new_objtree_VA("UPDATE OF %{columns:, }I",
+									1, "kind", ObjTypeString, "update_of");
+
+			foreach(cell, node->columns)
+			{
+				char   *colname = strVal(lfirst(cell));
+
+				cols = lappend(cols,
+							   new_string_object(colname));
+			}
+
+			append_array_object(update, "columns", cols);
+
+			events = lappend(events,
+							 new_object_object(update));
+		}
+	}
+	append_array_object(trigger, "events", events);
+
+	tmp = new_objtree_for_qualname_id(RelationRelationId,
+									  trigForm->tgrelid);
+	append_object_object(trigger, "relation", tmp);
+
+	tmp = new_objtree_VA("FROM %{relation}D", 0);
+	if (trigForm->tgconstrrelid)
+	{
+		ObjTree	   *rel;
+
+		rel = new_objtree_for_qualname_id(RelationRelationId,
+										  trigForm->tgconstrrelid);
+		append_object_object(tmp, "relation", rel);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "from_table", tmp);
+
+	list = NIL;
+	if (node->deferrable)
+		list = lappend(list,
+					   new_string_object("DEFERRABLE"));
+	if (node->initdeferred)
+		list = lappend(list,
+					   new_string_object("INITIALLY DEFERRED"));

I mildly wonder if DEFERRABLE/INITIALLY DEFERRED shouldn't instead have
an 'is_present' style representation.

+/*
+ * deparse_CreateStmt
+ *		Deparse a CreateStmt (CREATE TABLE)

I really wish CreateStmt were named CreateTableStmt. I don't know how
often that tripped me over, let alone everyone. Yes, that's an unrelated
rant ;)

+ * Given a table OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{

...

+	tmp = new_objtree_VA("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);

Hm. I had not thought about that before, but given that
default_tablespace exists, we should maybe somehow indicate which one
was chosen?

+static ObjElem *
+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(RelationRelationId, ownerId);
+		append_string_object(tmp, "attrname", colname);
+		ownedby = new_objtree_VA("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.
+	 */

"column"? Should have been row?

+	if (!ownedby)
+		/* XXX this shouldn't happen ... */
+		ownedby = new_objtree_VA("OWNED BY %{owner}D",
+								 3,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeNull,
+								 "present", ObjTypeBool, false);

Why shouldn't this happen? Free standing sequences are perfectly normal?
And OWNED BY NONE will need to be deparseable.

+static ObjTree *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *createSeq;
+	ObjTree    *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+
+	seqdata = get_sequence_values(objectId);
+
+	createSeq =
+		new_objtree_VA("CREATE %{persistence}s SEQUENCE %{identity}D "
+					   "%{definition: }s",
+					   1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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 */

Needs explanation about the reason. Hm. I don't think it's actually
correct. Right now the following won't be deparsed correctly:

CREATE TABLE seqowner(id int);
CREATE SEQUENCE seqowned OWNED BY seqowner.id;
that generates
CREATE TABLE public.seqowner (id pg_catalog.int4 ) WITH (oids=OFF)
CREATE SEQUENCE public.seqowned CACHE 1 NO CYCLE INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 RESTART 1

+/*
+ * deparse_IndexStmt
+ *		deparse an IndexStmt
+ *
+ * Given an index OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ */
+static ObjTree *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+	/* tablespace */
+	tmp = new_objtree_VA("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);

Same default tablespace question as with create table...

From 4e35ba6557a6d04539d69d75821c568f9efb09b5 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 12/42] deparse: Support CREATE TYPE AS RANGE

+static ObjTree *
+deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
+{
+	/* 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(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(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(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(tmp));
+	}

Maybe use the present = false stuff here as well?

From 04dc40db971dc51c7e8c646cb5d822488da306f7 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 13/42] deparse: Support CREATE EXTENSION

/*
+ * 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?

I tend to think it's ok. Might be worthwhile to add the final extension
version in some unused attribute?

From f45a9141905e93b83d28809e357f95c7fa713fe7 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 14/42] deparse: Support CREATE RULE

/me scheds some tears

From 67ff742875bfadd182ae28e40e54476c9e4d220e 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 16/42] deparse: Support for ALTER <OBJECT> RENAME

It supports everything but functions, aggregates, operator classes and
operator families.

AFAICS those are implemented, and you updated the description in git
since.

+static ObjTree *
+deparse_RenameStmt(Oid objectId, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree	   *renameStmt;
+	char	   *fmtstr;
+	Relation	relation;
+	Oid			schemaId;
+
+	/*
+	 * FIXME --- this code is missing support for inheritance behavioral flags,
+	 * i.e. the "*" and ONLY elements.
+	 */

Hm. I think we probably need that?

+	 * 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.
+	 */

We should probably prohibit DDL from within event triggers?

+	switch (node->renameType)
+	{
+		case OBJECT_COLLATION:
+		case OBJECT_CONVERSION:
+		case OBJECT_DOMAIN:
+		case OBJECT_TSDICTIONARY:
+		case OBJECT_TSPARSER:
+		case OBJECT_TSTEMPLATE:
+		case OBJECT_TSCONFIGURATION:
+		case OBJECT_TYPE:
+			{
+				char	   *identity;
+				HeapTuple	objTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				Oid			classId = get_objtype_catalog_oid(node->renameType);
+				AttrNumber	Anum_namespace = get_object_attnum_namespace(classId);
+
+				catalog = relation_open(classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, objectId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				identity = psprintf("%s.%s", get_namespace_name(DatumGetObjectId(objnsp)),
+									strVal(llast(node->object)));
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));

Is that correct? I.e. will it work for a namespace that needs to be
quoted? There's a couple of these. Afaics they all should use
new_objtree_for_qualname and %{}D? I guess in some cases, like the type,
that could be slightly more complicated, but I guess it has to be done
nonetheless?

+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			{
+				char	   *newident;
+				ObjectAddress objaddr;
+				const char	   *quoted_newname;
+				StringInfoData old_ident;
+				char	   *start;
+
+				/*
+				 * Generating a function/aggregate identity is altogether too
+				 * messy, so instead of doing it ourselves, we generate one for
+				 * the renamed object, then strip out the name and replace it
+				 * with the original name from the parse node.  This is so ugly
+				 * that we don't dare do it for any other object kind.
+				 */
+
+				objaddr.classId = get_objtype_catalog_oid(node->renameType);
+				objaddr.objectId = objectId;
+				objaddr.objectSubId = 0;
+				newident = getObjectIdentity(&objaddr);
+
+				quoted_newname = quote_identifier(node->newname);
+				start = strstr(newident, quoted_newname);
+				if (!start)
+					elog(ERROR, "could not find %s in %s", start, newident);
+				initStringInfo(&old_ident);
+				appendBinaryStringInfo(&old_ident, newident, start - newident);
+				appendStringInfoString(&old_ident,
+									   quote_identifier(strVal(llast(node->object))));
+				appendStringInfoString(&old_ident, start + strlen(quoted_newname));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 1,
+											"identity", ObjTypeString, old_ident.data);

Yuck.

I think it'd be just as easy to split the argument output from the
function name output in format_procedure_internal, and make that
separately callable.

Subject: [PATCH 18/42] deparse: Support CREATE FUNCTION

/*
+ * 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.
+ */

Support attached as 0003.

Subject: [PATCH 19/42] deparse: Support ALTER TABLE

+/*
+ * 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 = creating_extension;
+
+	stashed->d.alterTable.objectId = InvalidOid;
+	stashed->d.alterTable.objtype = objtype;
+	stashed->d.alterTable.subcmds = NIL;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->curcmd = stashed;
+
+	MemoryContextSwitchTo(oldcxt);
+}

Hm. So +EventTriggerComplexCmdStart is hardcoded to use SCT_AlterTable? That's a bit odd.

+/*
+ * 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.
+ */

I think that'd generally be better than requiring PG_CATCH.

+static ObjTree *
+deparse_AlterTableStmt(StashedCommand *cmd)
+{
+	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(tmp));
+				break;

Misses ONLY handling?

+			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(tmp));
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;

Hm, didn't you have a out of line version of this somewhere?

+ case AT_AlterColumnType:
+ {

+					/*
+					 * Error out if the USING clause was used.  We cannot use
+					 * it directly here, because it needs to run through
+					 * transformExpr() before being usable for ruleutils.c, and
+					 * we're not in a position to transform it ourselves.  To
+					 * fix this problem, tablecmds.c needs to be modified to store
+					 * the transformed expression somewhere in the StashedATSubcmd.
+					 */
+					tmp2 = new_objtree_VA("USING %{expression}s", 0);
+					if (def->raw_default)
+						elog(ERROR, "unimplemented deparse of ALTER TABLE TYPE USING");
+					else
+						append_bool_object(tmp2, "present", false);
+					append_object_object(tmp, "using", tmp2);

Hm. This probably needs to be fixed :(.

+				}
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS");
+				break;
+			case AT_SetRelOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE SET");
+				break;
+
+			case AT_ResetRelOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE RESET");
+				break;
+			case AT_GenericOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE OPTIONS (...)");
+				break;
+

Shouldn't be too hard...

Could you perhaps attach some string that can be searched for to all
commands that you think need to be implemented before being committable?

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);
}

Hm. Commands are always ordered in a way this is ok?

Subject: [PATCH 21/42] deparse: Support CREATE OPERATOR FAMILY

static ObjTree *
+deparse_CreateOpFamily(Oid objectId, Node *parsetree)
+{
+	HeapTuple   opfTup;
+	HeapTuple   amTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_am  amForm;
+	ObjTree	   *copfStmt;
+	ObjTree	   *tmp;
+
+	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));

Hm. Amname is unquoted? I can't believe this will ever really be a problem, but still.

Subject: [PATCH 24/42] deparse: support ALTER THING OWNER TO
static ObjTree *
+deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+	ObjTree	   *ownerStmt;
+	ObjectAddress addr;
+	char	   *fmt;
+
+	fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newname}I",
+				   stringify_objtype(node->objectType));

newname sounds like copied from rename.

Subject: [PATCH 25/42] deparse: Support ALTER EXTENSION / UPDATE TO

Subject: [PATCH 26/42] deparse: Support GRANT/REVOKE

+			else
+			{
+				Assert(cmd->type == SCT_Grant);
+
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;

Might actually be nice to fill those out to the object that's being
changed. But it might end up being more work than justified, and it can
be easily added later. I guess it'd also mean we would have to generate
multiple GRANT/REVOKE commands.

diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c09915d..92bccf5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -781,6 +781,7 @@ standard_ProcessUtility(Node *parsetree,
else
ExecuteGrantStmt((GrantStmt *) parsetree);
}
+			break;

Uh. Was that missing from an earlier commit?

case T_DropStmt:
{
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index cdce9e1..60b590b 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
{
@@ -62,6 +63,7 @@ extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,

extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
ObjectType objtype, Oid secondaryOid, 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 8ebbf34..c364603 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_Simple,
- SCT_AlterTable
+ SCT_AlterTable,
+ SCT_Grant
} StashedCommandType;

/*
@@ -61,6 +64,12 @@ typedef struct StashedCommand
ObjectType objtype;
List   *subcmds;
} alterTable;
+
+		struct GrantCommand
+		{
+			InternalGrant *istmt;
+			const char *type;
+		} 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

Maybe we should name it aclchk_internal.h?

Subject: [PATCH 27/42] deparse: Support ALTER FUNCTION

+ * deparse_AlterFunctionStmt
+ *		Deparse a AlterFunctionStmt (ALTER FUNCTION)
+ *
+ * Given a function OID and the parsetree that created it, return the JSON
+ * blob representing the alter command.
+ *
+ * XXX this is missing the per-function custom-GUC thing.
+ */

Hm, so my earlier ptatch needs to partially be copied here. I'm running out of time now though...

+static ObjTree *
+deparse_AlterFunction(Oid objectId, Node *parsetree)
+{
+	foreach(cell, node->actions)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "volatility") == 0)
+		{
+			tmp = new_objtree_VA(strVal(defel->arg), 0);
+		}
+		else if (strcmp(defel->defname, "strict") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "RETURNS NULL ON NULL INPUT" :
+								 "CALLED ON NULL INPUT", 0);
+		}

Ick. I wish there was NOT STRICT.

Subject: [PATCH 28/42] deparse: support COMMENT ON

+static ObjTree *
+deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	HeapTuple	constrTup;
+	Form_pg_constraint constrForm;
+	char	   *fmt;
+	ObjectAddress addr;
+
+	Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
+
+	if (node->comment)
+		fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}L",
+					   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	else
+		fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS NULL",
+					   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");

psprintf(...' IS %s', .... node->comment ? "%{comment}L" : "NULL") maybe?

+
+static ObjTree *
+deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree)
+{
+	if (node->comment)
+		fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}L",
+					   stringify_objtype(node->objtype));
+	else
+		fmt = psprintf("COMMENT ON %s %%{identity}s IS NULL",
+					   stringify_objtype(node->objtype));

similarly here?

+	comment = new_objtree_VA(fmt, 0);
+	if (node->comment)
+		append_string_object(comment, "comment", node->comment);

Or at leas tthis should be moved into the above if.

Subject: [PATCH 29/42] deparse: support SECURITY LABEL

static ObjTree *
+deparse_SecLabelStmt(Oid objectId, Oid objectSubId, Node *parsetree)
+{
+	SecLabelStmt *node = (SecLabelStmt *) parsetree;
+	ObjTree	   *label;
+	ObjectAddress addr;
+	char	   *fmt;
+
+	if (node->label)
+	{
+		fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}L",
+				   stringify_objtype(node->objtype));
+		label = new_objtree_VA(fmt, 0);
+
+		append_string_object(label, "label", node->label);
+	}
+	else
+	{
+		fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS NULL",
+				   stringify_objtype(node->objtype));
+		label = new_objtree_VA(fmt, 0);
+	}

Should probably merged similarly.

"deparse: Handle default security provider." should be squashed into this.

Subject: [PATCH 34/42] deparse: support CREATE TABLE AS

+static ObjTree *
+deparse_CreateTableAsStmt(Oid objectId, Node *parsetree)
+{
+	/*
+	 * Note that INSERT INTO is deparsed as CREATE TABLE AS.  They are
+	 * functionally equivalent synonyms so there is no harm from this.
+	 */
+	if (node->relkind == OBJECT_MATVIEW)
+		fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+	else
+		fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with}s %{on_commit}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";

With my replication hat on, which I admit isn't very general in this
case, this isn't actually what I want... What I really would rather have
there is a normal CREATE TABLE statement that I can replay on the other
side. But I guess that has to be done somehow in a utility hook :(

+	/* Add an ON COMMIT clause.  CREATE MATERIALIZED VIEW doesn't have one */
+	if (node->relkind == OBJECT_TABLE)
+	{
+		tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0);
+		switch (node->into->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);
+	}

That switch already exists somewhere else? Maybe add a function that
converts ONCOMMIT_* into the relevant string?

Ran out of energy here...

Greetings,

Andres Freund

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

Attachments:

0001-fixup-deparse-infrastructure-needed-for-command-depa.patchtext/x-patch; charset=us-asciiDownload
>From f8e036c07e3db393fa1201df6ea55607722dfeb3 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sat, 21 Feb 2015 13:53:41 +0100
Subject: [PATCH 1/3] fixup! deparse: infrastructure needed for command
 deparsing

Slight cleanup in new_objtree_VA()
---
 src/backend/tcop/deparse_utility.c | 26 +++++++-------------------
 1 file changed, 7 insertions(+), 19 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index f6f0311..d532b79 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -189,15 +189,9 @@ new_objtree_VA(char *fmt, int numobjs,...)
 	va_start(args, numobjs);
 	for (i = 0; i < numobjs; i++)
 	{
-		ObjTree    *value;
+		char	   *name;
 		ObjType		type;
 		ObjElem	   *elem;
-		char	   *name;
-		char	   *strval;
-		bool		boolval;
-		List	   *list;
-		int64		number;
-		float8		fnumber;
 
 		name = va_arg(args, char *);
 		type = va_arg(args, ObjType);
@@ -216,28 +210,22 @@ new_objtree_VA(char *fmt, int numobjs,...)
 		switch (type)
 		{
 			case ObjTypeBool:
-				boolval = va_arg(args, int);
-				elem = new_bool_object(boolval);
+				elem = new_bool_object(va_arg(args, int));
 				break;
 			case ObjTypeString:
-				strval = va_arg(args, char *);
-				elem = new_string_object(strval);
+				elem = new_string_object(va_arg(args, char *));
 				break;
 			case ObjTypeObject:
-				value = va_arg(args, ObjTree *);
-				elem = new_object_object(value);
+				elem = new_object_object(va_arg(args, ObjTree *));
 				break;
 			case ObjTypeArray:
-				list = va_arg(args, List *);
-				elem = new_array_object(list);
+				elem = new_array_object(va_arg(args, List *));
 				break;
 			case ObjTypeInteger:
-				number = va_arg(args, int64);
-				elem = new_integer_object(number);
+				elem = new_integer_object(va_arg(args, int64));
 				break;
 			case ObjTypeFloat:
-				fnumber = va_arg(args, double);
-				elem = new_float_object(fnumber);
+				elem = new_float_object(va_arg(args, double));
 				break;
 			default:
 				elog(ERROR, "invalid parameter type %d", type);
-- 
2.3.0.149.gf3f4077.dirty

0002-fixup-deparse-infrastructure-needed-for-command-depa.patchtext/x-patch; charset=us-asciiDownload
>From 64146f568d1391512ca71257be3ff0c2f676b8cb Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sat, 21 Feb 2015 13:58:42 +0100
Subject: [PATCH 2/3] fixup! deparse: infrastructure needed for command
 deparsing

Slight cleanup in new_objtree_VA() by treating ObjTypeNull just like the
other types.
---
 src/backend/tcop/deparse_utility.c | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index d532b79..35c4eca 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -196,13 +196,6 @@ new_objtree_VA(char *fmt, int numobjs,...)
 		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.
@@ -227,8 +220,10 @@ new_objtree_VA(char *fmt, int numobjs,...)
 			case ObjTypeFloat:
 				elem = new_float_object(va_arg(args, double));
 				break;
-			default:
-				elog(ERROR, "invalid parameter type %d", type);
+			case ObjTypeNull:
+				/* Null params don't have a value (obviously) */
+				elem = new_null_object();
+				break;
 		}
 
 		elem->name = name;
-- 
2.3.0.149.gf3f4077.dirty

0003-fixup-deparse-Support-CREATE-FUNCTION.patchtext/x-patch; charset=us-asciiDownload
>From 7e6ba04f038b205f1527277cff2f50a326a902f1 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sat, 21 Feb 2015 16:36:34 +0100
Subject: [PATCH 3/3] fixup! deparse: Support CREATE FUNCTION

Add SET support.
---
 src/backend/tcop/deparse_utility.c | 55 +++++++++++++++++++++++++++++++++++---
 1 file changed, 52 insertions(+), 3 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 35c4eca..4abf6fa 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3089,7 +3089,58 @@ deparse_CreateFunction(Oid objectId, Node *parsetree)
 		append_float_object(tmp, "rows", procForm->prorows);
 	append_object_object(createFunc, "rows", tmp);
 
-	append_array_object(createFunc, "set_options", NIL);
+	tmpdatum = SysCacheGetAttr(PROCOID, procTup,
+							   Anum_pg_proc_proconfig, &isnull);
+	if (!isnull)
+	{
+		List	   *sets = NIL;
+		ArrayType  *a = DatumGetArrayTypeP(tmpdatum);
+		int			i;
+
+		Assert(ARR_ELEMTYPE(a) == TEXTOID);
+		Assert(ARR_NDIM(a) == 1);
+		Assert(ARR_LBOUND(a)[0] == 1);
+
+		for (i = 1; i <= ARR_DIMS(a)[0]; i++)
+		{
+			Datum		d;
+
+			d = array_ref(a, 1, &i,
+						  -1 /* varlenarray */ ,
+						  -1 /* TEXT's typlen */ ,
+						  false /* TEXT's typbyval */ ,
+						  'i' /* TEXT's typalign */ ,
+						  &isnull);
+			if (!isnull)
+			{
+				char	   *configitem = TextDatumGetCString(d);
+				char	   *pos;
+				ObjTree	   *oneset;
+
+				pos = strchr(configitem, '=');
+				if (pos == NULL)
+					continue;
+				*pos++ = '\0';
+
+				/*
+				 * Some GUC variable names are 'LIST' type and hence must not
+				 * be quoted. FIXME: shouldn't this and pg_get_functiondef()
+				 * rather use guc.c to check for GUC_LIST?
+				 */
+				if (pg_strcasecmp(configitem, "DateStyle") == 0
+					|| pg_strcasecmp(configitem, "search_path") == 0)
+					oneset = new_objtree_VA("SET %{set_name}I TO %{set_value}s", 0);
+				else
+					oneset = new_objtree_VA("SET %{set_name}I TO %{set_value}L", 0);
+
+				append_string_object(oneset, "set_name", configitem);
+				append_string_object(oneset, "set_value", pos);
+
+				sets = lappend(sets, new_object_object(oneset));
+			}
+		}
+		append_array_object(createFunc, "set_options", sets);
+	}
 
 	if (probin == NULL)
 	{
@@ -3114,8 +3165,6 @@ deparse_CreateFunction(Oid objectId, Node *parsetree)
  *
  * Given a function OID and the parsetree that created it, return the JSON
  * blob representing the alter command.
- *
- * XXX this is missing the per-function custom-GUC thing.
  */
 static ObjTree *
 deparse_AlterFunction(Oid objectId, Node *parsetree)
-- 
2.3.0.149.gf3f4077.dirty

#17Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#1)
Re: deparsing utility commands

Alvaro, all,

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

This is a repost of the patch to add CREATE command deparsing support to
event triggers. It now supports not only CREATE but also ALTER and
other commands such as GRANT/REVOKE, COMMENT ON and SECURITY LABEL.
This patch series is broken up in a rather large number of patches, but
my intention would be to commit separately only the first 6 patches; the
remaining parts are split up only so that it's easy to see how deparsing
each patch is implemented, and would be committed as a single changeset
(but before that I need a bunch of extra fixes and additions to other
parts.)

Alright, I spent a solid few hours yesterday going back through the past
year+ discussion around this. I'm planning to spend more time on
review, though I see Andres has done a good review and I'm generally
on-board with the comments he made. There's a few things which aren't
really "review" but more commentary on the approach that I wanted to
bring up independently.

First off, I took a look at what ends up being required for CREATE
POLICY, as it's code I would have been writing if the timing had ended
up a bit differently. Below is the CREATE POLICY support and it looks
pretty darn reasonable to me and no worse than dealing with pg_dump, or
psql, or the other areas which we have to update whenever we're adding
new commands. To be clear, I'm no more interested in adding more work
to be done whenever we add a new command than the next committer, but
this isn't bad and gives us a new capability (well, almost, more below)
which I've wished for since I started hacking on PG some 13+ years ago.

It'd be *really* nice to be able to pass an object identifier to some
function and get back the CREATE (in particular, though perhaps DROP, or
whatever) command for it. This gets us *awful* close to that without
actually giving it to us and that's bugging me. The issue is the
parsetree, which I understand may be required in some cases (ALTER,
primairly, it seems?), but isn't always.

Looking at the CREATE POLICY patch, here's what I see.

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 2a0a2b9..cd600ff 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4402,6 +4402,91 @@ deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree)
}
static ObjTree *
+deparse_CreatePolicyStmt(Oid objectId, Node *parsetree)
+{
+	CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree;
+	ObjTree	   *policy;
+	ObjTree	   *tmp;
+	Relation	polRel = heap_open(PolicyRelationId, AccessShareLock);
+	HeapTuple	polTup = get_catalog_object_by_oid(polRel, objectId);
+	Form_pg_policy polForm;
+	ListCell   *cell;
+	List	   *list;
+
+	if (!HeapTupleIsValid(polTup))
+		elog(ERROR, "cache lookup failed for policy %u", objectId);
+	polForm = (Form_pg_policy) GETSTRUCT(polTup);

Alright, we get the parsetree and build a CreatePolicyStmt with it, but
what do we use it for? We also get the polForm from pg_policy.

+	policy = new_objtree_VA("CREATE POLICY %{identity}I ON %{table}D %{for_command}s "
+							"%{to_role}s %{using}s %{with_check}s", 1,
+							"identity", ObjTypeString, node->policy_name);

The policy_name is certainly available from pg_policy instead.

+	/* add the "ON table" clause */
+	append_object_object(policy, "table",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 polForm->polrelid));

Here we're getting the polrelid from the polForm.

+	/* add "FOR command" clause */
+	tmp = new_objtree_VA("FOR %{command}s", 1,
+						 "command", ObjTypeString, node->cmd);
+	append_object_object(policy, "for_command", tmp);

The cmd is also available from the polForm.

+	/* add the "TO role" clause. Always contains at least PUBLIC */
+	tmp = new_objtree_VA("TO %{role:, }I", 0);
+	list = NIL;
+	foreach (cell, node->roles)
+	{
+		list = lappend(list,
+					   new_string_object(strVal(lfirst(cell))));
+	}
+	append_array_object(tmp, "role", list);
+	append_object_object(policy, "to_role", tmp);
+
+	/* add the USING clause, if any */
+	tmp = new_objtree_VA("USING (%{expression}s)", 0);
+	if (node->qual)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polqual expression in policy %u", objectId);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policy, "using", tmp);
+
+	/* add the WITH CHECK clause, if any */
+	tmp = new_objtree_VA("WITH CHECK (%{expression}s)", 0);
+	if (node->with_check)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polwithcheck expression in policy %u", objectId);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policy, "with_check", tmp);
+
+	heap_close(polRel, AccessShareLock);

The USING and WITH CHECK quals can be extracted from the polForm also,
of course, it's what psql and pg_dump are doing, after all.

So, why depend on the parsetree? What about have another argument which
a user could use without the parsetree, for things that it's absolutely
required for today (eg: ALTER sub-command differentiation)? Maybe
that's possible and maybe it isn't, but at least for the CREATE cases we
should be able to avoid forcing a user to provide a built parsetree, and
that'd be *really* nice and open up this feature to quite a few other
use-cases that I can think of. I'd further suggest that we provide
these command to the SQL level, and then have a wrapper which will take
the name of an object, resolve it to Oid, and then pass back the CREATE
command for it.

For as much commentary as there has been about event triggers and
replication and BDR, and about how this is going to end up being a
little-used side-feature, I'm amazed that there has been very little
discussion about how this would finally put into the backend the ability
to build up CREATE commands for objects and remove the need to use
pg_dump for that (something which isn't exactly fun for GUI applications
and other use-cases). Further, this would increase the number of people
using the actually complicated part of this, which is building up of the
command from the catalog, and that helps address both the concerns about
bad lurking bugs that go unnoticed and about how this wouldn't be used
very much and therefore might bitrot.

I don't know about the rest of you, but I'd love to see a psql backslash
command to compliment this capability, to which you can specify a table
and get back a CREATE TABLE statement.

And, yes, I'm fine with the JSON aspect of this (even though I realize
my above commentary glosses over it) and think that's likely to be
picked up by other clients also- so we should certainly provide a way to
expose that structure to clients too (imagine pgAdmin4 built on top of
this!).

Thanks!

Stephen

#18Andres Freund
andres@2ndquadrant.com
In reply to: Stephen Frost (#17)
Re: deparsing utility commands

On 2015-02-21 14:51:32 -0500, Stephen Frost wrote:

It'd be *really* nice to be able to pass an object identifier to some
function and get back the CREATE (in particular, though perhaps DROP, or
whatever) command for it. This gets us *awful* close to that without
actually giving it to us and that's bugging me. The issue is the
parsetree, which I understand may be required in some cases (ALTER,
primairly, it seems?), but isn't always.

The USING and WITH CHECK quals can be extracted from the polForm also,
of course, it's what psql and pg_dump are doing, after all.

So, why depend on the parsetree? What about have another argument which
a user could use without the parsetree, for things that it's absolutely
required for today (eg: ALTER sub-command differentiation)?

I'm really not wild about pretty massively expanding the scope at the
eleventh hour. There's a fair number of commands where this the
deparsing command will give you a bunch of commands - look at CREATE
TABLE and CREATE SCHEMA ... for the most extreme examples. For those
there's no chance to do that without the parse tree available.

Yes, it might be possible to use the same code for a bunch of minor
commands, but not for the interesting/complex stuff.

Maybe that's possible and maybe it isn't, but at least for the CREATE
cases we should be able to avoid forcing a user to provide a built
parsetree, and that'd be *really* nice and open up this feature to
quite a few other use-cases that I can think of. I'd further suggest
that we provide these command to the SQL level, and then have a
wrapper which will take the name of an object, resolve it to Oid, and
then pass back the CREATE command for it.

I think this is a different feature that might end up sharing some of
the infrastructure, but not more.

For as much commentary as there has been about event triggers and
replication and BDR, and about how this is going to end up being a
little-used side-feature, I'm amazed that there has been very little
discussion about how this would finally put into the backend the ability
to build up CREATE commands for objects and remove the need to use
pg_dump for that (something which isn't exactly fun for GUI applications
and other use-cases).

I don't think it's all that related, so I'm not surprised. If you
execute a CREATE TABLE(id serial primary key); you'll get a bunch of
commands with this facility - it'd be much less clear what exactly shall
be dumped in the case of some hypothetical GUI when you want to dump the
table.

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

#19Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#16)
1 attachment(s)
Re: deparsing utility commands

On 2015-02-21 17:30:24 +0100, Andres Freund wrote:

/*
+ * 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.
+ */

Subject: [PATCH 27/42] deparse: Support ALTER FUNCTION

+ * deparse_AlterFunctionStmt
+ *		Deparse a AlterFunctionStmt (ALTER FUNCTION)
+ *
+ * Given a function OID and the parsetree that created it, return the JSON
+ * blob representing the alter command.
+ *
+ * XXX this is missing the per-function custom-GUC thing.
+ */

Hm, so my earlier ptatch needs to partially be copied here. I'm running out of time now though...

Updated 0003 attached that supports both SET for both CREATE and
ALTER. In my previous patch I'd looked at proconfig for the values. A
bit further thought revealed that that's not such a great idea: It works
well enough for CREATE FUNCTION, but already breaks down at CREATE OR
REPLACE FUNCTION unless we want to emit RESET ALL (SET ...)+ which seems
mighty ugly.

Also, the code is actually a good bit easier to understand this
way. I. hate. arrays. ;)

Greetings,

Andres Freund

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

Attachments:

0003-Support-SET-RESET-in-CREATE-ALTER-FUNCTION.patchtext/x-patch; charset=us-asciiDownload
>From bb8b0027e82e628fb098b9707f36fa5ce08b9234 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sat, 21 Feb 2015 16:36:34 +0100
Subject: [PATCH 3/3] Support SET/RESET in CREATE/ALTER FUNCTION.

---
 src/backend/tcop/deparse_utility.c | 61 ++++++++++++++++++++++++++++++++++----
 1 file changed, 55 insertions(+), 6 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 35c4eca..cb52cb2 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2802,14 +2802,46 @@ deparse_CreateDomain(Oid objectId, Node *parsetree)
 	return createDomain;
 }
 
+static ObjTree *
+deparse_FunctionSet(VariableSetKind kind, char *name, char *value)
+{
+	ObjTree	   *r;
+
+	if (kind == VAR_RESET_ALL)
+	{
+		r = new_objtree_VA("RESET ALL", 0);
+	}
+	else if (value != NULL)
+	{
+		/*
+		 * Some GUC variable names are 'LIST' type and hence must not be
+		 * quoted. FIXME: shouldn't this and pg_get_functiondef() rather use
+		 * guc.c to check for GUC_LIST?
+		 */
+		if (pg_strcasecmp(name, "DateStyle") == 0
+			|| pg_strcasecmp(name, "search_path") == 0)
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}s", 0);
+		else
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}L", 0);
+
+		append_string_object(r, "set_name", name);
+		append_string_object(r, "set_value", value);
+	}
+	else
+	{
+		r = new_objtree_VA("RESET %{set_name}I", 0);
+		append_string_object(r, "set_name", name);
+	}
+
+	return r;
+}
+
 /*
  * 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 ObjTree *
 deparse_CreateFunction(Oid objectId, Node *parsetree)
@@ -2825,6 +2857,7 @@ deparse_CreateFunction(Oid objectId, Node *parsetree)
 	char	   *probin;
 	List	   *params;
 	List	   *defaults;
+	List	   *sets = NIL;
 	ListCell   *cell;
 	ListCell   *curdef;
 	ListCell   *table_params = NULL;
@@ -3089,7 +3122,21 @@ deparse_CreateFunction(Oid objectId, Node *parsetree)
 		append_float_object(tmp, "rows", procForm->prorows);
 	append_object_object(createFunc, "rows", tmp);
 
-	append_array_object(createFunc, "set_options", NIL);
+	foreach(cell, node->options)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+			sets = lappend(sets, new_object_object(tmp));
+		}
+	}
+	append_array_object(createFunc, "set_options", sets);
 
 	if (probin == NULL)
 	{
@@ -3114,8 +3161,6 @@ deparse_CreateFunction(Oid objectId, Node *parsetree)
  *
  * Given a function OID and the parsetree that created it, return the JSON
  * blob representing the alter command.
- *
- * XXX this is missing the per-function custom-GUC thing.
  */
 static ObjTree *
 deparse_AlterFunction(Oid objectId, Node *parsetree)
@@ -3203,8 +3248,12 @@ deparse_AlterFunction(Oid objectId, Node *parsetree)
 									 defGetNumeric(defel));
 		}
 		else if (strcmp(defel->defname, "set") == 0)
-			elog(ERROR, "unimplemented deparse of ALTER FUNCTION SET");
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
 
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+		}
 		elems = lappend(elems, new_object_object(tmp));
 	}
 
-- 
2.3.0.149.gf3f4077.dirty

#20Stephen Frost
sfrost@snowman.net
In reply to: Andres Freund (#18)
Re: deparsing utility commands

Andres,

* Andres Freund (andres@2ndquadrant.com) wrote:

On 2015-02-21 14:51:32 -0500, Stephen Frost wrote:

It'd be *really* nice to be able to pass an object identifier to some
function and get back the CREATE (in particular, though perhaps DROP, or
whatever) command for it. This gets us *awful* close to that without
actually giving it to us and that's bugging me. The issue is the
parsetree, which I understand may be required in some cases (ALTER,
primairly, it seems?), but isn't always.

The USING and WITH CHECK quals can be extracted from the polForm also,
of course, it's what psql and pg_dump are doing, after all.

So, why depend on the parsetree? What about have another argument which
a user could use without the parsetree, for things that it's absolutely
required for today (eg: ALTER sub-command differentiation)?

I'm really not wild about pretty massively expanding the scope at the
eleventh hour. There's a fair number of commands where this the
deparsing command will give you a bunch of commands - look at CREATE
TABLE and CREATE SCHEMA ... for the most extreme examples. For those
there's no chance to do that without the parse tree available.

For my 2c, I don't agree with either of your assumptions above. I don't
see this as a massive expansion of the scope, nor that we're in the 11th
hour at this point. Agreed, it's the last commitfest, but we're near
the beginning of it and Alvaro has already made modifications to the
patch set, as is generally expected to happen for any patch in a
commitfest, without much trouble. The changes which I'm suggesting are
nearly trivial for the larger body of work and only slightly more than
trivial for the more complicated pieces.

Yes, it might be possible to use the same code for a bunch of minor
commands, but not for the interesting/complex stuff.

We can clearly rebuild at least CREATE commands for all objects without
access to the parse tree, obviously pg_dump manages somehow. I didn't
specify that a single command had to be used. Further, the actual
deparse_CreateStmt doesn't look like it'd have a terribly hard time
producing something without access to the parsetree.

The whole premise of this approach is that we're using the results of
the catalog changes as the basis for what's needed to recreate the
command. The places where we're referring to the parsetree look more
like a crutch of convenience than for any particular good reason to.
Consider this part of 0011 which includes deparse_CreateStmt:

/*
* 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.
*/

and then looking down through to deparse_ColumnDef, we see that the
ColumnDef passed in is used almost exclusivly for the *column name*
(which is used to look up the entry in pg_attribute..). Looping over
the pg_attribute entries for the table in the attnum order would
certainly return the same result, or something has gone wrong.

Beyond the inheiritance consideration, the only other reference actually
leads to what you argued (correctly, in my view) was a mistake in the
code- where a NOT NULL is omitted for the primary key case. Weren't you
also complaining about the 'OF type' form being a potential issue?
That'd also go away with the approach I'm advocating. In short, I
suspect that if this approach had been taken originally, at least some
of the concerns and issued levied against the current implementation
wouldn't exist. Thankfully, the way the code has been developed, the
majority of the code is general infrastructure and the changes I'm
suggesting are all in code which is simpler, thanks to that
infrastructure already being there.

All I'm suggesting is that we focus on collecting the information from
the catalog and avoid using the parsetree. For at least the CREATE and
DROP cases, that should be entirely possible. This does end up being a
bit different from the original goal, which was closer to "reproduce
exactly the command that was specified," but as shown above, that
probably wasn't what we ever really wanted. The original command is
ambiguous on a number of levels and even where it isn't we can get the
canonical information we need straight from the catalog.

Maybe that's possible and maybe it isn't, but at least for the CREATE
cases we should be able to avoid forcing a user to provide a built
parsetree, and that'd be *really* nice and open up this feature to
quite a few other use-cases that I can think of. I'd further suggest
that we provide these command to the SQL level, and then have a
wrapper which will take the name of an object, resolve it to Oid, and
then pass back the CREATE command for it.

I think this is a different feature that might end up sharing some of
the infrastructure, but not more.

I know you've looked through this code also and I really don't get the
comment that only "some" of this infrastructure would be shared. As I
tried to point out, for the 'CREATE POLICY' case, a few minor
modifications would have it provide exactly what I'm suggesting and I'm
sure that most of the cases would be similar. Simply looking through
the code with an eye towards avoiding the parsetree in favor of pulling
information from the catalog would illustrate the point I'm making, I
believe.

For as much commentary as there has been about event triggers and
replication and BDR, and about how this is going to end up being a
little-used side-feature, I'm amazed that there has been very little
discussion about how this would finally put into the backend the ability
to build up CREATE commands for objects and remove the need to use
pg_dump for that (something which isn't exactly fun for GUI applications
and other use-cases).

I don't think it's all that related, so I'm not surprised. If you
execute a CREATE TABLE(id serial primary key); you'll get a bunch of
commands with this facility - it'd be much less clear what exactly shall
be dumped in the case of some hypothetical GUI when you want to dump the
table.

I really don't think it's all that strange or complicated to consider
and we've got a rather nice example of what a good approach would be.

This may only apply to the CREATE and DROP cases, but that's no small
thing.

Apologies for taking so long to reply, it wasn't intentional.
Unfortunately, I'm not going to have a lot of time tomorrow either but I
hope to find time later in the week to resume review and perhaps to
provide code to back the approach I'm advocating.

Thanks!

Stephen

#21Andres Freund
andres@2ndquadrant.com
In reply to: Stephen Frost (#20)
Re: deparsing utility commands

Hi,

On 2015-02-23 19:48:43 -0500, Stephen Frost wrote:

Yes, it might be possible to use the same code for a bunch of minor
commands, but not for the interesting/complex stuff.

We can clearly rebuild at least CREATE commands for all objects without
access to the parse tree, obviously pg_dump manages somehow.

That's not the same. pg_dump recreates a static state, not running log
of changes that can be replayed.

I didn't specify that a single command had to be used.

Well, if you want to get 'the create table statement' you'll need to
decide what you want. With ddl event triggers it's clear - something
that, when replayed, results in the same catalog contents. Such an easy
definition doesn't, as far as I know, exist for a set of functions you
seem to imagine.

Further, the actual deparse_CreateStmt doesn't look like it'd have a
terribly hard time producing something without access to the
parsetree.

The interesting bit isn't the deparse_CreateStmt itself, but all the
stuff that's also triggered when you do a CREATE TABLE
nontrival-stuff;. utility.c will first transform the statement and then
run run and stash every created subcommand. And the subcommands will
*also* get deparsed.

Just think about what to do about CREATE TABLE foo(id serial primary
key, data text, bar_id REFERENCES foo.bar); - there's no way you can get
which other objects to dump from the catalog alone. What for a schema,
considering CREATE SCHEMA ... (schema_elements)+;?

Sure, individual subcommands could refer to the catalog instead of the
parse tree. But to get the whole thing you can't easily just refer to
it.

All I'm suggesting is that we focus on collecting the information from
the catalog and avoid using the parsetree. For at least the CREATE and
DROP cases, that should be entirely possible.

DROP's already in 9.4, the additions in 9.5 were more or less usability
things. The problem generating DROPs is right now more identifying
which one you want to drop and checking the dependencies - the latter
I'm not sure how to do without actually executing the DROP.

Maybe that's possible and maybe it isn't, but at least for the CREATE
cases we should be able to avoid forcing a user to provide a built
parsetree, and that'd be *really* nice and open up this feature to
quite a few other use-cases that I can think of. I'd further suggest
that we provide these command to the SQL level, and then have a
wrapper which will take the name of an object, resolve it to Oid, and
then pass back the CREATE command for it.

I think this is a different feature that might end up sharing some of
the infrastructure, but not more.

I know you've looked through this code also and I really don't get the
comment that only "some" of this infrastructure would be shared. As I
tried to point out, for the 'CREATE POLICY' case, a few minor
modifications would have it provide exactly what I'm suggesting and I'm
sure that most of the cases would be similar. Simply looking through
the code with an eye towards avoiding the parsetree in favor of pulling
information from the catalog would illustrate the point I'm making, I
believe.

I've no problem at all changing CREATE POLICY (and some other) deparse
functions to look more at the catalog than the command. What I don't see
is changing all of the create deparse functions, guarantee that they are
usable for getting the DDL of catalog objects and provide SQL
accessible infrastructure for that. That's a *massive* undertaking.

What I mean with 'sharing some of the infrastructure' is that I can see
a good portion of the deparse_* functions being reused for what you
desire.

But the decision about which of those to call will be an entirely
separate project. So is a whole new infrastructure to consider locking
and visibility (all the deparse stuff uses continualy evolving catalog
snapshots!) that it'll need as that's a problem the event trigger stuff
has much less to care about, because the objects are new rows and thus
can't be updated by other backends.

I don't think it's all that related, so I'm not surprised. If you
execute a CREATE TABLE(id serial primary key); you'll get a bunch of
commands with this facility - it'd be much less clear what exactly shall
be dumped in the case of some hypothetical GUI when you want to dump the
table.

I really don't think it's all that strange or complicated to consider
and we've got a rather nice example of what a good approach would be.

Right. We got a *massive* program that solves dependencies and doesn't
do all that much useful/correct things if you only let it dump
individual objects. And that dumping of individual objects is what you
want... ;)

Greetings,

Andres Freund

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

#22Stephen Frost
sfrost@snowman.net
In reply to: Andres Freund (#21)
Re: deparsing utility commands

* Andres Freund (andres@2ndquadrant.com) wrote:

Hi,

On 2015-02-23 19:48:43 -0500, Stephen Frost wrote:

Yes, it might be possible to use the same code for a bunch of minor
commands, but not for the interesting/complex stuff.

We can clearly rebuild at least CREATE commands for all objects without
access to the parse tree, obviously pg_dump manages somehow.

That's not the same. pg_dump recreates a static state, not running log
of changes that can be replayed.

There isn't anything particularly special about these function, that I
can see at least, when it comes to a 'running log' vs. 'static state'.

I didn't specify that a single command had to be used.

Well, if you want to get 'the create table statement' you'll need to
decide what you want. With ddl event triggers it's clear - something
that, when replayed, results in the same catalog contents. Such an easy
definition doesn't, as far as I know, exist for a set of functions you
seem to imagine.

This is really an orthogonal consideration. I'd draw a parallel to
CREATE TABLE .. LIKE, where you have to specify what you want. Perhaps
that's reason enough for this initial version to not be exposed out to
psql, but it doesn't change the point that going based off of the
catalog and not the parsetree would facilitate this capability.
Requiring the parsetree precludes any progress in this direction.

Further, the actual deparse_CreateStmt doesn't look like it'd have a
terribly hard time producing something without access to the
parsetree.

The interesting bit isn't the deparse_CreateStmt itself, but all the
stuff that's also triggered when you do a CREATE TABLE
nontrival-stuff;. utility.c will first transform the statement and then
run run and stash every created subcommand. And the subcommands will
*also* get deparsed.

Right, all of that is completely independent of deparse_CreateStmt and
there's nothing in your argument for why deparse_CreateStmt should get
access to or need the parsetree. If I'm following correctly, your
argument is that we shouldn't care that deparse_CreateStmt gets the
parsetree because of how it's getting called and the argument I'm making
is that deparse_CreateStmt really doesn't need the parsetree and if it
wasn't required then deparse_CreateStmt could serve additional
use-cases.

Just think about what to do about CREATE TABLE foo(id serial primary
key, data text, bar_id REFERENCES foo.bar); - there's no way you can get
which other objects to dump from the catalog alone. What for a schema,
considering CREATE SCHEMA ... (schema_elements)+;?

Sure, individual subcommands could refer to the catalog instead of the
parse tree. But to get the whole thing you can't easily just refer to
it.

I'm not entirely following this last sentence, but I'm encouragerd by
the agreement (if I understand correctly) that the subcommands could use
the catalog instead of the parsetree. The question of if that makes
them useful for other callers is perhaps up for some debate, but they
certainly look valuable from here. They may need to get extended to
have other options passed in (include defaults, include constraints,
etc) which then cause other sub-commands to be called (or perhaps
there's a larger function overall which is called and handles those
pieces and combines the results and the sub-command remains the same)
but all of that is good and interesting discussion which can happen if
we write these functions without the parsetree. With the parsetree
requirement, we can't really even have those discussions.

All I'm suggesting is that we focus on collecting the information from
the catalog and avoid using the parsetree. For at least the CREATE and
DROP cases, that should be entirely possible.

DROP's already in 9.4, the additions in 9.5 were more or less usability
things. The problem generating DROPs is right now more identifying
which one you want to drop and checking the dependencies - the latter
I'm not sure how to do without actually executing the DROP.

I'm a bit confused by this as executing the DROP is going to follow the
entries in pg_depend, which we could do also.. What am I missing there?

I think this is a different feature that might end up sharing some of
the infrastructure, but not more.

I know you've looked through this code also and I really don't get the
comment that only "some" of this infrastructure would be shared. As I
tried to point out, for the 'CREATE POLICY' case, a few minor
modifications would have it provide exactly what I'm suggesting and I'm
sure that most of the cases would be similar. Simply looking through
the code with an eye towards avoiding the parsetree in favor of pulling
information from the catalog would illustrate the point I'm making, I
believe.

I've no problem at all changing CREATE POLICY (and some other) deparse
functions to look more at the catalog than the command. What I don't see
is changing all of the create deparse functions, guarantee that they are
usable for getting the DDL of catalog objects and provide SQL
accessible infrastructure for that. That's a *massive* undertaking.

Perhaps we can agree to start with the first sentence in this paragraph
then? I'm not against waiting for the other pieces until we've had more
time to properly have that discussion.

What I mean with 'sharing some of the infrastructure' is that I can see
a good portion of the deparse_* functions being reused for what you
desire.

But the decision about which of those to call will be an entirely
separate project. So is a whole new infrastructure to consider locking
and visibility (all the deparse stuff uses continualy evolving catalog
snapshots!) that it'll need as that's a problem the event trigger stuff
has much less to care about, because the objects are new rows and thus
can't be updated by other backends.

For my part, at least, I'd want my current backend to show the tables
based on what has already happened and not based on some point prior, so
either this is exactly what is wanted or I've misunderstood your point
here. I realize that the same isn't exactly true for pg_dump, but
that's also not the use-case I was suggesting (though I do admit that it
might be interesting to figure out if there's a way for pg_dump to make
use of this, but that's an even farther afield discussion).

I don't think it's all that related, so I'm not surprised. If you
execute a CREATE TABLE(id serial primary key); you'll get a bunch of
commands with this facility - it'd be much less clear what exactly shall
be dumped in the case of some hypothetical GUI when you want to dump the
table.

I really don't think it's all that strange or complicated to consider
and we've got a rather nice example of what a good approach would be.

Right. We got a *massive* program that solves dependencies and doesn't
do all that much useful/correct things if you only let it dump
individual objects. And that dumping of individual objects is what you
want... ;)

"Dumping" as in pg_dump-style might be a bit beyond what I'm thinking
about at the moment. Regardless, as I've tried to point out above, the
changes which I'm actually suggesting for this initial body of work are
just to avoid the parsetree and go based off of what the catalog has.
I'm hopeful that's a small enough and reasonable enough change to happen
during this commitfest. I don't have any issue tabling the rest of the
discussion and future work based on that to a later time.

Thanks!

Stephen

#23Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#22)
Re: deparsing utility commands

Stephen Frost wrote:

Regardless, as I've tried to point out above, the
changes which I'm actually suggesting for this initial body of work are
just to avoid the parsetree and go based off of what the catalog has.
I'm hopeful that's a small enough and reasonable enough change to happen
during this commitfest. I don't have any issue tabling the rest of the
discussion and future work based on that to a later time.

At some point I did consider the idea that the CREATE cases of the
deparse code could be usable without a parse tree. For the specific
case of deparsing a DDL command as it's being ran, it's important to
have the parse tree: options such as IF EXISTS, OR REPLACE and others
don't live in the catalogs and so they must be obtained from the parse
tree. I think it is possible to adjust some of the code so that it can
run with a NULL parse tree, and set the options to empty in those cases;
that should return a usable command to re-create the object.

One point to bear in mind is that there is still some work left; and if
I need to additionally add extra interfaces so that this code can be
called from outside event triggers, I will not have any time to review
other people's patches from the commitfest. So here's my suggestion:
let this be pushed with the current interfaces only, with an eye towards
not adding obstacles to future support for a different interface.
That's more in the spirit of incremental improvement than trying to cram
everything in the initial commit.

I'm thinking something like
SELECT * FROM pg_creation_commands({'pg_class'::regclass, 'sometable'::pg_class});
would return a set of commands in the JSON-blob format that creates the
table. The input argument is an array of object addresses, so that you
can request creation commands for multiple objects. (It's not my
intention to provide this functionality right away, but if somebody else
wants to work on top of the current deparse patch, by my guest; if it
proves simple enough we can still get it into 9.5 as part of this
patch.)

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#24Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#23)
Re: deparsing utility commands

On 2015-02-24 10:48:38 -0300, Alvaro Herrera wrote:

Stephen Frost wrote:

Regardless, as I've tried to point out above, the
changes which I'm actually suggesting for this initial body of work are
just to avoid the parsetree and go based off of what the catalog has.
I'm hopeful that's a small enough and reasonable enough change to happen
during this commitfest. I don't have any issue tabling the rest of the
discussion and future work based on that to a later time.

At some point I did consider the idea that the CREATE cases of the
deparse code could be usable without a parse tree. For the specific
case of deparsing a DDL command as it's being ran, it's important to
have the parse tree: options such as IF EXISTS, OR REPLACE and others
don't live in the catalogs and so they must be obtained from the parse
tree.

Yep.

One point to bear in mind is that there is still some work left; and if
I need to additionally add extra interfaces so that this code can be
called from outside event triggers, I will not have any time to review
other people's patches from the commitfest. So here's my suggestion:
let this be pushed with the current interfaces only, with an eye towards
not adding obstacles to future support for a different interface.
That's more in the spirit of incremental improvement than trying to cram
everything in the initial commit.

+1

I'm thinking something like
SELECT * FROM pg_creation_commands({'pg_class'::regclass, 'sometable'::pg_class});

would return a set of commands in the JSON-blob format that creates the
table. The input argument is an array of object addresses, so that you
can request creation commands for multiple objects. (It's not my
intention to provide this functionality right away, but if somebody else
wants to work on top of the current deparse patch, by my guest; if it
proves simple enough we can still get it into 9.5 as part of this
patch.)

I think that'd be usefuful functionality, but I can't see it being
realistic for 9.5 unless somebody starts working on it right now with
full force.

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

#25Ryan Pedela
rpedela@datalanche.com
In reply to: Alvaro Herrera (#23)
Re: deparsing utility commands

On Tue, Feb 24, 2015 at 6:48 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Stephen Frost wrote:

I'm thinking something like
SELECT * FROM pg_creation_commands({'pg_class'::regclass,
'sometable'::pg_class});
would return a set of commands in the JSON-blob format that creates the
table. The input argument is an array of object addresses, so that you
can request creation commands for multiple objects. (It's not my
intention to provide this functionality right away, but if somebody else
wants to work on top of the current deparse patch, by my guest; if it
proves simple enough we can still get it into 9.5 as part of this
patch.)

+1

Another possible function could be to diff two relations to produce a set
of DDL commands that could be used for schema migrations.

Also thank you very much! CREATE/ALTER support in event triggers is the
upcoming feature I am most excited about, and I am happy to see progress.

Thanks,
Ryan Pedela

#26Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andres Freund (#16)
44 attachment(s)
Re: deparsing utility commands

Andres Freund wrote:

Here's a new set of patches. Several fixes have been merged into the
patches themselves, before rebasing (in particular, some more commands,
such as DefineRelation and AlterDomain routines are now returning
ObjectAddress rather than OID). I have accumulated some newer fixes as
fixup commits at the end of the series. I have handled some of your
comments that needed code changes; more changes pending. Additionally:

On 2015-02-15 01:48:15 -0300, Alvaro Herrera wrote:

One line of defense which I just tought about is that instead of
sprinkling EventTriggerStashCommand() calls all over ProcessUtilitySlow,
we should add only one at the bottom.

Doesn't sound like a bad idea, but I'm not sure whether it's realistic
given that some commands do multiple EventTriggerStashCommand()s?

What the new code does is to have a boolean flag, default false, and
only auto-stash commands if the flag is false. Commands that process
stuff differently need to set the flag to true (meaning "already
stashed") and then the auto-stash step is skipped.

1. ruleutils.c seems to deparse temp schemas as pg_temp_X instead of
just pg_temp; so some commands like CREATE VIEW..AS SELECT FROM temp_table
fail in an ugly way. Maybe the solution is to tell ruleutils that the
temp schema is always visible; or maybe the solution is to tell it that
it's spelled pg_temp instead.

Hm. I'm not immediately following why that's happening for deparsing but
not for the normal get_viewdef stuff?

Haven't researched this closely yet, but I think it's a minor bug.

@@ -1060,6 +1066,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_CAST:
case OBJECT_COLUMN:
case OBJECT_COLLATION:
+ case OBJECT_COMPOSITE:
case OBJECT_CONVERSION:
case OBJECT_DEFAULT:
case OBJECT_DOMAIN:
@@ -1088,6 +1095,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
case OBJECT_TYPE:
+ case OBJECT_USER_MAPPING:
case OBJECT_VIEW:
return true;
}

Should those two be broken out and just committed?

No, those two symbols are created by the infrastructure patch because
they are not required by the current code.

@@ -1193,14 +1201,6 @@ EventTriggerBeginCompleteQuery(void)
EventTriggerQueryState *state;
MemoryContext cxt;

-	/*
-	 * Currently, sql_drop and table_rewrite events are the only reason to
-	 * have event trigger state at all; so if there are none, don't install
-	 * one.
-	 */
-	if (!trackDroppedObjectsNeeded())
-		return false;
-
cxt = AllocSetContextCreate(TopMemoryContext,
"event trigger state",
ALLOCSET_DEFAULT_MINSIZE,
@@ -1211,7 +1211,7 @@ EventTriggerBeginCompleteQuery(void)
slist_init(&(state->SQLDropList));
state->in_sql_drop = false;
state->table_rewrite_oid = InvalidOid;
-
+	state->stash = NIL;
state->previous = currentEventTriggerState;
currentEventTriggerState = state;

Hm. I'm not clear why we don't check for other types of event triggers
in EventTriggerBeginCompleteQuery and skip the work otherwise. If we
just enable them all the time - which imo is ok given how they're split
of in the normal process utility - we should remove the boolean return
value from Begin/CompleteQuery and drop
* Note: it's an error to call this routine if EventTriggerBeginCompleteQuery
* returned false previously.
from EndCompleteQuery.

I think this merits some more checking, to avoid event trigger overhead
when there are no triggers that are going to use this mechanism.

+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);

It's a bit wierd that the drop list is managed using a slist, but the
stash isn't...

Yeah, I think I'll change the stash to an slist too. I used List for

+				/*
+				 * Obtain schema name, if any ("pg_temp" if a temp object)
+				 */
+				if (is_objectclass_supported(addr.classId))
+				{
+					AttrNumber	nspAttnum;
+
+					nspAttnum = get_object_attnum_namespace(addr.classId);
+					if (nspAttnum != InvalidAttrNumber)
+					{
+						Relation	catalog;
+						HeapTuple	objtup;
+						Oid			schema_oid;
+						bool		isnull;
+
+						catalog = heap_open(addr.classId, AccessShareLock);
+						objtup = get_catalog_object_by_oid(catalog,
+														   addr.objectId);
+						if (!HeapTupleIsValid(objtup))
+							elog(ERROR, "cache lookup failed for object %u/%u",
+								 addr.classId, addr.objectId);
+						schema_oid = heap_getattr(objtup, nspAttnum,
+												  RelationGetDescr(catalog), &isnull);
+						if (isnull)
+							elog(ERROR, "invalid null namespace in object %u/%u/%d",
+								 addr.classId, addr.objectId, addr.objectSubId);
+						if (isAnyTempNamespace(schema_oid))
+							schema = pstrdup("pg_temp");
+						else
+							schema = get_namespace_name(schema_oid);
+
+						heap_close(catalog, AccessShareLock);
+					}
+				}

Hm. How do we get here for !is_objectclass_supported()?

The whole idea here is that object classes that are not "supported" (by
the objectaddress.c generic stuff) then it's not an object type that
lives within a schema. The main point of this block is to obtain schema
name, so objects that don't live in schemas obviously continue to have
schema = NULL.

+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{

Would look nicer if boolval et al weren't declared - I'd just pass the
va_arg() return value directly to new_*_object().

Attached as 0001.

Additionally I find the separate handling of ObjTypeNull not so
nice. 0002.

Both nice, thanks. Folded into infrastructure commit.

+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{

+ append_bool_object(typeParam, "is_array", is_array);

Maybe name that one typarray?

Done. I also fixed an issue with timestamp/timestamptz/intervals when
there were arrays of those, which I noticed a few days ago while fooling
with the test framework.

+ * Handle deparsing of simple commands.
+ *
+ * This function contains a large switch that mirrors that in
+ * ProcessUtilitySlow.  All cases covered there should also be covered here.
+ */
+static ObjTree *
+deparse_simple_command(StashedCommand *cmd)
+{

It is not clear to me why some commands error out and other don't. It
makes sense to me to error out for things like GrantStmt that shouldn't
end up here, but... I guess that's just an artifact of the patch series
because you add the handling for the non elog()ing commands later?

Yeah, exactly. In this one I added elog(ERROR) to all cases in the
infrastructure patch. Note that the intention is that all command types
are supported by the time we push, and therefore this:

I think this should use ereport() in some cases if we're not going to
support some commands for now.

is unnecessary.

+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * in order to obtain deparsed versions of expressions.  In such results,
+	 * we want all object names to be qualified, so that results are "portable"
+	 * to environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = false;
+	PushOverrideSearchPath(overridePath);

Ah, the 'addTemp = false' probably is the answer to my question above
about view deparsing adding a fully qualified pg_temp...

Not real sure about that. Might be. But I vaguely recall I needed
isTemp=false for something else.

+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ * n		expand as a simple number (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.
+ *------

I think this documentation should at least be referred to from
comments in deparse_utility.c. I was wondering where it is.

I'll add a reference.

diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 6fbe283..a842ef2 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -416,7 +416,7 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
{
bool		first = true;
JsonbIterator *it;
-	int			type = 0;
+	JsonbIteratorToken type = WJB_DONE;
JsonbValue	v;
int			level = 0;
bool		redo_switch = false;
@@ -498,7 +498,7 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
first = false;
break;
default:
-				elog(ERROR, "unknown flag of jsonb iterator");
+				elog(ERROR, "unknown jsonb iterator token type");
}
}

Huh?

Apologies -- should be somewhere separate, I guess.

I think this infrastructure pretty desperately requires some higher
level overview. Like how are we going from the node tree, to the object
tree, to jsonb, to actual DDL. I think I understand, but it took me a
while. Doesn't have to be long and super detailed...

Will add.

From dcb353c8c9bd93778a62719ff8bf32b9a419e16d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org> Date: Thu, 25 Sep 2014
15:54:00 -0300 Subject: [PATCH 09/42] deparse: Support CREATE TYPE AS

+static ObjTree *
+deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
+				  ColumnDef *coldef)
+{
+	if (!composite)
+	{
+		/*
+		 * 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.  Therefore,
+		 * we scan the list of constraints attached to this column to determine
+		 * whether we need to emit anything.
+		 * (Fortunately, NOT NULL constraints cannot be table constraints.)
+		 */

Is the primary key bit really a problem? To me it sounds like it's
actually good to retain a NOT NULL in that case. Note that pg_dump
actually *does* emit a NOT NULL here, even if you drop the primary
key. Since the column behaves as NOT NULL and is dumped as such it seems
like a good idea to fully treat it as such.

I think pg_dump is wrong -- as far as I recall, the standard wants you
to drop the NOT NULL bit if you drop the primary key, which would
probably not happen if you just add NOT NULL blindly. My patch to
catalog not null constraints tries to handle this .. but of course,
that's still dormant.

+	list = NIL;
+	if (node->deferrable)
+		list = lappend(list,
+					   new_string_object("DEFERRABLE"));
+	if (node->initdeferred)
+		list = lappend(list,
+					   new_string_object("INITIALLY DEFERRED"));

I mildly wonder if DEFERRABLE/INITIALLY DEFERRED shouldn't instead have
an 'is_present' style representation.

Yeah, good point. Many other clauses in various commands probably need
the same thing.

+/*
+ * deparse_CreateStmt
+ *		Deparse a CreateStmt (CREATE TABLE)

I really wish CreateStmt were named CreateTableStmt. I don't know how
often that tripped me over, let alone everyone. Yes, that's an unrelated
rant ;)

Agreed.

+ * Given a table OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{

...

+	tmp = new_objtree_VA("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);

Hm. I had not thought about that before, but given that
default_tablespace exists, we should maybe somehow indicate which one
was chosen?

Hmm, yeah, good point. We do likewise for WITH/WITHOUT OIDS, for
instance, so that's fair. Will add.

+	if (!ownedby)
+		/* XXX this shouldn't happen ... */
+		ownedby = new_objtree_VA("OWNED BY %{owner}D",
+								 3,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeNull,
+								 "present", ObjTypeBool, false);

Why shouldn't this happen? Free standing sequences are perfectly normal?
And OWNED BY NONE will need to be deparseable.

Mumble.

+static ObjTree *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{

+ /* we purposefully do not emit OWNED BY here */

Needs explanation about the reason. Hm. I don't think it's actually
correct. Right now the following won't be deparsed correctly:

CREATE TABLE seqowner(id int);
CREATE SEQUENCE seqowned OWNED BY seqowner.id;
that generates
CREATE TABLE public.seqowner (id pg_catalog.int4 ) WITH (oids=OFF)
CREATE SEQUENCE public.seqowned CACHE 1 NO CYCLE INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 RESTART 1

I think I was blindfolded into supporting SERIAL. Will look into this.

+ * 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?

I tend to think it's ok. Might be worthwhile to add the final extension
version in some unused attribute?

Yes, agreed.

It supports everything but functions, aggregates, operator classes and
operator families.

AFAICS those are implemented, and you updated the description in git
since.

Yeah, sorry. I just changed the way we handle functions/aggregates as
you suggest, by adding format_procedure_args(). However, in looking
closer, the representation is not very nice, because you end up with
veyr ugly quoting:

alvherre=# alter function "test schema"."foo bar" ("test schema"."test type") rename to "ack ick";
NOTICE: JSON blob: {
"fmt": "ALTER FUNCTION %{identity}s RENAME TO %{newname}I",
"identity": "\"test schema\".\"ack ick\"(\"test schema\".\"test type\")",
"newname": "ack ick"
}
NOTICE: expanded: ALTER FUNCTION "test schema"."ack ick"("test schema"."test type") RENAME TO "ack ick"
ALTER FUNCTION

Note the identity bit. I think I should use a signature object like
CREATE FUNCTION, which ends up a lot more verbose but seems more usable
programmatically:

"signature": {
"arguments": [
{
"default": {
"fmt": "DEFAULT %{value}s",
"present": false
},
"fmt": "%{mode}s %{name}s %{type}T %{default}s",
"mode": "IN",
"name": {
"fmt": "%{name}I",
"name": "a",
"present": true
},
"type": {
"schemaname": "test schema",
"typarray": false,
"typename": "test type",
"typmod": ""
}
}
],
"fmt": "%{identity}D(%{arguments:, }s)",
"identity": {
"objname": "test function",
"schemaname": "test schema"
}

(ALTER FUNCTION RENAME has no need for things such as DEFAULT and INOUT,
though, I think, so it might end up being simpler than this)

+static ObjTree *
+deparse_RenameStmt(Oid objectId, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree	   *renameStmt;
+	char	   *fmtstr;
+	Relation	relation;
+	Oid			schemaId;
+
+	/*
+	 * FIXME --- this code is missing support for inheritance behavioral flags,
+	 * i.e. the "*" and ONLY elements.
+	 */

Hm. I think we probably need that?

Sure.

+	 * 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.
+	 */

We should probably prohibit DDL from within event triggers?

Not real sure about adding that restriction. Would make things simpler,
sure.

+	switch (node->renameType)
+	{
+		case OBJECT_COLLATION:
+		case OBJECT_CONVERSION:
+		case OBJECT_DOMAIN:
+		case OBJECT_TSDICTIONARY:
+		case OBJECT_TSPARSER:
+		case OBJECT_TSTEMPLATE:
+		case OBJECT_TSCONFIGURATION:
+		case OBJECT_TYPE:
+			{
+				char	   *identity;
+				HeapTuple	objTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				Oid			classId = get_objtype_catalog_oid(node->renameType);
+				AttrNumber	Anum_namespace = get_object_attnum_namespace(classId);
+
+				catalog = relation_open(classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, objectId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				identity = psprintf("%s.%s", get_namespace_name(DatumGetObjectId(objnsp)),
+									strVal(llast(node->object)));
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));

Is that correct? I.e. will it work for a namespace that needs to be
quoted? There's a couple of these. Afaics they all should use
new_objtree_for_qualname and %{}D? I guess in some cases, like the type,
that could be slightly more complicated, but I guess it has to be done
nonetheless?

Well, the quoting could be solved by adding a call to
quote_qualified_identifier(), but you're right that it would be simpler
to use %{}D here. It would apply similarly to the function example I
gave above.

+ case OBJECT_AGGREGATE:
+ case OBJECT_FUNCTION:

+				/*
+				 * Generating a function/aggregate identity is altogether too
+				 * messy, so instead of doing it ourselves, we generate one for
+				 * the renamed object, then strip out the name and replace it
+				 * with the original name from the parse node.  This is so ugly
+				 * that we don't dare do it for any other object kind.
+				 */

Yuck.

I think it'd be just as easy to split the argument output from the
function name output in format_procedure_internal, and make that
separately callable.

Fixed, at least partially.

Subject: [PATCH 18/42] deparse: Support CREATE FUNCTION

/*
+ * 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.
+ */

Support attached as 0003.

Thanks, folded.

+void
+EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_AlterTable;

Hm. So +EventTriggerComplexCmdStart is hardcoded to use SCT_AlterTable? That's a bit odd.

Yeah, that's broken, isn't it. My initial thought was that we would
have Simple commands and Complex commands only. Only later I grew the
SCT_Grant case, and it's becoming obvious that I will need
SCT_AlterDefaultPrivileges and SCT_AlterOpFamily, at least. I will
probably rename these functions from StuffComplexCommandSomething into
StuffAlterTableSomething, to make it clear that they only serve
AlterTable.

+/*
+ * 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.
+ */

I think that'd generally be better than requiring PG_CATCH.

Haven't done anything about this yet.

+static ObjTree *
+deparse_AlterTableStmt(StashedCommand *cmd)
+{
+	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(tmp));
+				break;

Misses ONLY handling?

Yeah -- the "Recurse" bit in the subtype tells us whether to add ONLY or
not, I think.

+			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(tmp));
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;

Hm, didn't you have a out of line version of this somewhere?

It's similar, but as far as I remember the other one was different.
Didn't look closely, though.

+ case AT_AlterColumnType:
+ {

+					/*
+					 * Error out if the USING clause was used.  We cannot use
+					 * it directly here, because it needs to run through
+					 * transformExpr() before being usable for ruleutils.c, and
+					 * we're not in a position to transform it ourselves.  To
+					 * fix this problem, tablecmds.c needs to be modified to store
+					 * the transformed expression somewhere in the StashedATSubcmd.
+					 */
+					tmp2 = new_objtree_VA("USING %{expression}s", 0);
+					if (def->raw_default)
+						elog(ERROR, "unimplemented deparse of ALTER TABLE TYPE USING");
+					else
+						append_bool_object(tmp2, "present", false);
+					append_object_object(tmp, "using", tmp2);

Hm. This probably needs to be fixed :(.

Sure.

Shouldn't be too hard...

Could you perhaps attach some string that can be searched for to all
commands that you think need to be implemented before being committable?

Well, I think all the ones that say "unimplemented" should be
implemented before commit. Most of the remaining ones should be
relatively simple to add, with the two exceptions I mentioned above that
require their own symbol in the SCT enum.

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);
}

Hm. Commands are always ordered in a way this is ok?

Well, as far as I can tell this works well. If we had a concrete
example of a problematic sequence of commands, we could look into how to
fix it.

+	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));

Hm. Amname is unquoted? I can't believe this will ever really be a problem, but still.

That's a bug -- fixed.

Subject: [PATCH 24/42] deparse: support ALTER THING OWNER TO
static ObjTree *
+deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+	ObjTree	   *ownerStmt;
+	ObjectAddress addr;
+	char	   *fmt;
+
+	fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newname}I",
+				   stringify_objtype(node->objectType));

newname sounds like copied from rename.

Oops, true. Renamed to "newowner".

+			else
+			{
+				Assert(cmd->type == SCT_Grant);
+
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;

Might actually be nice to fill those out to the object that's being
changed. But it might end up being more work than justified, and it can
be easily added later. I guess it'd also mean we would have to generate
multiple GRANT/REVOKE commands.

Nah -- a single GRANT command can modify several objects of the same
kind. That's why I used a separate representation.

diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c09915d..92bccf5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -781,6 +781,7 @@ standard_ProcessUtility(Node *parsetree,
else
ExecuteGrantStmt((GrantStmt *) parsetree);
}
+			break;

Uh. Was that missing from an earlier commit?

Yep, sorry, fixed. (In fact I already pushed it.)

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

Maybe we should name it aclchk_internal.h?

Might as well ...

+static ObjTree *
+deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	HeapTuple	constrTup;
+	Form_pg_constraint constrForm;
+	char	   *fmt;
+	ObjectAddress addr;
+
+	Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
+
+	if (node->comment)
+		fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}L",
+					   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	else
+		fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS NULL",
+					   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");

psprintf(...' IS %s', .... node->comment ? "%{comment}L" : "NULL") maybe?

I fixed COMMENT and SECURITY LABEL to use a different, more complex
representation, that can be used to turn the comment literal and the
NULL part on/off. That seems better for programmatic access.

+	if (node->label)
+	{
+		fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}L",
+				   stringify_objtype(node->objtype));
+		label = new_objtree_VA(fmt, 0);
+
+		append_string_object(label, "label", node->label);
+	}

"deparse: Handle default security provider." should be squashed into this.

No, actually I think that one is wrong. The security provider should be
returned as an Oid into secondaryOid to ProcessUtilitySlow, to avoid
messing with the parse node.

+	if (node->relkind == OBJECT_TABLE)
+	{
+		tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0);
+		switch (node->into->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);
+	}

That switch already exists somewhere else? Maybe add a function that
converts ONCOMMIT_* into the relevant string?

Some refactoring is probably in order here.

Ran out of energy here...

You had a lot of it -- this stuff isn't small. Many, many thanks for
the review.

Subject: [PATCH 2/3] fixup! deparse: infrastructure needed for command
deparsing

Slight cleanup in new_objtree_VA() by treating ObjTypeNull just like the
other types.

BTW these two patches created a compiler warning for me; it says "elem
can be used uninitialized". I had to add a default clause.

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

Attachments:

0001-Remove-OBJECT_ATTRIBUTE.patchtext/x-diff; charset=us-asciiDownload
>From fb4de0c4adf2d8dd04bd3c2ca79a715787018d43 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Feb 2015 17:02:25 -0300
Subject: [PATCH 01/44] Remove OBJECT_ATTRIBUTE

All cases in which it is used can be processed as OBJECT_COLUMN instead;
there is no functional difference.
---
 src/backend/catalog/objectaddress.c  | 1 -
 src/backend/commands/alter.c         | 1 -
 src/backend/commands/event_trigger.c | 1 -
 src/backend/parser/gram.y            | 2 +-
 src/backend/tcop/utility.c           | 3 ---
 src/include/nodes/parsenodes.h       | 1 -
 6 files changed, 1 insertion(+), 8 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d899dd7..1da38c0 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1591,7 +1591,6 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_TYPE:
 		case OBJECT_DOMAIN:
-		case OBJECT_ATTRIBUTE:
 		case OBJECT_DOMCONSTRAINT:
 			if (!pg_type_ownercheck(address.objectId, roleid))
 				aclcheck_error_type(ACLCHECK_NOT_OWNER, address.objectId);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 78b54b4..7f85fa4 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -330,7 +330,6 @@ ExecRenameStmt(RenameStmt *stmt)
 			return RenameRelation(stmt);
 
 		case OBJECT_COLUMN:
-		case OBJECT_ATTRIBUTE:
 			return renameatt(stmt);
 
 		case OBJECT_RULE:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index dcf5b98..d14346f 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1056,7 +1056,6 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 			/* no support for event triggers on event triggers */
 			return false;
 		case OBJECT_AGGREGATE:
-		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
 		case OBJECT_COLUMN:
 		case OBJECT_COLLATION:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6c21002..8ad933d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -7848,7 +7848,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
 			| ALTER TYPE_P any_name RENAME ATTRIBUTE name TO name opt_drop_behavior
 				{
 					RenameStmt *n = makeNode(RenameStmt);
-					n->renameType = OBJECT_ATTRIBUTE;
+					n->renameType = OBJECT_COLUMN;
 					n->relationType = OBJECT_TYPE;
 					n->relation = makeRangeVarFromAnyName($3, @3, yyscanner);
 					n->subname = $6;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6d26986..2b6fc3e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1615,9 +1615,6 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_AGGREGATE:
 			tag = "ALTER AGGREGATE";
 			break;
-		case OBJECT_ATTRIBUTE:
-			tag = "ALTER TYPE";
-			break;
 		case OBJECT_CAST:
 			tag = "ALTER CAST";
 			break;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ac13302..09607eb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1212,7 +1212,6 @@ typedef struct SetOperationStmt
 typedef enum ObjectType
 {
 	OBJECT_AGGREGATE,
-	OBJECT_ATTRIBUTE,			/* type's attribute, when distinct from column */
 	OBJECT_CAST,
 	OBJECT_COLUMN,
 	OBJECT_COLLATION,
-- 
2.1.4

0002-Have-RENAME-routines-return-ObjectAddress-rather-tha.patchtext/x-diff; charset=us-asciiDownload
>From 7cf60d343620fd0f862af6b38e8d3164a62c287f Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Feb 2015 17:24:04 -0300
Subject: [PATCH 02/44] Have RENAME routines return ObjectAddress rather than
 OID

This lets them include an objectSubId when appropriate (i.e. when
renaming columns), and additionally they make the containing catalog OID
available for further processing.  The object OID that was previously
returned is still available in the objectId field of the returned
ObjectAddress.

This isn't terribly exciting in itself but it supports future event
trigger changes.

Discussion: 20150218213255.GC6717@tamriel.snowman.net
Reviewed-By: Andres Freund, Stephen Frost
---
 src/backend/catalog/objectaddress.c |  6 ++++
 src/backend/commands/alter.c        |  8 ++++--
 src/backend/commands/dbcommands.c   |  7 +++--
 src/backend/commands/policy.c       |  7 +++--
 src/backend/commands/schemacmds.c   |  7 +++--
 src/backend/commands/tablecmds.c    | 55 +++++++++++++++++++++++--------------
 src/backend/commands/tablespace.c   |  7 +++--
 src/backend/commands/trigger.c      |  7 +++--
 src/backend/commands/typecmds.c     |  6 ++--
 src/backend/commands/user.c         |  7 +++--
 src/backend/rewrite/rewriteDefine.c |  7 +++--
 src/include/catalog/objectaddress.h | 12 ++++++++
 src/include/commands/alter.h        |  3 +-
 src/include/commands/dbcommands.h   |  3 +-
 src/include/commands/policy.h       |  3 +-
 src/include/commands/schemacmds.h   |  3 +-
 src/include/commands/tablecmds.h    |  9 ++++--
 src/include/commands/tablespace.h   |  3 +-
 src/include/commands/trigger.h      |  3 +-
 src/include/commands/typecmds.h     |  2 +-
 src/include/commands/user.h         |  3 +-
 src/include/rewrite/rewriteDefine.h |  3 +-
 22 files changed, 120 insertions(+), 51 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 1da38c0..90bbb99 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -531,6 +531,12 @@ ObjectTypeMap[] =
 	{ "policy", OBJECT_POLICY }
 };
 
+const ObjectAddress InvalidObjectAddress =
+{
+	InvalidOid,
+	InvalidOid,
+	0
+};
 
 static ObjectAddress get_object_address_unqualified(ObjectType objtype,
 							   List *qualname, bool missing_ok);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 7f85fa4..c8901be 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -299,8 +299,10 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 /*
  * Executes an ALTER OBJECT / RENAME TO statement.  Based on the object
  * type, the function appropriate to that type is executed.
+ *
+ * Return value is the address of the renamed object.
  */
-Oid
+ObjectAddress
 ExecRenameStmt(RenameStmt *stmt)
 {
 	switch (stmt->renameType)
@@ -377,13 +379,13 @@ ExecRenameStmt(RenameStmt *stmt)
 										   stmt->newname);
 				heap_close(catalog, RowExclusiveLock);
 
-				return address.objectId;
+				return address;
 			}
 
 		default:
 			elog(ERROR, "unrecognized rename stmt type: %d",
 				 (int) stmt->renameType);
-			return InvalidOid;	/* keep compiler happy */
+			return InvalidObjectAddress;	/* keep compiler happy */
 	}
 }
 
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 5e66961..9747dd0 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -938,7 +938,7 @@ dropdb(const char *dbname, bool missing_ok)
 /*
  * Rename database
  */
-Oid
+ObjectAddress
 RenameDatabase(const char *oldname, const char *newname)
 {
 	Oid			db_id;
@@ -946,6 +946,7 @@ RenameDatabase(const char *oldname, const char *newname)
 	Relation	rel;
 	int			notherbackends;
 	int			npreparedxacts;
+	ObjectAddress address;
 
 	/*
 	 * Look up the target database's OID, and get exclusive lock on it. We
@@ -1013,12 +1014,14 @@ RenameDatabase(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(DatabaseRelationId, db_id, 0);
 
+	ObjectAddressSet(address, DatabaseRelationId, db_id);
+
 	/*
 	 * Close pg_database, but keep lock till commit.
 	 */
 	heap_close(rel, NoLock);
 
-	return db_id;
+	return address;
 }
 
 
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d98da0d..68bad97 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -837,7 +837,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
  * rename_policy -
  *   change the name of a policy on a relation
  */
-Oid
+ObjectAddress
 rename_policy(RenameStmt *stmt)
 {
 	Relation		pg_policy_rel;
@@ -847,6 +847,7 @@ rename_policy(RenameStmt *stmt)
 	ScanKeyData		skey[2];
 	SysScanDesc		sscan;
 	HeapTuple		policy_tuple;
+	ObjectAddress	address;
 
 	/* Get id of table.  Also handles permissions checks. */
 	table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
@@ -925,6 +926,8 @@ rename_policy(RenameStmt *stmt)
 	InvokeObjectPostAlterHook(PolicyRelationId,
 							  HeapTupleGetOid(policy_tuple), 0);
 
+	ObjectAddressSet(address, PolicyRelationId, opoloid);
+
 	/*
 	 * Invalidate relation's relcache entry so that other backends (and
 	 * this one too!) are sent SI message to make them rebuild relcache
@@ -937,7 +940,7 @@ rename_policy(RenameStmt *stmt)
 	heap_close(pg_policy_rel, RowExclusiveLock);
 	relation_close(target_table, NoLock);
 
-	return opoloid;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index a44dbf4..77b5e21 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -195,13 +195,14 @@ RemoveSchemaById(Oid schemaOid)
 /*
  * Rename schema
  */
-Oid
+ObjectAddress
 RenameSchema(const char *oldname, const char *newname)
 {
 	Oid			nspOid;
 	HeapTuple	tup;
 	Relation	rel;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	rel = heap_open(NamespaceRelationId, RowExclusiveLock);
 
@@ -243,10 +244,12 @@ RenameSchema(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(NamespaceRelationId, HeapTupleGetOid(tup), 0);
 
+	ObjectAddressSet(address, NamespaceRelationId, nspOid);
+
 	heap_close(rel, NoLock);
 	heap_freetuple(tup);
 
-	return nspOid;
+	return address;
 }
 
 void
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f5d5b63..45e01f9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2158,8 +2158,10 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 
 /*
  *		renameatt_internal		- workhorse for renameatt
+ *
+ * Return value is the attribute number in the 'myrelid' relation.
  */
-static void
+static AttrNumber
 renameatt_internal(Oid myrelid,
 				   const char *oldattname,
 				   const char *newattname,
@@ -2172,7 +2174,7 @@ renameatt_internal(Oid myrelid,
 	Relation	attrelation;
 	HeapTuple	atttup;
 	Form_pg_attribute attform;
-	int			attnum;
+	AttrNumber	attnum;
 
 	/*
 	 * Grab an exclusive lock on the target table, which we will NOT release
@@ -2300,6 +2302,8 @@ renameatt_internal(Oid myrelid,
 	heap_close(attrelation, RowExclusiveLock);
 
 	relation_close(targetrelation, NoLock);		/* close rel but keep lock */
+
+	return attnum;
 }
 
 /*
@@ -2322,11 +2326,15 @@ RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 /*
  *		renameatt		- changes the name of a attribute in a relation
+ *
+ * The returned ObjectAddress is that of the pg_class address of the column.
  */
-Oid
+ObjectAddress
 renameatt(RenameStmt *stmt)
 {
 	Oid			relid;
+	AttrNumber	attnum;
+	ObjectAddress address;
 
 	/* lock level taken here should match renameatt_internal */
 	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
@@ -2339,26 +2347,27 @@ renameatt(RenameStmt *stmt)
 		ereport(NOTICE,
 				(errmsg("relation \"%s\" does not exist, skipping",
 						stmt->relation->relname)));
-		return InvalidOid;
+		return InvalidObjectAddress;
 	}
 
-	renameatt_internal(relid,
-					   stmt->subname,	/* old att name */
-					   stmt->newname,	/* new att name */
-					   interpretInhOption(stmt->relation->inhOpt),		/* recursive? */
-					   false,	/* recursing? */
-					   0,		/* expected inhcount */
-					   stmt->behavior);
+	attnum =
+		renameatt_internal(relid,
+						   stmt->subname,	/* old att name */
+						   stmt->newname,	/* new att name */
+						   interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+						   false,	/* recursing? */
+						   0,		/* expected inhcount */
+						   stmt->behavior);
 
-	/* This is an ALTER TABLE command so it's about the relid */
-	return relid;
-}
+	ObjectAddressSubSet(address, RelationRelationId, relid, attnum);
 
+	return address;
+}
 
 /*
  * same logic as renameatt_internal
  */
-static Oid
+static ObjectAddress
 rename_constraint_internal(Oid myrelid,
 						   Oid mytypid,
 						   const char *oldconname,
@@ -2371,6 +2380,7 @@ rename_constraint_internal(Oid myrelid,
 	Oid			constraintOid;
 	HeapTuple	tuple;
 	Form_pg_constraint con;
+	ObjectAddress	address;
 
 	AssertArg(!myrelid || !mytypid);
 
@@ -2446,15 +2456,17 @@ rename_constraint_internal(Oid myrelid,
 	else
 		RenameConstraintById(constraintOid, newconname);
 
+	ObjectAddressSet(address, ConstraintRelationId, constraintOid);
+
 	ReleaseSysCache(tuple);
 
 	if (targetrelation)
 		relation_close(targetrelation, NoLock); /* close rel but keep lock */
 
-	return constraintOid;
+	return address;
 }
 
-Oid
+ObjectAddress
 RenameConstraint(RenameStmt *stmt)
 {
 	Oid			relid = InvalidOid;
@@ -2497,10 +2509,11 @@ RenameConstraint(RenameStmt *stmt)
  * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE
  * RENAME
  */
-Oid
+ObjectAddress
 RenameRelation(RenameStmt *stmt)
 {
 	Oid			relid;
+	ObjectAddress address;
 
 	/*
 	 * Grab an exclusive lock on the target table, index, sequence, view,
@@ -2520,13 +2533,15 @@ RenameRelation(RenameStmt *stmt)
 		ereport(NOTICE,
 				(errmsg("relation \"%s\" does not exist, skipping",
 						stmt->relation->relname)));
-		return InvalidOid;
+		return InvalidObjectAddress;
 	}
 
 	/* Do the work */
 	RenameRelationInternal(relid, stmt->newname, false);
 
-	return relid;
+	ObjectAddressSet(address, RelationRelationId, relid);
+
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 03cc8fe..68b6917 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -846,7 +846,7 @@ directory_is_empty(const char *path)
 /*
  * Rename a tablespace
  */
-Oid
+ObjectAddress
 RenameTableSpace(const char *oldname, const char *newname)
 {
 	Oid			tspId;
@@ -856,6 +856,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	HeapTuple	tup;
 	HeapTuple	newtuple;
 	Form_pg_tablespace newform;
+	ObjectAddress address;
 
 	/* Search pg_tablespace */
 	rel = heap_open(TableSpaceRelationId, RowExclusiveLock);
@@ -912,9 +913,11 @@ RenameTableSpace(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(TableSpaceRelationId, tspId, 0);
 
+	ObjectAddressSet(address, TableSpaceRelationId, tspId);
+
 	heap_close(rel, NoLock);
 
-	return tspId;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index a84e86e..041ae8d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1249,7 +1249,7 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
  *		modify tgname in trigger tuple
  *		update row in catalog
  */
-Oid
+ObjectAddress
 renametrig(RenameStmt *stmt)
 {
 	Oid			tgoid;
@@ -1259,6 +1259,7 @@ renametrig(RenameStmt *stmt)
 	SysScanDesc tgscan;
 	ScanKeyData key[2];
 	Oid			relid;
+	ObjectAddress address;
 
 	/*
 	 * Look up name, check permissions, and acquire lock (which we will NOT
@@ -1351,6 +1352,8 @@ renametrig(RenameStmt *stmt)
 						stmt->subname, RelationGetRelationName(targetrel))));
 	}
 
+	ObjectAddressSet(address, TriggerRelationId, tgoid);
+
 	systable_endscan(tgscan);
 
 	heap_close(tgrel, RowExclusiveLock);
@@ -1360,7 +1363,7 @@ renametrig(RenameStmt *stmt)
 	 */
 	relation_close(targetrel, NoLock);
 
-	return tgoid;
+	return address;
 }
 
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index b77e1b4..b02abfe 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3205,7 +3205,7 @@ GetDomainConstraints(Oid typeOid)
 /*
  * Execute ALTER TYPE RENAME
  */
-Oid
+ObjectAddress
 RenameType(RenameStmt *stmt)
 {
 	List	   *names = stmt->object;
@@ -3215,6 +3215,7 @@ RenameType(RenameStmt *stmt)
 	Relation	rel;
 	HeapTuple	tup;
 	Form_pg_type typTup;
+	ObjectAddress address;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -3272,10 +3273,11 @@ RenameType(RenameStmt *stmt)
 		RenameTypeInternal(typeOid, newTypeName,
 						   typTup->typnamespace);
 
+	ObjectAddressSet(address, TypeRelationId, typeOid);
 	/* Clean up */
 	heap_close(rel, RowExclusiveLock);
 
-	return typeOid;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 2210eed..0d30838 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1114,7 +1114,7 @@ DropRole(DropRoleStmt *stmt)
 /*
  * Rename role
  */
-Oid
+ObjectAddress
 RenameRole(const char *oldname, const char *newname)
 {
 	HeapTuple	oldtuple,
@@ -1128,6 +1128,7 @@ RenameRole(const char *oldname, const char *newname)
 	bool		repl_repl[Natts_pg_authid];
 	int			i;
 	Oid			roleid;
+	ObjectAddress address;
 
 	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
 	dsc = RelationGetDescr(rel);
@@ -1216,6 +1217,8 @@ RenameRole(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
 
+	ObjectAddressSet(address, AuthIdRelationId, roleid);
+
 	ReleaseSysCache(oldtuple);
 
 	/*
@@ -1223,7 +1226,7 @@ RenameRole(const char *oldname, const char *newname)
 	 */
 	heap_close(rel, NoLock);
 
-	return roleid;
+	return address;
 }
 
 /*
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index eab4d73..75b6d80 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -897,7 +897,7 @@ RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
 /*
  * Rename an existing rewrite rule.
  */
-Oid
+ObjectAddress
 RenameRewriteRule(RangeVar *relation, const char *oldName,
 				  const char *newName)
 {
@@ -907,6 +907,7 @@ RenameRewriteRule(RangeVar *relation, const char *oldName,
 	HeapTuple	ruletup;
 	Form_pg_rewrite ruleform;
 	Oid			ruleOid;
+	ObjectAddress address;
 
 	/*
 	 * Look up name, check permissions, and acquire lock (which we will NOT
@@ -969,10 +970,12 @@ RenameRewriteRule(RangeVar *relation, const char *oldName,
 	 */
 	CacheInvalidateRelcache(targetrel);
 
+	ObjectAddressSet(address, RewriteRelationId, ruleOid);
+
 	/*
 	 * Close rel, but keep exclusive lock!
 	 */
 	relation_close(targetrel, NoLock);
 
-	return ruleOid;
+	return address;
 }
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6f4dbab..619b2f5 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -28,6 +28,18 @@ typedef struct ObjectAddress
 	int32		objectSubId;	/* Subitem within object (eg column), or 0 */
 } ObjectAddress;
 
+extern const ObjectAddress InvalidObjectAddress;
+
+#define ObjectAddressSubSet(addr, class_id, object_id, object_sub_id) \
+	do { \
+		(addr).classId = (class_id); \
+		(addr).objectId = (object_id); \
+		(addr).objectSubId = (object_sub_id); \
+	} while (0)
+
+#define ObjectAddressSet(addr, class_id, object_id) \
+	ObjectAddressSubSet(addr, class_id, object_id, 0)
+
 extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
 				   List *objargs, Relation *relp,
 				   LOCKMODE lockmode, bool missing_ok);
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index 14d24f1..5df28d3 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -15,10 +15,11 @@
 #define ALTER_H
 
 #include "catalog/dependency.h"
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "utils/relcache.h"
 
-extern Oid	ExecRenameStmt(RenameStmt *stmt);
+extern ObjectAddress ExecRenameStmt(RenameStmt *stmt);
 
 extern Oid	ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
 extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 4b60cdb..42bc5fa 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -15,6 +15,7 @@
 #define DBCOMMANDS_H
 
 #include "access/xlogreader.h"
+#include "catalog/objectaddress.h"
 #include "lib/stringinfo.h"
 #include "nodes/parsenodes.h"
 
@@ -40,7 +41,7 @@ typedef struct xl_dbase_drop_rec
 
 extern Oid	createdb(const CreatedbStmt *stmt);
 extern void dropdb(const char *dbname, bool missing_ok);
-extern Oid	RenameDatabase(const char *oldname, const char *newname);
+extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
 extern Oid	AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel);
 extern Oid	AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
 extern Oid	AlterDatabaseOwner(const char *dbname, Oid newOwnerId);
diff --git a/src/include/commands/policy.h b/src/include/commands/policy.h
index e911fcc..0848a18 100644
--- a/src/include/commands/policy.h
+++ b/src/include/commands/policy.h
@@ -15,6 +15,7 @@
 #ifndef POLICY_H
 #define POLICY_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "utils/relcache.h"
 
@@ -28,7 +29,7 @@ extern Oid AlterPolicy(AlterPolicyStmt *stmt);
 extern Oid get_relation_policy_oid(Oid relid, const char *policy_name,
 						bool missing_ok);
 
-extern Oid rename_policy(RenameStmt *stmt);
+extern ObjectAddress rename_policy(RenameStmt *stmt);
 
 
 #endif   /* POLICY_H */
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index d08fdd4..1c0f3ca 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -15,6 +15,7 @@
 #ifndef SCHEMACMDS_H
 #define SCHEMACMDS_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
 extern Oid CreateSchemaCommand(CreateSchemaStmt *parsetree,
@@ -22,7 +23,7 @@ extern Oid CreateSchemaCommand(CreateSchemaStmt *parsetree,
 
 extern void RemoveSchemaById(Oid schemaOid);
 
-extern Oid	RenameSchema(const char *oldname, const char *newname);
+extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern Oid	AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaOid, Oid newOwnerId);
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index a55e8d4..f529337 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -16,6 +16,7 @@
 
 #include "access/htup.h"
 #include "catalog/dependency.h"
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
@@ -53,11 +54,13 @@ extern void ExecuteTruncate(TruncateStmt *stmt);
 
 extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass);
 
-extern Oid	renameatt(RenameStmt *stmt);
+extern ObjectAddress renameatt(RenameStmt *stmt);
 
-extern Oid	RenameConstraint(RenameStmt *stmt);
+extern ObjectAddress renameatt_type(RenameStmt *stmt);
 
-extern Oid	RenameRelation(RenameStmt *stmt);
+extern ObjectAddress RenameConstraint(RenameStmt *stmt);
+
+extern ObjectAddress RenameRelation(RenameStmt *stmt);
 
 extern void RenameRelationInternal(Oid myrelid,
 					   const char *newrelname, bool is_internal);
diff --git a/src/include/commands/tablespace.h b/src/include/commands/tablespace.h
index 70734d6..86b0477 100644
--- a/src/include/commands/tablespace.h
+++ b/src/include/commands/tablespace.h
@@ -15,6 +15,7 @@
 #define TABLESPACE_H
 
 #include "access/xlogreader.h"
+#include "catalog/objectaddress.h"
 #include "lib/stringinfo.h"
 #include "nodes/parsenodes.h"
 
@@ -42,7 +43,7 @@ typedef struct TableSpaceOpts
 
 extern Oid	CreateTableSpace(CreateTableSpaceStmt *stmt);
 extern void DropTableSpace(DropTableSpaceStmt *stmt);
-extern Oid	RenameTableSpace(const char *oldname, const char *newname);
+extern ObjectAddress RenameTableSpace(const char *oldname, const char *newname);
 extern Oid	AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt);
 
 extern void TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo);
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index d0c0dcc..e09ac29 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -13,6 +13,7 @@
 #ifndef TRIGGER_H
 #define TRIGGER_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 
@@ -115,7 +116,7 @@ extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 extern void RemoveTriggerById(Oid trigOid);
 extern Oid	get_trigger_oid(Oid relid, const char *name, bool missing_ok);
 
-extern Oid	renametrig(RenameStmt *stmt);
+extern ObjectAddress renametrig(RenameStmt *stmt);
 
 extern void EnableDisableTrigger(Relation rel, const char *tgname,
 					 char fires_when, bool skip_system);
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index e18a714..daeabfe 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -41,7 +41,7 @@ extern void checkDomainOwner(HeapTuple tup);
 
 extern List *GetDomainConstraints(Oid typeOid);
 
-extern Oid	RenameType(RenameStmt *stmt);
+extern ObjectAddress RenameType(RenameStmt *stmt);
 extern Oid	AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype);
 extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
 					   bool hasDependEntry);
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d766851..ccadb04 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -11,6 +11,7 @@
 #ifndef USER_H
 #define USER_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
 
@@ -27,7 +28,7 @@ extern Oid	AlterRole(AlterRoleStmt *stmt);
 extern Oid	AlterRoleSet(AlterRoleSetStmt *stmt);
 extern void DropRole(DropRoleStmt *stmt);
 extern void GrantRole(GrantRoleStmt *stmt);
-extern Oid	RenameRole(const char *oldname, const char *newname);
+extern ObjectAddress RenameRole(const char *oldname, const char *newname);
 extern void DropOwnedObjects(DropOwnedStmt *stmt);
 extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
 extern List *roleNamesToIds(List *memberNames);
diff --git a/src/include/rewrite/rewriteDefine.h b/src/include/rewrite/rewriteDefine.h
index d384552..c112b85 100644
--- a/src/include/rewrite/rewriteDefine.h
+++ b/src/include/rewrite/rewriteDefine.h
@@ -14,6 +14,7 @@
 #ifndef REWRITEDEFINE_H
 #define REWRITEDEFINE_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "utils/relcache.h"
 
@@ -32,7 +33,7 @@ extern Oid DefineQueryRewrite(char *rulename,
 				   bool replace,
 				   List *action);
 
-extern Oid RenameRewriteRule(RangeVar *relation, const char *oldName,
+extern ObjectAddress RenameRewriteRule(RangeVar *relation, const char *oldName,
 				  const char *newName);
 
 extern void setRuleCheckAsUser(Node *node, Oid userid);
-- 
2.1.4

0003-deparse-core-have-COMMENT-return-an-ObjectAddress-no.patchtext/x-diff; charset=us-asciiDownload
>From 874430af90aacdf8aaa3d2a0b47fe9ceb7962f9b Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 19 Feb 2015 16:29:57 -0300
Subject: [PATCH 03/44] deparse/core: have COMMENT return an ObjectAddress, not
 OID

---
 src/backend/commands/comment.c | 8 ++++----
 src/include/commands/comment.h | 3 ++-
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index ed1e46e..6d8c006 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -36,11 +36,11 @@
  * This routine is used to add the associated comment into
  * pg_description for the object specified by the given SQL command.
  */
-Oid
+ObjectAddress
 CommentObject(CommentStmt *stmt)
 {
-	ObjectAddress address;
 	Relation	relation;
+	ObjectAddress address = InvalidObjectAddress;
 
 	/*
 	 * When loading a dump, we may see a COMMENT ON DATABASE for the old name
@@ -60,7 +60,7 @@ CommentObject(CommentStmt *stmt)
 			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_DATABASE),
 					 errmsg("database \"%s\" does not exist", database)));
-			return InvalidOid;
+			return address;
 		}
 	}
 
@@ -126,7 +126,7 @@ CommentObject(CommentStmt *stmt)
 	if (relation != NULL)
 		relation_close(relation, NoLock);
 
-	return address.objectId;
+	return address;
 }
 
 /*
diff --git a/src/include/commands/comment.h b/src/include/commands/comment.h
index 3d61b44..990d362 100644
--- a/src/include/commands/comment.h
+++ b/src/include/commands/comment.h
@@ -15,6 +15,7 @@
 #ifndef COMMENT_H
 #define COMMENT_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
 /*------------------------------------------------------------------
@@ -29,7 +30,7 @@
  *------------------------------------------------------------------
  */
 
-extern Oid	CommentObject(CommentStmt *stmt);
+extern ObjectAddress CommentObject(CommentStmt *stmt);
 
 extern void DeleteComments(Oid oid, Oid classoid, int32 subid);
 
-- 
2.1.4

0004-deparse-core-have-ALTER-TABLE-return-table-OID-and-o.patchtext/x-diff; charset=us-asciiDownload
>From 2fce571ffc199aa6456796fbf5b91d5ef7082354 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 04/44] deparse/core: have ALTER TABLE return table OID and
 other data

Currently, most if not all CREATE commands return the OID of the created
object.  This is useful for event triggers to try to figure out exactly
what happened.  This patch extends this idea a bit further to cover
ALTER TABLE as well: auxilliary info such as a second OID or a column
number are also returned for each subcommand of the ALTER TABLE.  This
OID or attnum can later be used to determine, for instance, which
constraint was added by ALTER TABLE ADD CONSTRAINT, or which parent got
dropped from the inherit-from parents list by NO INHERIT.  This makes it
possible to decode with precision exactly what happened during execution
of any ALTER TABLE command.

There is no user-visible change here; this patch makes the info
available for later.
---
 src/backend/catalog/heap.c          |  40 +++--
 src/backend/catalog/index.c         |   7 +-
 src/backend/catalog/pg_constraint.c |   2 +
 src/backend/commands/tablecmds.c    | 307 ++++++++++++++++++++++++------------
 src/include/catalog/heap.h          |   3 +-
 src/include/catalog/index.h         |   2 +-
 6 files changed, 244 insertions(+), 117 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 17f7266..0247da6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -97,7 +97,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,
@@ -1844,7 +1844,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)
 {
@@ -1949,6 +1949,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	 */
 	InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
 								  RelationGetRelid(rel), attnum, is_internal);
+
+	return attrdefOid;
 }
 
 /*
@@ -1956,8 +1958,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)
@@ -1967,6 +1971,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
+	Oid			constrOid;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2018,7 +2023,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 */
@@ -2049,11 +2054,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
@@ -2065,7 +2074,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 */
 
 	/*
@@ -2082,12 +2091,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:
@@ -2175,6 +2186,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,
@@ -2195,10 +2207,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;
@@ -2218,6 +2231,7 @@ AddRelationNewConstraints(Relation rel,
 	{
 		Constraint *cdef = (Constraint *) lfirst(cell);
 		char	   *ccname;
+		Oid			constrOid;
 
 		if (cdef->contype != CONSTR_CHECK)
 			continue;
@@ -2327,13 +2341,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 f85ed93..5773298 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1111,7 +1111,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
@@ -1128,7 +1129,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,
@@ -1316,6 +1317,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 77fe918..3c756f8 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 45e01f9..a6bf0e0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -282,9 +282,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);
@@ -326,26 +326,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);
@@ -353,20 +353,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,
@@ -377,9 +377,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);
@@ -392,7 +392,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,
+static Oid ATExecClusterOn(Relation rel, const char *indexName,
 				LOCKMODE lockmode);
 static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
 static bool ATPrepChangePersistence(Relation rel, bool toLogged);
@@ -407,10 +407,10 @@ static void ATExecEnableDisableTrigger(Relation rel, char *trigname,
 static void ATExecEnableDisableRule(Relation rel, char *rulename,
 						char fires_when, LOCKMODE lockmode);
 static void ATPrepAddInherit(Relation child_rel);
-static void ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
-static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
+static Oid ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
+static Oid ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
-static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
+static Oid ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
 static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
 static void ATExecGenericOptions(Relation rel, List *options);
@@ -620,6 +620,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;
@@ -1735,6 +1736,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;
@@ -3408,78 +3410,95 @@ static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
+	AttrNumber	colno = InvalidAttrNumber;
+	Oid			newoid = InvalidOid;
+
+	/* temporary measure to silence unused-variable compiler warnings */
+	(void) colno;
+	(void) newoid;
+
 	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,
@@ -3492,10 +3511,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),
@@ -3503,7 +3524,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);
@@ -3592,19 +3613,20 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 
 		case AT_AddInherit:
-			ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
+			newoid = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
 			break;
 		case AT_DropInherit:
-			ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
+			newoid = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
 			break;
 		case AT_AddOf:
-			ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
+			newoid = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
 			break;
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
 		case AT_ReplicaIdentity:
-			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def,
+								  lockmode);
 			break;
 		case AT_EnableRowSecurity:
 			ATExecEnableRowSecurity(rel);
@@ -4605,7 +4627,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)
@@ -4690,7 +4712,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					  colDef->colname, RelationGetRelationName(rel))));
 
 			heap_close(attrdesc, RowExclusiveLock);
-			return;
+			return InvalidAttrNumber;
 		}
 	}
 
@@ -4936,6 +4958,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 		heap_close(childrel, NoLock);
 	}
+
+	return newattnum;
 }
 
 /*
@@ -5050,7 +5074,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;
@@ -5135,18 +5159,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)
 {
@@ -5190,18 +5218,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)
 {
@@ -5252,6 +5284,8 @@ ATExecColumnDefault(Relation rel, const char *colName,
 		AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
 								  false, true, false);
 	}
+
+	return attnum;
 }
 
 /*
@@ -5281,13 +5315,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);
@@ -5322,7 +5357,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\"",
@@ -5341,9 +5377,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)
 {
@@ -5351,6 +5389,7 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	HeapTuple	tuple,
 				newtuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 	Datum		datum,
 				newOptions;
 	bool		isnull;
@@ -5369,7 +5408,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\"",
@@ -5408,12 +5448,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;
@@ -5421,6 +5463,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);
@@ -5453,7 +5496,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\"",
@@ -5483,6 +5527,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return attnum;
 }
 
 
@@ -5707,8 +5753,10 @@ 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 void
+static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
@@ -5751,12 +5799,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)
 {
@@ -5766,6 +5816,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	IndexInfo  *indexInfo;
 	char	   *constraintName;
 	char		constraintType;
+	Oid			conOid;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(OidIsValid(index_oid));
@@ -5810,30 +5861,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));
 
 	/*
@@ -5844,9 +5899,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:
@@ -5877,17 +5933,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.
  *
@@ -5906,7 +5967,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)
@@ -5915,6 +5976,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)
@@ -5957,6 +6019,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 */
@@ -5972,7 +6041,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.
@@ -5980,7 +6049,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
@@ -6018,16 +6087,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)
 {
@@ -6426,6 +6498,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Close pk table, but keep lock until we've committed.
 	 */
 	heap_close(pkrel, NoLock);
+
+	return constrOid;
 }
 
 /*
@@ -6438,7 +6512,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)
 {
@@ -6449,6 +6523,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;
@@ -6508,6 +6583,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);
 
@@ -6555,6 +6632,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 /*
@@ -6565,7 +6644,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)
 {
@@ -6575,6 +6654,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);
 
@@ -6616,9 +6696,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;
 
 			/*
@@ -6633,7 +6714,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 conid);
+										 constrOid);
 			heap_close(refrel, NoLock);
 
 			/*
@@ -6714,6 +6795,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 
@@ -7800,7 +7883,7 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
 	}
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
@@ -8194,9 +8277,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 	/* Cleanup */
 	heap_freetuple(heapTup);
+
+	return attnum;
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnGenericOptions(Relation rel,
 								const char *colName,
 								List *options,
@@ -8215,9 +8300,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);
@@ -8244,7 +8330,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)));
@@ -8293,6 +8380,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	heap_close(attrel, RowExclusiveLock);
 
 	heap_freetuple(newtuple);
+
+	return attnum;
 }
 
 /*
@@ -8934,7 +9023,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;
@@ -8952,6 +9041,8 @@ ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 
 	/* And do the work */
 	mark_index_clustered(rel, indexOid, false);
+
+	return indexOid;
 }
 
 /*
@@ -9652,11 +9743,12 @@ ATPrepAddInherit(Relation child_rel)
 				 errmsg("cannot change inheritance of typed table")));
 }
 
-static void
+static Oid
 ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 {
 	Relation	parent_rel,
 				catalogRelation;
+	Oid			parent_oid;
 	SysScanDesc scan;
 	ScanKeyData key;
 	HeapTuple	inheritsTuple;
@@ -9776,11 +9868,15 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 							 inhseqno + 1,
 							 catalogRelation);
 
+	parent_oid = RelationGetRelid(parent_rel);
+
 	/* Now we're done with pg_inherits */
 	heap_close(catalogRelation, RowExclusiveLock);
 
 	/* keep our lock on the parent relation until commit */
 	heap_close(parent_rel, NoLock);
+
+	return parent_oid;
 }
 
 /*
@@ -10049,10 +10145,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
  * coninhcount and conislocal for inherited constraints are adjusted in
  * exactly the same way.
  */
-static void
+static Oid
 ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 {
 	Relation	parent_rel;
+	Oid			parent_oid;
 	Relation	catalogRelation;
 	SysScanDesc scan;
 	ScanKeyData key[3];
@@ -10221,6 +10318,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 		}
 	}
 
+	parent_oid = RelationGetRelid(parent_rel);
+
 	systable_endscan(scan);
 	heap_close(catalogRelation, RowExclusiveLock);
 
@@ -10239,6 +10338,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 
 	/* keep our lock on the parent relation until commit */
 	heap_close(parent_rel, NoLock);
+
+	return parent_oid;
 }
 
 /*
@@ -10296,8 +10397,10 @@ drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid)
  * TABLE OF.  All attname, atttypid, atttypmod and attcollation must match.  The
  * subject table must not have inheritance parents.  These restrictions ensure
  * that you cannot create a configuration impossible with CREATE TABLE OF alone.
+ *
+ * The OID of the type is returned.
  */
-static void
+static Oid
 ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 {
 	Oid			relid = RelationGetRelid(rel);
@@ -10426,6 +10529,8 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	heap_close(relationRelation, RowExclusiveLock);
 
 	ReleaseSysCache(typetuple);
+
+	return typeid;
 }
 
 /*
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index e5c204d..fe1ddd9 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 e7cc7a0..e22ef8f 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -63,7 +63,7 @@ extern Oid index_create(Relation heapRelation,
 			 bool is_internal,
 			 bool if_not_exists);
 
-extern void index_constraint_create(Relation heapRelation,
+extern Oid index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
 						const char *constraintName,
-- 
2.1.4

0005-deparse-core-have-ALTER-EXTENSION-ADD-DROP-report-OI.patchtext/x-diff; charset=us-asciiDownload
>From 6df2abe044d570ed9e10286ab523d50b13b9595e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 18 Feb 2015 13:12:24 -0300
Subject: [PATCH 05/44] deparse/core: have ALTER EXTENSION ADD/DROP report OID
 of affected object

---
 src/backend/commands/extension.c | 6 +++++-
 src/backend/tcop/utility.c       | 3 ++-
 src/include/commands/extension.h | 3 ++-
 3 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 3b95552..73f8bea 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2881,7 +2881,7 @@ ApplyExtensionUpdates(Oid extensionOid,
  * Execute ALTER EXTENSION ADD/DROP
  */
 Oid
-ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt)
+ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt, Oid *objectId)
 {
 	ObjectAddress extension;
 	ObjectAddress object;
@@ -2906,6 +2906,10 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt)
 	object = get_object_address(stmt->objtype, stmt->objname, stmt->objargs,
 								&relation, ShareUpdateExclusiveLock, false);
 
+	Assert(object.objectSubId == 0);
+	if (objectId)
+		*objectId = object.objectId;
+
 	/* Permission check: must own target object, too */
 	check_object_ownership(GetUserId(), stmt->objtype, object,
 						   stmt->objname, stmt->objargs, relation);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 2b6fc3e..5f5f69c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1190,7 +1190,8 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
+				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+											   NULL);
 				break;
 
 			case T_CreateFdwStmt:
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index a349d6a..8df38f2 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -38,7 +38,8 @@ extern Oid InsertExtensionTuple(const char *extName, Oid extOwner,
 
 extern Oid	ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
 
-extern Oid	ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt);
+extern Oid	ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
+							   Oid *objectId);
 
 extern Oid	get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
-- 
2.1.4

0006-deparse-core-return-ObjAddress-rather-than-Oid.patchtext/x-diff; charset=us-asciiDownload
>From 482186e2d6d25356b88c9b16ed5389e1bb046cdf Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 19 Feb 2015 16:06:30 -0300
Subject: [PATCH 06/44] deparse/core: return ObjAddress rather than Oid

---
 src/backend/commands/createas.c  | 14 +++++-----
 src/backend/commands/indexcmds.c | 15 ++++++----
 src/backend/commands/sequence.c  | 10 ++++---
 src/backend/commands/tablecmds.c | 31 ++++++++++++---------
 src/backend/commands/trigger.c   |  1 -
 src/backend/commands/typecmds.c  | 60 ++++++++++++++++++++++++++++++----------
 src/backend/commands/view.c      | 27 ++++++++++--------
 src/backend/tcop/utility.c       | 19 +++++++------
 src/include/commands/defrem.h    |  3 +-
 src/include/commands/sequence.h  |  3 +-
 src/include/commands/tablecmds.h |  2 +-
 src/include/commands/typecmds.h  | 10 +++----
 src/include/commands/view.h      |  3 +-
 13 files changed, 124 insertions(+), 74 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index c961429..cc7a5e1 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -288,7 +288,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	bool		is_matview;
 	char		relkind;
 	CreateStmt *create;
-	Oid			intoRelationId;
+	ObjectAddress intoRelationAddr;
 	Relation	intoRelationDesc;
 	RangeTblEntry *rte;
 	Datum		toast_options;
@@ -385,7 +385,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	/*
 	 * Actually create the target table
 	 */
-	intoRelationId = DefineRelation(create, relkind, InvalidOid);
+	intoRelationAddr = DefineRelation(create, relkind, InvalidOid);
 
 	/*
 	 * If necessary, create a TOAST table for the target table.  Note that
@@ -403,7 +403,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 
 	(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
 
-	NewRelationCreateToastTable(intoRelationId, toast_options);
+	NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
 
 	/* Create the "view" part of a materialized view. */
 	if (is_matview)
@@ -411,14 +411,14 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 		/* StoreViewQuery scribbles on tree, so make a copy */
 		Query	   *query = (Query *) copyObject(into->viewQuery);
 
-		StoreViewQuery(intoRelationId, query, false);
+		StoreViewQuery(intoRelationAddr.objectId, query, false);
 		CommandCounterIncrement();
 	}
 
 	/*
 	 * Finally we can open the target table
 	 */
-	intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
+	intoRelationDesc = heap_open(intoRelationAddr.objectId, AccessExclusiveLock);
 
 	/*
 	 * Check INSERT permission on the constructed table.
@@ -428,7 +428,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	rte = makeNode(RangeTblEntry);
 	rte->rtekind = RTE_RELATION;
-	rte->relid = intoRelationId;
+	rte->relid = intoRelationAddr.objectId;
 	rte->relkind = relkind;
 	rte->requiredPerms = ACL_INSERT;
 
@@ -446,7 +446,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 * be enabled here.  We don't actually support that currently, so throw
 	 * our own ereport(ERROR) if that happens.
 	 */
-	if (check_enable_rls(intoRelationId, InvalidOid, false) == RLS_ENABLED)
+	if (check_enable_rls(intoRelationAddr.objectId, InvalidOid, false) == RLS_ENABLED)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 (errmsg("policies not yet implemented for this command"))));
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index e859669..5fcff50 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -291,9 +291,9 @@ CheckIndexCompatible(Oid oldId,
  *		it will be filled later.
  * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
  *
- * Returns the OID of the created index.
+ * Returns the object address of the created index.
  */
-Oid
+ObjectAddress
 DefineIndex(Oid relationId,
 			IndexStmt *stmt,
 			Oid indexRelationId,
@@ -323,6 +323,7 @@ DefineIndex(Oid relationId,
 	int			numberOfAttributes;
 	TransactionId limitXmin;
 	VirtualTransactionId *old_snapshots;
+	ObjectAddress address;
 	int			n_old_snapshots;
 	LockRelId	heaprelid;
 	LOCKTAG		heaplocktag;
@@ -613,10 +614,14 @@ DefineIndex(Oid relationId,
 					 stmt->concurrent, !check_rights,
 					 stmt->if_not_exists);
 
+	address.classId = RelationRelationId;
+	address.objectId = indexRelationId;
+	address.objectSubId = 0;
+
 	if (!OidIsValid(indexRelationId))
 	{
 		heap_close(rel, NoLock);
-		return indexRelationId;
+		return address;
 	}
 
 	/* Add any requested comment */
@@ -628,7 +633,7 @@ DefineIndex(Oid relationId,
 	{
 		/* Close the heap and we're done, in the non-concurrent case */
 		heap_close(rel, NoLock);
-		return indexRelationId;
+		return address;
 	}
 
 	/* save lockrelid and locktag for below, then close rel */
@@ -873,7 +878,7 @@ DefineIndex(Oid relationId,
 	 */
 	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
 
-	return indexRelationId;
+	return address;
 }
 
 
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 622ccf7..d1570be 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -103,13 +103,14 @@ static void process_owned_by(Relation seqrel, List *owned_by);
  * DefineSequence
  *				Creates a new sequence relation
  */
-Oid
+ObjectAddress
 DefineSequence(CreateSeqStmt *seq)
 {
 	FormData_pg_sequence new;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
 	Oid			seqoid;
+	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	tuple;
 	TupleDesc	tupDesc;
@@ -138,7 +139,7 @@ DefineSequence(CreateSeqStmt *seq)
 					(errcode(ERRCODE_DUPLICATE_TABLE),
 					 errmsg("relation \"%s\" already exists, skipping",
 							seq->sequence->relname)));
-			return InvalidOid;
+			return InvalidObjectAddress;
 		}
 	}
 
@@ -232,7 +233,8 @@ DefineSequence(CreateSeqStmt *seq)
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
 
-	seqoid = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId);
+	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId);
+	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
 	rel = heap_open(seqoid, AccessExclusiveLock);
@@ -248,7 +250,7 @@ DefineSequence(CreateSeqStmt *seq)
 
 	heap_close(rel, NoLock);
 
-	return seqoid;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a6bf0e0..bb0874c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -441,10 +441,10 @@ static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
  * "on behalf of" someone else, so we still want to see that the current user
  * has permissions to do it.
  *
- * If successful, returns the OID of the new relation.
+ * If successful, returns the address of the new relation.
  * ----------------------------------------------------------------
  */
-Oid
+ObjectAddress
 DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 {
 	char		relname[NAMEDATALEN];
@@ -465,6 +465,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 	Oid			ofTypeId;
+	ObjectAddress address;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -690,13 +691,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 		AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
 								  true, true, false);
 
+	address.classId = RelationRelationId;
+	address.objectId = relationId;
+	address.objectSubId = 0;
+
 	/*
 	 * Clean up.  We keep lock on new relation (although it shouldn't be
 	 * visible to anyone else anyway, until commit).
 	 */
 	relation_close(rel, NoLock);
 
-	return relationId;
+	return address;
 }
 
 /*
@@ -5763,7 +5768,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	bool		check_rights;
 	bool		skip_build;
 	bool		quiet;
-	Oid			new_index;
+	ObjectAddress address;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(!stmt->concurrent);
@@ -5778,13 +5783,13 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	/* suppress notices when rebuilding existing index */
 	quiet = is_rebuild;
 
-	new_index = DefineIndex(RelationGetRelid(rel),
-							stmt,
-							InvalidOid, /* no predefined OID */
-							true,		/* is_alter_table */
-							check_rights,
-							skip_build,
-							quiet);
+	address = DefineIndex(RelationGetRelid(rel),
+						  stmt,
+						  InvalidOid, /* no predefined OID */
+						  true,		/* is_alter_table */
+						  check_rights,
+						  skip_build,
+						  quiet);
 
 	/*
 	 * If TryReuseIndex() stashed a relfilenode for us, we used it for the new
@@ -5794,13 +5799,13 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	 */
 	if (OidIsValid(stmt->oldNode))
 	{
-		Relation	irel = index_open(new_index, NoLock);
+		Relation	irel = index_open(address.objectId, NoLock);
 
 		RelationPreserveStorage(irel->rd_node, true);
 		index_close(irel, NoLock);
 	}
 
-	return new_index;
+	return address.objectId;
 }
 
 /*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 041ae8d..00654f7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1366,7 +1366,6 @@ renametrig(RenameStmt *stmt)
 	return address;
 }
 
-
 /*
  * EnableDisableTrigger()
  *
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index b02abfe..68d73b5 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2076,7 +2076,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
 	CreateStmt *createStmt = makeNode(CreateStmt);
 	Oid			old_type_oid;
 	Oid			typeNamespace;
-	Oid			relid;
+	ObjectAddress address;
 
 	/*
 	 * now set the parameters for keys/inheritance etc. All of these are
@@ -2115,17 +2115,19 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
 	/*
 	 * Finally create the relation.  This also creates the type.
 	 */
-	relid = DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid);
-	Assert(relid != InvalidOid);
-	return relid;
+	address = DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid);
+	Assert(address.objectId != InvalidOid);
+	return address.objectId;
 }
 
 /*
  * AlterDomainDefault
  *
  * Routine implementing ALTER DOMAIN SET/DROP DEFAULT statements.
+ *
+ * Returns ObjectAddress of the modified domain.
  */
-Oid
+ObjectAddress
 AlterDomainDefault(List *names, Node *defaultRaw)
 {
 	TypeName   *typename;
@@ -2140,6 +2142,7 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 	bool		new_record_repl[Natts_pg_type];
 	HeapTuple	newtuple;
 	Form_pg_type typTup;
+	ObjectAddress address;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2249,19 +2252,25 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 
 	InvokeObjectPostAlterHook(TypeRelationId, domainoid, 0);
 
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;
+
 	/* Clean up */
 	heap_close(rel, NoLock);
 	heap_freetuple(newtuple);
 
-	return domainoid;
+	return address;
 }
 
 /*
  * AlterDomainNotNull
  *
  * Routine implementing ALTER DOMAIN SET/DROP NOT NULL statements.
+ *
+ * Returns ObjectAddress of the modified domain.
  */
-Oid
+ObjectAddress
 AlterDomainNotNull(List *names, bool notNull)
 {
 	TypeName   *typename;
@@ -2269,6 +2278,7 @@ AlterDomainNotNull(List *names, bool notNull)
 	Relation	typrel;
 	HeapTuple	tup;
 	Form_pg_type typTup;
+	ObjectAddress address = InvalidObjectAddress;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2289,7 +2299,7 @@ AlterDomainNotNull(List *names, bool notNull)
 	if (typTup->typnotnull == notNull)
 	{
 		heap_close(typrel, RowExclusiveLock);
-		return InvalidOid;
+		return address;
 	}
 
 	/* Adding a NOT NULL constraint requires checking existing columns */
@@ -2363,11 +2373,15 @@ AlterDomainNotNull(List *names, bool notNull)
 
 	InvokeObjectPostAlterHook(TypeRelationId, domainoid, 0);
 
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;
+
 	/* Clean up */
 	heap_freetuple(tup);
 	heap_close(typrel, RowExclusiveLock);
 
-	return domainoid;
+	return address;
 }
 
 /*
@@ -2375,7 +2389,7 @@ AlterDomainNotNull(List *names, bool notNull)
  *
  * Implements the ALTER DOMAIN DROP CONSTRAINT statement
  */
-Oid
+ObjectAddress
 AlterDomainDropConstraint(List *names, const char *constrName,
 						  DropBehavior behavior, bool missing_ok)
 {
@@ -2388,6 +2402,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
 	ScanKeyData key[1];
 	HeapTuple	contup;
 	bool		found = false;
+	ObjectAddress address = InvalidObjectAddress;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2434,6 +2449,11 @@ AlterDomainDropConstraint(List *names, const char *constrName,
 			found = true;
 		}
 	}
+
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;
+
 	/* Clean up after the scan */
 	systable_endscan(conscan);
 	heap_close(conrel, RowExclusiveLock);
@@ -2453,7 +2473,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
 							constrName, TypeNameToString(typename))));
 	}
 
-	return domainoid;
+	return address;
 }
 
 /*
@@ -2461,7 +2481,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
  *
  * Implements the ALTER DOMAIN .. ADD CONSTRAINT statement.
  */
-Oid
+ObjectAddress
 AlterDomainAddConstraint(List *names, Node *newConstraint)
 {
 	TypeName   *typename;
@@ -2471,6 +2491,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
 	Form_pg_type typTup;
 	Constraint *constr;
 	char	   *ccbin;
+	ObjectAddress address;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2555,10 +2576,14 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
 	if (!constr->skip_validation)
 		validateDomainConstraint(domainoid, ccbin);
 
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;
+
 	/* Clean up */
 	heap_close(typrel, RowExclusiveLock);
 
-	return domainoid;
+	return address;
 }
 
 /*
@@ -2566,7 +2591,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
  *
  * Implements the ALTER DOMAIN .. VALIDATE CONSTRAINT statement.
  */
-Oid
+ObjectAddress
 AlterDomainValidateConstraint(List *names, char *constrName)
 {
 	TypeName   *typename;
@@ -2584,6 +2609,7 @@ AlterDomainValidateConstraint(List *names, char *constrName)
 	HeapTuple	tuple;
 	HeapTuple	copyTuple;
 	ScanKeyData key;
+	ObjectAddress address;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2654,6 +2680,10 @@ AlterDomainValidateConstraint(List *names, char *constrName)
 	InvokeObjectPostAlterHook(ConstraintRelationId,
 							  HeapTupleGetOid(copyTuple), 0);
 
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;
+
 	heap_freetuple(copyTuple);
 
 	systable_endscan(scan);
@@ -2663,7 +2693,7 @@ AlterDomainValidateConstraint(List *names, char *constrName)
 
 	ReleaseSysCache(tup);
 
-	return domainoid;
+	return address;
 }
 
 static void
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 7358723..b6730f3 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -65,7 +65,7 @@ validateWithCheckOption(char *value)
  * work harder.
  *---------------------------------------------------------------------
  */
-static Oid
+static ObjectAddress
 DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 					  List *options)
 {
@@ -143,6 +143,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		TupleDesc	descriptor;
 		List	   *atcmds = NIL;
 		AlterTableCmd *atcmd;
+		ObjectAddress address;
 
 		/* Relation is already locked, but we must build a relcache entry. */
 		rel = relation_open(viewOid, NoLock);
@@ -208,16 +209,20 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		/* OK, let's do it. */
 		AlterTableInternal(viewOid, atcmds, true);
 
+		address.classId = RelationRelationId;
+		address.objectId = viewOid;
+		address.objectSubId = 0;
+
 		/*
 		 * Seems okay, so return the OID of the pre-existing view.
 		 */
 		relation_close(rel, NoLock);	/* keep the lock! */
 
-		return viewOid;
+		return address;
 	}
 	else
 	{
-		Oid			relid;
+		ObjectAddress	address;
 
 		/*
 		 * now set the parameters for keys/inheritance etc. All of these are
@@ -237,9 +242,9 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		 * existing view, so we don't need more code to complain if "replace"
 		 * is false).
 		 */
-		relid = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid);
-		Assert(relid != InvalidOid);
-		return relid;
+		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid);
+		Assert(address.objectId != InvalidOid);
+		return address;
 	}
 }
 
@@ -388,14 +393,14 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
  * DefineView
  *		Execute a CREATE VIEW command.
  */
-Oid
+ObjectAddress
 DefineView(ViewStmt *stmt, const char *queryString)
 {
 	Query	   *viewParse;
-	Oid			viewOid;
 	RangeVar   *view;
 	ListCell   *cell;
 	bool		check_option;
+	ObjectAddress address;
 
 	/*
 	 * Run parse analysis to convert the raw parse tree to a Query.  Note this
@@ -533,7 +538,7 @@ DefineView(ViewStmt *stmt, const char *queryString)
 	 * NOTE: if it already exists and replace is false, the xact will be
 	 * aborted.
 	 */
-	viewOid = DefineVirtualRelation(view, viewParse->targetList,
+	address = DefineVirtualRelation(view, viewParse->targetList,
 									stmt->replace, stmt->options);
 
 	/*
@@ -543,9 +548,9 @@ DefineView(ViewStmt *stmt, const char *queryString)
 	 */
 	CommandCounterIncrement();
 
-	StoreViewQuery(viewOid, viewParse, stmt->replace);
+	StoreViewQuery(address.objectId, viewParse, stmt->replace);
 
-	return viewOid;
+	return address;
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5f5f69c..6ef5db1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -886,6 +886,7 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	ObjectAddress address;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -911,7 +912,6 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					List	   *stmts;
 					ListCell   *l;
-					Oid			relOid;
 
 					/* Run parse analysis ... */
 					stmts = transformCreateStmt((CreateStmt *) parsetree,
@@ -928,9 +928,9 @@ ProcessUtilitySlow(Node *parsetree,
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 
 							/* Create the table itself */
-							relOid = DefineRelation((CreateStmt *) stmt,
-													RELKIND_RELATION,
-													InvalidOid);
+							address = DefineRelation((CreateStmt *) stmt,
+													 RELKIND_RELATION,
+													 InvalidOid);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -952,16 +952,17 @@ ProcessUtilitySlow(Node *parsetree,
 												   toast_options,
 												   true);
 
-							NewRelationCreateToastTable(relOid, toast_options);
+							NewRelationCreateToastTable(address.objectId,
+														toast_options);
 						}
 						else if (IsA(stmt, CreateForeignTableStmt))
 						{
 							/* Create the table itself */
-							relOid = DefineRelation((CreateStmt *) stmt,
-													RELKIND_FOREIGN_TABLE,
-													InvalidOid);
+							address = DefineRelation((CreateStmt *) stmt,
+													 RELKIND_FOREIGN_TABLE,
+													 InvalidOid);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
-											   relOid);
+											   address.objectId);
 						}
 						else
 						{
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index cf586fe..5674a97 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -14,6 +14,7 @@
 #ifndef DEFREM_H
 #define DEFREM_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "utils/array.h"
 
@@ -21,7 +22,7 @@
 extern void RemoveObjects(DropStmt *stmt);
 
 /* commands/indexcmds.c */
-extern Oid DefineIndex(Oid relationId,
+extern ObjectAddress DefineIndex(Oid relationId,
 			IndexStmt *stmt,
 			Oid indexRelationId,
 			bool is_alter_table,
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 1baf43d..e3b5142 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -14,6 +14,7 @@
 #define SEQUENCE_H
 
 #include "access/xlogreader.h"
+#include "catalog/objectaddress.h"
 #include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "nodes/parsenodes.h"
@@ -72,7 +73,7 @@ extern Datum lastval(PG_FUNCTION_ARGS);
 
 extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS);
 
-extern Oid	DefineSequence(CreateSeqStmt *stmt);
+extern ObjectAddress DefineSequence(CreateSeqStmt *stmt);
 extern Oid	AlterSequence(AlterSeqStmt *stmt);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index f529337..876fbfd 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -22,7 +22,7 @@
 #include "utils/relcache.h"
 
 
-extern Oid	DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId);
+extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId);
 
 extern void RemoveRelations(DropStmt *drop);
 
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index daeabfe..a0082de 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -30,11 +30,11 @@ extern Oid	AlterEnum(AlterEnumStmt *stmt, bool isTopLevel);
 extern Oid	DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
 
-extern Oid	AlterDomainDefault(List *names, Node *defaultRaw);
-extern Oid	AlterDomainNotNull(List *names, bool notNull);
-extern Oid	AlterDomainAddConstraint(List *names, Node *constr);
-extern Oid	AlterDomainValidateConstraint(List *names, char *constrName);
-extern Oid AlterDomainDropConstraint(List *names, const char *constrName,
+extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
+extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
+extern ObjectAddress AlterDomainAddConstraint(List *names, Node *constr);
+extern ObjectAddress AlterDomainValidateConstraint(List *names, char *constrName);
+extern ObjectAddress AlterDomainDropConstraint(List *names, const char *constrName,
 						  DropBehavior behavior, bool missing_ok);
 
 extern void checkDomainOwner(HeapTuple tup);
diff --git a/src/include/commands/view.h b/src/include/commands/view.h
index 595d266..53db76e 100644
--- a/src/include/commands/view.h
+++ b/src/include/commands/view.h
@@ -14,11 +14,12 @@
 #ifndef VIEW_H
 #define VIEW_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
 extern void validateWithCheckOption(char *value);
 
-extern Oid	DefineView(ViewStmt *stmt, const char *queryString);
+extern ObjectAddress DefineView(ViewStmt *stmt, const char *queryString);
 
 extern void StoreViewQuery(Oid viewOid, Query *viewParse, bool replace);
 
-- 
2.1.4

0007-deparse-infrastructure-needed-for-command-deparsing.patchtext/x-diff; charset=us-asciiDownload
>From dcf886df6a3acb483e709817ac2f43869b6048cd Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 24 Sep 2014 15:53:04 -0300
Subject: [PATCH 07/44] deparse: infrastructure needed for command deparsing

This patch introduces unused infrastructure for command deparsing.
There are three main parts:

1. A list (stash) of executed commands in Node format, stored in the
event trigger state.  At ddl_command_end, the stored items can be
extracted.  For now this only support "basic" commands (in particular
not ALTER TABLE or GRANT).  It's useful enough to cover all CREATE
command as well as many simple ALTER forms.

2. Support code to enable writing routines that convert the Node format
into a JSON blob.  This JSON representation allows extracting and
modifying individual parts of the command.

3. Code to convert the JSON blobs back into plain strings.

No actual routines to convert Node into JSON is provided by this patch;
that is left to later patches.  This split is only presented as is for
ease of review.
---
 src/backend/catalog/objectaddress.c  | 115 +++++
 src/backend/commands/event_trigger.c | 227 ++++++++-
 src/backend/tcop/Makefile            |   2 +-
 src/backend/tcop/deparse_utility.c   | 942 +++++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           |   2 +
 src/backend/utils/adt/Makefile       |   2 +-
 src/backend/utils/adt/ddl_json.c     | 676 +++++++++++++++++++++++++
 src/backend/utils/adt/format_type.c  | 113 ++++-
 src/backend/utils/adt/jsonb.c        |   4 +-
 src/include/catalog/objectaddress.h  |   2 +
 src/include/catalog/pg_proc.h        |   5 +-
 src/include/commands/event_trigger.h |   3 +
 src/include/commands/extension.h     |   2 +-
 src/include/nodes/parsenodes.h       |   2 +
 src/include/tcop/deparse_utility.h   |  59 +++
 src/include/utils/builtins.h         |   7 +
 16 files changed, 2146 insertions(+), 17 deletions(-)
 create mode 100644 src/backend/tcop/deparse_utility.c
 create mode 100644 src/backend/utils/adt/ddl_json.c
 create mode 100644 src/include/tcop/deparse_utility.h

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 90bbb99..ea7670f 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -855,6 +855,121 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 }
 
 /*
+ * Return the OID of the catalog corresponding to the given object type
+ */
+Oid
+get_objtype_catalog_oid(ObjectType objtype)
+{
+	Oid		catalog_id;
+
+	switch (objtype)
+	{
+		case OBJECT_INDEX:
+		case OBJECT_COMPOSITE:
+		case OBJECT_SEQUENCE:
+		case OBJECT_TABLE:
+		case OBJECT_VIEW:
+		case OBJECT_MATVIEW:
+		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_COLUMN:
+			catalog_id = RelationRelationId;
+			break;
+		case OBJECT_RULE:
+			catalog_id = RewriteRelationId;
+			break;
+		case OBJECT_TRIGGER:
+			catalog_id = TriggerRelationId;
+			break;
+		case OBJECT_DOMCONSTRAINT:
+		case OBJECT_TABCONSTRAINT:
+			catalog_id = ConstraintRelationId;
+			break;
+		case OBJECT_POLICY:
+			catalog_id = PolicyRelationId;
+			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_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 catalog_id;
+}
+
+/*
  * Find an ObjectAddress for a type of object that is identified by an
  * unqualified name.
  */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index d14346f..aba0eb4 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -25,16 +25,20 @@
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/trigger.h"
 #include "funcapi.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -53,6 +57,7 @@ typedef struct EventTriggerQueryState
 	int			table_rewrite_reason;	/* AT_REWRITE reason */
 
 	MemoryContext cxt;
+	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -71,6 +76,7 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
 	{"AGGREGATE", true},
 	{"CAST", true},
@@ -1059,6 +1065,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CAST:
 		case OBJECT_COLUMN:
 		case OBJECT_COLLATION:
+		case OBJECT_COMPOSITE:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFAULT:
 		case OBJECT_DOMAIN:
@@ -1087,6 +1094,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
+		case OBJECT_USER_MAPPING:
 		case OBJECT_VIEW:
 			return true;
 	}
@@ -1192,14 +1200,6 @@ EventTriggerBeginCompleteQuery(void)
 	EventTriggerQueryState *state;
 	MemoryContext cxt;
 
-	/*
-	 * Currently, sql_drop and table_rewrite events are the only reason to
-	 * have event trigger state at all; so if there are none, don't install
-	 * one.
-	 */
-	if (!trackDroppedObjectsNeeded())
-		return false;
-
 	cxt = AllocSetContextCreate(TopMemoryContext,
 								"event trigger state",
 								ALLOCSET_DEFAULT_MINSIZE,
@@ -1210,7 +1210,7 @@ EventTriggerBeginCompleteQuery(void)
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
-
+	state->stash = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
 
@@ -1535,3 +1535,212 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
 }
+
+/*
+ * EventTriggerStashCommand
+ * 		Save data about a simple DDL command that was just executed
+ *
+ * address identifies the object being operated on.  secondaryOid is the OID of
+ * an object that was related in some way to the executed command; its meaning
+ * is command-specific.
+ *
+ * For instance, for an ALTER obj SET SCHEMA command, objtype is the type of
+ * object being moved, objectId is its OID, and secondaryOid is the OID of the
+ * old schema.  (The destination schema OID can be obtained by catalog lookup
+ * of the object.)
+ */
+void
+EventTriggerStashCommand(ObjectAddress address, Oid secondaryOid,
+						 Node *parsetree)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_Simple;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.simple.address = address;
+	stashed->d.simple.secondaryOid = secondaryOid;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+Datum
+pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_get_creation_commands()")));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->stash)
+	{
+		StashedCommand *cmd = lfirst(lc);
+		char	   *command;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid; in those cases, return an empty
+		 * command instead of trying to soldier on.
+		 *
+		 * One might think that a viable alternative would be to look up the
+		 * Oid of the existing object and run the deparse with that.  But since
+		 * the parse tree might be different from the one that created the
+		 * object in the first place, we might not end up in a consistent state
+		 * anyway.
+		 */
+		if (cmd->type == SCT_Simple &&
+			!OidIsValid(cmd->d.simple.address.objectId))
+			continue;
+
+		command = deparse_utility_command(cmd);
+
+		/*
+		 * Some parse trees return NULL when deparse is attempted; we don't
+		 * emit anything for them.
+		 */
+		if (command != NULL)
+		{
+			Datum		values[9];
+			bool		nulls[9];
+			ObjectAddress addr;
+			int			i = 0;
+
+			MemSet(nulls, 0, sizeof(nulls));
+
+			if (cmd->type == SCT_Simple)
+			{
+				Oid			classId;
+				Oid			objId;
+				uint32		objSubId;
+				const char *tag;
+				char	   *identity;
+				char	   *type;
+				char	   *schema = NULL;
+
+				if (cmd->type == SCT_Simple)
+				{
+					classId = cmd->d.simple.address.classId;
+					objId = cmd->d.simple.address.objectId;
+					objSubId = cmd->d.simple.address.objectSubId;
+				}
+
+				tag = CreateCommandTag(cmd->parsetree);
+				addr.classId = classId;
+				addr.objectId = objId;
+				addr.objectSubId = objSubId;
+
+				type = getObjectTypeDescription(&addr);
+				identity = getObjectIdentity(&addr);
+
+				/*
+				 * Obtain schema name, if any ("pg_temp" if a temp object)
+				 */
+				if (is_objectclass_supported(addr.classId))
+				{
+					AttrNumber	nspAttnum;
+
+					nspAttnum = get_object_attnum_namespace(addr.classId);
+					if (nspAttnum != InvalidAttrNumber)
+					{
+						Relation	catalog;
+						HeapTuple	objtup;
+						Oid			schema_oid;
+						bool		isnull;
+
+						catalog = heap_open(addr.classId, AccessShareLock);
+						objtup = get_catalog_object_by_oid(catalog,
+														   addr.objectId);
+						if (!HeapTupleIsValid(objtup))
+							elog(ERROR, "cache lookup failed for object %u/%u",
+								 addr.classId, addr.objectId);
+						schema_oid = heap_getattr(objtup, nspAttnum,
+												  RelationGetDescr(catalog), &isnull);
+						if (isnull)
+							elog(ERROR, "invalid null namespace in object %u/%u/%d",
+								 addr.classId, addr.objectId, addr.objectSubId);
+						if (isAnyTempNamespace(schema_oid))
+							schema = pstrdup("pg_temp");
+						else
+							schema = get_namespace_name(schema_oid);
+
+						heap_close(catalog, AccessShareLock);
+					}
+				}
+
+				/* classid */
+				values[i++] = ObjectIdGetDatum(addr.classId);
+				/* objid */
+				values[i++] = ObjectIdGetDatum(addr.objectId);
+				/* objsubid */
+				values[i++] = Int32GetDatum(addr.objectSubId);
+				/* command tag */
+				values[i++] = CStringGetTextDatum(tag);
+				/* object_type */
+				values[i++] = CStringGetTextDatum(type);
+				/* schema */
+				if (schema == NULL)
+					nulls[i++] = true;
+				else
+					values[i++] = CStringGetTextDatum(schema);
+				/* identity */
+				values[i++] = CStringGetTextDatum(identity);
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+			}
+
+			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+		}
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
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..4aa8ba5
--- /dev/null
+++ b/src/backend/tcop/deparse_utility.c
@@ -0,0 +1,942 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.c
+ *	  Functions to convert utility commands to machine-parseable
+ *	  representation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ *
+ * This is intended to provide JSON blobs representing DDL commands, which can
+ * later be re-processed into plain strings by well-defined sprintf-like
+ * expansion.  These JSON objects are intended to allow for machine-editing of
+ * the commands, by replacing certain nodes within the objects.
+ *
+ * Much of the information in the output blob actually comes from system
+ * catalogs, not from the command parse node, as it is impossible to reliably
+ * construct a fully-specified command (i.e. one not dependent on search_path
+ * etc) looking only at the parse node.
+ *
+ * IDENTIFICATION
+ *	  src/backend/tcop/deparse_utility.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/heap.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "lib/ilist.h"
+#include "lib/stringinfo.h"
+#include "nodes/makefuncs.h"
+#include "nodes/parsenodes.h"
+#include "parser/analyze.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/jsonb.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Before they are turned into JSONB representation, each command is
+ * represented as an object tree, using the structs below.
+ */
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeInteger,
+	ObjTypeFloat,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjTree
+{
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+
+	union
+	{
+		bool		boolean;
+		char	   *string;
+		int64		integer;
+		float8		flt;
+		ObjTree	   *object;
+		List	   *array;
+	} value;
+	slist_node	node;
+} ObjElem;
+
+static ObjElem *new_null_object(void);
+static ObjElem *new_bool_object(bool value);
+static ObjElem *new_string_object(char *value);
+static ObjElem *new_object_object(ObjTree *value);
+static ObjElem *new_array_object(List *array);
+static ObjElem *new_integer_object(int64 value);
+static ObjElem *new_float_object(float8 value);
+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);
+#ifdef NOT_USED
+static void append_integer_object(ObjTree *tree, char *name, int64 value);
+#endif
+static void append_float_object(ObjTree *tree, char *name, float8 value);
+static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
+static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+
+/*
+ * Allocate a new object tree to store parameter values.
+ */
+static ObjTree *
+new_objtree(void)
+{
+	ObjTree    *params;
+
+	params = palloc(sizeof(ObjTree));
+	params->numParams = 0;
+	slist_init(&params->params);
+
+	return params;
+}
+
+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one, a
+ * name (string), type (from the ObjType enum) and value must be supplied.  The
+ * value must match the type given; for instance, ObjTypeInteger requires an
+ * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of
+ * ObjElem), ObjTypeObject requires an ObjTree, and so on.  Each element type *
+ * must match the conversion specifier given in the format string, as described
+ * in pg_event_trigger_expand_command, q.v.
+ *
+ * Note we don't have the luxury of sprintf-like compiler warnings for
+ * malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree();
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		char	   *name;
+		ObjType		type;
+		ObjElem	   *elem;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/*
+		 * 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:
+				elem = new_bool_object(va_arg(args, int));
+				break;
+			case ObjTypeString:
+				elem = new_string_object(va_arg(args, char *));
+				break;
+			case ObjTypeObject:
+				elem = new_object_object(va_arg(args, ObjTree *));
+				break;
+			case ObjTypeArray:
+				elem = new_array_object(va_arg(args, List *));
+				break;
+			case ObjTypeInteger:
+				elem = new_integer_object(va_arg(args, int64));
+				break;
+			case ObjTypeFloat:
+				elem = new_float_object(va_arg(args, double));
+				break;
+			case ObjTypeNull:
+				/* Null params don't have a value (obviously) */
+				elem = new_null_object();
+				break;
+			default:
+				elog(ERROR, "invalid ObjTree element type %d", type);
+		}
+
+		elem->name = name;
+		append_premade_object(tree, elem);
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/* Allocate a new parameter with a NULL value */
+static ObjElem *
+new_null_object(void)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = NULL;
+	param->objtype = ObjTypeNull;
+
+	return param;
+}
+
+/* Append a NULL object to a tree */
+static void
+append_null_object(ObjTree *tree, char *name)
+{
+	ObjElem    *param;
+
+	param = new_null_object();
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new boolean parameter */
+static ObjElem *
+new_bool_object(bool value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeBool;
+	param->value.boolean = value;
+
+	return param;
+}
+
+/* Append a boolean parameter to a tree */
+static void
+append_bool_object(ObjTree *tree, char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = new_bool_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new string object */
+static ObjElem *
+new_string_object(char *value)
+{
+	ObjElem    *param;
+
+	Assert(value);
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeString;
+	param->value.string = value;
+
+	return param;
+}
+
+/*
+ * Append a string parameter to a tree.
+ */
+static void
+append_string_object(ObjTree *tree, char *name, char *value)
+{
+	ObjElem	   *param;
+
+	Assert(name);
+	param = new_string_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+static ObjElem *
+new_integer_object(int64 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeInteger;
+	param->value.integer = value;
+
+	return param;
+}
+
+static ObjElem *
+new_float_object(float8 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeFloat;
+	param->value.flt = value;
+
+	return param;
+}
+
+#ifdef NOT_USED
+/*
+ * Append an int64 parameter to a tree.
+ */
+static void
+append_integer_object(ObjTree *tree, char *name, int64 value)
+{
+	ObjElem	   *param;
+
+	param = new_integer_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+#endif
+
+/*
+ * Append a float8 parameter to a tree.
+ */
+static void
+append_float_object(ObjTree *tree, char *name, float8 value)
+{
+	ObjElem	   *param;
+
+	param = new_float_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new object parameter */
+static ObjElem *
+new_object_object(ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeObject;
+	param->value.object = value;
+
+	return param;
+}
+
+/* Append an object parameter to a tree */
+static void
+append_object_object(ObjTree *tree, char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	Assert(name);
+	param = new_object_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new array parameter */
+static ObjElem *
+new_array_object(List *array)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeArray;
+	param->value.array = array;
+
+	return param;
+}
+
+/* Append an array parameter to a tree */
+static void
+append_array_object(ObjTree *tree, char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = new_array_object(array);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Append a preallocated parameter to a tree */
+static inline void
+append_premade_object(ObjTree *tree, ObjElem *elem)
+{
+	slist_push_head(&tree->params, &elem->node);
+	tree->numParams++;
+}
+
+/*
+ * Helper for objtree_to_jsonb: process an individual element from an object or
+ * an array into the output parse state.
+ */
+static void
+objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object,
+						 JsonbIteratorToken elem_token)
+{
+	ListCell   *cell;
+	JsonbValue	val;
+
+	switch (object->objtype)
+	{
+		case ObjTypeNull:
+			val.type = jbvNull;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeString:
+			val.type = jbvString;
+			val.val.string.len = strlen(object->value.string);
+			val.val.string.val = object->value.string;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeInteger:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeFloat:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(float8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeBool:
+			val.type = jbvBool;
+			val.val.boolean = object->value.boolean;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeObject:
+			/* recursively add the object into the existing parse state */
+			objtree_to_jsonb_rec(object->value.object, state);
+			break;
+
+		case ObjTypeArray:
+			pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+			foreach(cell, object->value.array)
+			{
+				ObjElem   *elem = lfirst(cell);
+
+				objtree_to_jsonb_element(state, elem, WJB_ELEM);
+			}
+			pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+			break;
+
+		default:
+			elog(ERROR, "unrecognized object type %d", object->objtype);
+			break;
+	}
+}
+
+/*
+ * Recursive helper for objtree_to_jsonb
+ */
+static JsonbValue *
+objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state)
+{
+	slist_iter	iter;
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	slist_foreach(iter, &tree->params)
+	{
+		ObjElem    *object = slist_container(ObjElem, node, iter.cur);
+		JsonbValue	key;
+
+		/* Push the key first */
+		key.type = jbvString;
+		key.val.string.len = strlen(object->name);
+		key.val.string.val = object->name;
+		pushJsonbValue(&state, WJB_KEY, &key);
+
+		/* Then process the value according to its type */
+		objtree_to_jsonb_element(state, object, WJB_VALUE);
+	}
+
+	return pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Create a JSONB representation from an ObjTree.
+ */
+static Jsonb *
+objtree_to_jsonb(ObjTree *tree)
+{
+	JsonbValue *value;
+
+	value = objtree_to_jsonb_rec(tree, NULL);
+	return JsonbValueToJsonb(value);
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{
+	ObjTree    *typeParam;
+	Oid			typnspid;
+	char	   *typnsp;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		is_array;
+
+	format_type_detailed(typeId, typmod,
+						 &typnspid, &typename, &typmodstr, &is_array);
+
+	if (!OidIsValid(typnspid))
+		typnsp = pstrdup("");
+	else if (isAnyTempNamespace(typnspid))
+		typnsp = pstrdup("pg_temp");
+	else
+		typnsp = get_namespace_name(typnspid);
+
+	/* We don't use new_objtree_VA here because types don't have a "fmt" */
+	typeParam = new_objtree();
+	append_string_object(typeParam, "schemaname", typnsp);
+	append_string_object(typeParam, "typename", typename);
+	append_string_object(typeParam, "typmod", typmodstr);
+	append_bool_object(typeParam, "is_array", is_array);
+
+	return typeParam;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements
+ *
+ * Elements "schemaname" and "objname" are set.  If the namespace OID
+ * corresponds to a temp schema, that's set to "pg_temp".
+ *
+ * The difference between those two element types is whether the objname will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static ObjTree *
+new_objtree_for_qualname(Oid nspid, char *name)
+{
+	ObjTree    *qualified;
+	char	   *namespace;
+
+	/*
+	 * We don't use new_objtree_VA here because these names don't have a "fmt"
+	 */
+	qualified = new_objtree();
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+	append_string_object(qualified, "schemaname", namespace);
+	append_string_object(qualified, "objname", pstrdup(name));
+
+	return qualified;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements, with the object specified
+ * by classId/objId
+ *
+ * Elements "schemaname" and "objname" are set.  If the object is a temporary
+ * object, the schema name is set to "pg_temp".
+ */
+static ObjTree *
+new_objtree_for_qualname_id(Oid classId, Oid objectId)
+{
+	ObjTree    *qualified;
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		objnsp;
+	Datum		objname;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	bool		isnull;
+
+	catalog = heap_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL namespace");
+	objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL name");
+
+	qualified = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+										 NameStr(*DatumGetName(objname)));
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function contains a large switch that mirrors that in
+ * ProcessUtilitySlow.  All cases covered there should also be covered here.
+ */
+static ObjTree *
+deparse_simple_command(StashedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+	ObjTree	   *command;
+
+	Assert(cmd->type == SCT_Simple);
+
+	parsetree = cmd->parsetree;
+	objectId = cmd->d.simple.address.objectId;
+
+	/* This switch needs to handle everything that ProcessUtilitySlow does */
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateForeignTableStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTableStmt:
+		case T_AlterTableMoveAllStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterDomainStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+			/* other local objects */
+		case T_DefineStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_IndexStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateExtensionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterExtensionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterExtensionContentsStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropUserMappingStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_ImportForeignSchemaStmt:
+			/* generated commands are stashed individually */
+			elog(ERROR, "unexpected command %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterEnumStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_ViewStmt:		/* CREATE VIEW */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateFunctionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterFunctionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_RuleStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateSeqStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterSeqStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateTableAsStmt:
+			/* XXX handle at least the CREATE MATVIEW case? */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_RefreshMatViewStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateTrigStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePLangStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateDomainStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateConversionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateCastStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpClassStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpFamilyStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterOpFamilyStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSDictionaryStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSConfigurationStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_RenameStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterObjectSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterOwnerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CommentStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_GrantStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropOwnedStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_AlterDefaultPrivilegesStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePolicyStmt:	/* CREATE POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterPolicyStmt:		/* ALTER POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_SecLabelStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		default:
+			command = NULL;
+			elog(LOG, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return command;
+}
+
+/*
+ * Given a utility command parsetree and the OID of the corresponding object,
+ * return a JSON representation of the command.
+ *
+ * The command is expanded fully, so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+char *
+deparse_utility_command(StashedCommand *cmd)
+{
+	OverrideSearchPath *overridePath;
+	MemoryContext	oldcxt;
+	MemoryContext	tmpcxt;
+	ObjTree		   *tree;
+	char		   *command;
+	StringInfoData  str;
+
+	/*
+	 * Allocate everything done by the deparsing routines into a temp context,
+	 * to avoid having to sprinkle them with memory handling code; but allocate
+	 * the output StringInfo before switching.
+	 */
+	initStringInfo(&str);
+	tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "deparse ctx",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * in order to obtain deparsed versions of expressions.  In such results,
+	 * we want all object names to be qualified, so that results are "portable"
+	 * to environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = false;
+	PushOverrideSearchPath(overridePath);
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			tree = deparse_simple_command(cmd);
+			break;
+		default:
+			elog(ERROR, "unexpected deparse node type %d", cmd->type);
+	}
+
+	PopOverrideSearchPath();
+
+	if (tree)
+	{
+		Jsonb *jsonb;
+
+		jsonb = objtree_to_jsonb(tree);
+		command = JsonbToCString(&str, &jsonb->root, 128);
+	}
+	else
+		command = NULL;
+
+	/*
+	 * Clean up.  Note that since we created the StringInfo in the caller's
+	 * context, the output string is not deleted here.
+	 */
+	MemoryContextSwitchTo(oldcxt);
+	MemoryContextDelete(tmpcxt);
+
+	return command;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6ef5db1..bb9ddc7 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -874,6 +874,8 @@ standard_ProcessUtility(Node *parsetree,
  * The "Slow" variant of ProcessUtility should only receive statements
  * supported by the event triggers facility.  Therefore, we always
  * perform the trigger support calls if the context allows it.
+ *
+ * See deparse_utility_command, which must be kept in sync with this.
  */
 static void
 ProcessUtilitySlow(Node *parsetree,
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 20e5ff1..110c6ee 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -18,7 +18,7 @@ endif
 # keep this list arranged alphabetically or it gets to be a mess
 OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
 	array_userfuncs.o arrayutils.o ascii.o bool.o \
-	cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \
+	cash.o char.o date.o datetime.o datum.o dbsize.o ddl_json.o domains.o \
 	encode.o enum.o float.o format_type.o formatting.o genfile.o \
 	geo_ops.o geo_selfuncs.o inet_cidr_ntop.o inet_net_pton.o int.o \
 	int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
diff --git a/src/backend/utils/adt/ddl_json.c b/src/backend/utils/adt/ddl_json.c
new file mode 100644
index 0000000..5b0326c
--- /dev/null
+++ b/src/backend/utils/adt/ddl_json.c
@@ -0,0 +1,676 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_json.c
+ *	  JSON code related to DDL command deparsing
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/ddl_json.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "lib/stringinfo.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+
+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	SpecNumber,
+	SpecStringLiteral,
+	SpecIdentifier
+} convSpecifier;
+
+typedef enum
+{
+	tv_absent,
+	tv_true,
+	tv_false
+} trivalue;
+
+static void expand_one_jsonb_element(StringInfo out, char *param,
+						 JsonbValue *jsonval, convSpecifier specifier,
+						 const char *fmt);
+static void expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep,
+				   convSpecifier specifier, const char *fmt);
+static void fmtstr_error_callback(void *arg);
+
+static trivalue
+find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	bool		result;
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+		return tv_absent;
+	if (value->type != jbvBool)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not of type boolean",
+						keyname)));
+	result = value->val.boolean ? tv_true : tv_false;
+	pfree(value);
+
+	return result;
+}
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvString, an error is raised.  If it doesn't
+ * exist, an error is raised if missing_ok; otherwise return NULL.
+ *
+ * If it exists and is a string, a freshly palloc'ed copy is returned.
+ *
+ * If *length is not NULL, it is set to the length of the string.
+ */
+static char *
+find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname,
+							  bool missing_ok, int *length)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	char	   *str;
+
+	/* XXX verify that this is an object, not an array */
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+	{
+		if (missing_ok)
+			return NULL;
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing element \"%s\" in json object", keyname)));
+	}
+
+	str = pnstrdup(value->val.string.val, value->val.string.len);
+	if (length)
+		*length = value->val.string.len;
+	pfree(value);
+	return str;
+}
+
+#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)
+
+/*
+ * Recursive helper for pg_event_trigger_expand_command
+ *
+ * Find the "fmt" element in the given container, and expand it into the
+ * provided StringInfo.
+ */
+static void
+expand_fmt_recursive(JsonbContainer *container, StringInfo out)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	int			len;
+
+	start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len);
+	end_ptr = start_ptr + len;
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{
+		convSpecifier specifier;
+		bool		is_array;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		is_array = false;
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			for (; cp < end_ptr;)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing conversion name in conversion specifier")));
+
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'L':
+				specifier = SpecStringLiteral;
+				break;
+			case 'T':
+				specifier = SpecTypename;
+				break;
+			case 'O':
+				specifier = SpecOperatorname;
+				break;
+			case 'n':
+				specifier = SpecNumber;
+				break;
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid conversion specifier \"%c\"", *cp)));
+		}
+
+		/*
+		 * Obtain the element to be expanded.
+		 */
+		key.type = jbvString;
+		key.val.string.val = param;
+		key.val.string.len = strlen(param);
+
+		value = findJsonbValueFromContainer(container, JB_FOBJECT, &key);
+
+		/* Validate that we got an array if the format string specified one. */
+
+		/* And finally print out the data */
+		if (is_array)
+			expand_jsonb_array(out, param, value, arraysep, specifier, start_ptr);
+		else
+			expand_one_jsonb_element(out, param, value, specifier, start_ptr);
+	}
+}
+
+/*
+ * Expand a json value as an identifier.  The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	Assert(jsonval->type == jbvString);
+
+	str = pnstrdup(jsonval->val.string.val,
+				   jsonval->val.string.len);
+	appendStringInfoString(buf, quote_identifier(str));
+	pfree(str);
+}
+
+/*
+ * Expand a json value as a dot-separated-name.  The value must be of type
+ * object and must contain elements "schemaname" (optional), "objname"
+ * (mandatory), "attrname" (optional).  Double quotes are added to each element
+ * as necessary, and dot separators where needed.
+ *
+ * One day we might need a "catalog" element as well, but no current use case
+ * needs that.
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+		pfree(str);
+	}
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfo(buf, "%s", quote_identifier(str));
+	pfree(str);
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"attrname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, ".%s", quote_identifier(str));
+		pfree(str);
+	}
+}
+
+/*
+ * expand a json value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename;
+	char	   *typmodstr;
+	trivalue	is_array;
+	char	   *array_decor;
+
+	/*
+	 * We omit schema-qualifying the output name if the schema element is
+	 * either the empty string or NULL; the difference between those two cases
+	 * is that in the latter we quote the type name, in the former we don't.
+	 * This allows for types with special typmod needs, such as interval and
+	 * timestamp (see format_type_detailed), while at the same time allowing
+	 * for the schema name to be omitted from type names that require quotes
+	 * but are to be obtained from a user schema.
+	 */
+
+	schema = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										   "schemaname", true, NULL);
+	typename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											 "typename", false, NULL);
+	typmodstr = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											  "typmod", true, NULL);
+	is_array = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+										   "is_array");
+	switch (is_array)
+	{
+		default:
+		case tv_absent:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("missing is_array element")));
+			break;
+		case tv_true:
+			array_decor = "[]";
+			break;
+		case tv_false:
+			array_decor = "";
+			break;
+	}
+
+	if (schema == NULL)
+		appendStringInfo(buf, "%s%s%s",
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else if (schema[0] == '\0')
+		appendStringInfo(buf, "%s%s%s",
+						 typename,
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else
+		appendStringInfo(buf, "%s.%s%s%s",
+						 quote_identifier(schema),
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+}
+
+/*
+ * Expand a json value as an operator name
+ */
+static void
+expand_jsonval_operator(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	/* schema might be NULL or empty */
+	if (str != NULL && str[0] != '\0')
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfoString(buf, str);
+}
+
+/*
+ * Expand a json value as a string.  The value must be of type string or of
+ * type object.  In the latter 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, JsonbValue *jsonval)
+{
+	if (jsonval->type == jbvString)
+	{
+		appendBinaryStringInfo(buf, jsonval->val.string.val,
+							   jsonval->val.string.len);
+	}
+	else if (jsonval->type == jbvBinary)
+	{
+		trivalue	present;
+
+		present = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											  "present");
+		/*
+		 * If "present" is set to false, this element expands to empty;
+		 * otherwise (either true or absent), fall through to expand "fmt".
+		 */
+		if (present == tv_false)
+			return;
+
+		expand_fmt_recursive(jsonval->val.binary.data, buf);
+	}
+}
+
+/*
+ * Expand a json value as a string literal
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval)
+{
+	char   *str;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int         dqnextchar = 0;
+
+	str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+
+	/* easy case: if there are no ' and no \, just use a single quote */
+	if (strchr(str, '\'') == NULL &&
+		strchr(str, '\\') == NULL)
+	{
+		appendStringInfo(buf, "'%s'", str);
+		pfree(str);
+		return;
+	}
+
+	/* Otherwise need to find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(str, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar %= sizeof(dqsuffixes) - 1;
+	}
+	/* add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data);
+	pfree(dqdelim.data);
+	pfree(str);
+}
+
+/*
+ * Expand a json value as an integer quantity
+ */
+static void
+expand_jsonval_number(StringInfo buf, JsonbValue *jsonval)
+{
+	char *strdatum;
+
+	strdatum = DatumGetCString(DirectFunctionCall1(numeric_out,
+												   NumericGetDatum(jsonval->val.numeric)));
+	appendStringInfoString(buf, strdatum);
+}
+
+/*
+ * Expand one json element into the output StringInfo according to the
+ * conversion specifier.  The element type is validated, and an error is raised
+ * if it doesn't match what we expect for the conversion specifier.
+ */
+static void
+expand_one_jsonb_element(StringInfo out, char *param, JsonbValue *jsonval,
+						 convSpecifier specifier, const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (!jsonval)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" not found", param)));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%I element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_identifier(out, jsonval);
+			break;
+
+		case SpecDottedName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%D element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_dottedname(out, jsonval);
+			break;
+
+		case SpecString:
+			if (jsonval->type != jbvString &&
+				jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string or object for %%s element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_string(out, jsonval);
+			break;
+
+		case SpecStringLiteral:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%L element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_strlit(out, jsonval);
+			break;
+
+		case SpecTypename:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%T element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_typename(out, jsonval);
+			break;
+
+		case SpecOperatorname:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%O element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_operator(out, jsonval);
+			break;
+
+		case SpecNumber:
+			if (jsonval->type != jbvNumeric)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON numeric for %%n element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_number(out, jsonval);
+			break;
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*
+ * Iterate on the elements of a JSON array, expanding each one into the output
+ * StringInfo per the given conversion specifier, separated by the given
+ * separator.
+ */
+static void
+expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep, convSpecifier specifier,
+				   const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+	JsonbContainer *container;
+	JsonbIterator  *it;
+	JsonbValue	v;
+	int			type;
+	bool		first = true;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (jsonarr->type != jbvBinary)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	container = jsonarr->val.binary.data;
+	if ((container->header & JB_FARRAY) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	it = JsonbIteratorInit(container);
+	while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+	{
+		switch (type)
+		{
+			case WJB_ELEM:
+				if (!first)
+					appendStringInfoString(out, arraysep);
+				first = false;
+				expand_one_jsonb_element(out, param, &v, specifier, NULL);
+				break;
+		}
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ * n		expand as a simple number (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.
+ *------
+ */
+Datum
+pg_event_trigger_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	Datum		d;
+	Jsonb	   *jsonb;
+	StringInfoData out;
+
+	initStringInfo(&out);
+	d = DirectFunctionCall1(jsonb_in,
+							PointerGetDatum(TextDatumGetCString(json)));
+	jsonb = (Jsonb *) DatumGetPointer(d);
+
+	expand_fmt_recursive(&jsonb->root, &out);
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(out.data));
+}
+
+/*
+ * Error context callback for JSON format string expansion.
+ *
+ * Possible improvement: indicate which element we're expanding, if applicable
+ */
+static void
+fmtstr_error_callback(void *arg)
+{
+	errcontext("while expanding format string \"%s\"", (char *) arg);
+
+}
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index fc816ce..dbf6b60 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
 	return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
@@ -323,6 +326,106 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
+/*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - is_array indicates whether []s must be added
+ *
+ * Also, we don't try to decode type names to their standard-mandated names,
+ * except in the cases of unusual typmod rules, as specified above.
+ *
+ * XXX there is a lot of code duplication between this routine and
+ * format_type_internal.  (One thing that doesn't quite match is the whole
+ * allow_invalid business.)
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *is_array)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID)
+	{
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPOID:
+			case TIMESTAMPTZOID:
+				/* the TZ part is added by typmod */
+				*typname = pstrdup("TIMESTAMP");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		*is_array = false;
+
+		ReleaseSysCache(tuple);
+		return;
+	}
+
+	/*
+	 * Check if it's a regular (variable length) array type.  As above,
+	 * fixed-length array types such as "name" shouldn't get deconstructed.
+	 */
+	array_base_type = typeform->typelem;
+
+	if (array_base_type != InvalidOid &&
+		typeform->typstorage != 'p')
+	{
+		/* Switch our attention to the array element type */
+		ReleaseSysCache(tuple);
+		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+		typeform = (Form_pg_type) GETSTRUCT(tuple);
+		type_oid = array_base_type;
+		*is_array = true;
+	}
+	else
+		*is_array = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
 
 /*
  * Add typmod decoration to the basic type name
@@ -338,7 +441,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,7 +453,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 644ea6d..966c9bd 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -416,7 +416,7 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 {
 	bool		first = true;
 	JsonbIterator *it;
-	int			type = 0;
+	JsonbIteratorToken type = WJB_DONE;
 	JsonbValue	v;
 	int			level = 0;
 	bool		redo_switch = false;
@@ -498,7 +498,7 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
 				first = false;
 				break;
 			default:
-				elog(ERROR, "unknown flag of jsonb iterator");
+				elog(ERROR, "unknown jsonb iterator token type");
 		}
 	}
 
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 619b2f5..d7d9d28 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -44,6 +44,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 4268b99..f642c0d 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5084,12 +5084,15 @@ DESCR("peek at binary changes from replication slot");
 
 /* event triggers */
 DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,16,16,25,25,25,25,1009,1009}" "{o,o,o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, original, normal, object_type, schema_name, object_name, object_identity, address_names, address_args}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
-
 DESCR("list objects dropped by the current command");
 DATA(insert OID = 4566 (  pg_event_trigger_table_rewrite_oid	PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 26 "" "{26}" "{o}" "{oid}" _null_ pg_event_trigger_table_rewrite_oid _null_ _null_ _null_ ));
 DESCR("return Oid of the table getting rewritten");
 DATA(insert OID = 4567 (  pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ ));
 DESCR("return reason code for table getting rewritten");
+DATA(insert OID = 3590 (  pg_event_trigger_get_creation_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,114}" "{o,o,o,o,o,o,o,o,o}" "{classid,objid,objsubid,command_tag,object_type,schema,identity,in_extension,command}" _null_ pg_event_trigger_get_creation_commands _null_ _null_ _null_ ));
+DESCR("list JSON-formatted commands executed by the current command");
+DATA(insert OID = 3591 (  pg_event_trigger_expand_command PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 25 "114" _null_ _null_ _null_ _null_ pg_event_trigger_expand_command _null_ _null_ _null_ ));
+DESCR("format JSON command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 9ac9fc3..be37875 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -60,4 +60,7 @@ extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 							 bool original, bool normal);
 
+extern void EventTriggerStashCommand(ObjectAddress address, Oid secondaryOid,
+						 Node *parsetree);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 8df38f2..965f96b 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -23,7 +23,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 09607eb..34c8ae5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1215,6 +1215,7 @@ typedef enum ObjectType
 	OBJECT_CAST,
 	OBJECT_COLUMN,
 	OBJECT_COLLATION,
+	OBJECT_COMPOSITE,
 	OBJECT_CONVERSION,
 	OBJECT_DATABASE,
 	OBJECT_DEFAULT,
@@ -1247,6 +1248,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..99e130d
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+#include "access/attnum.h"
+#include "nodes/nodes.h"
+
+/*
+ * Support for keeping track of a command to deparse.
+ *
+ * When a command is run, we collect some information about it for later
+ * deparsing; deparse_utility_command can later be used to obtain a usable
+ * representation of it.
+ */
+
+typedef enum StashedCommandType
+{
+	SCT_Simple,
+} StashedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct StashedATSubcmd
+{
+	AttrNumber		attnum;	/* affected column number */
+	Oid				oid;	/* affected constraint, default value or index */
+	Node		   *parsetree;
+} StashedATSubcmd;
+
+typedef struct StashedCommand
+{
+	StashedCommandType type;
+	bool		in_extension;
+	Node	   *parsetree;
+
+	union
+	{
+		struct SimpleCommand
+		{
+			ObjectAddress address;
+			Oid			secondaryOid;
+		} simple;
+	} d;
+} StashedCommand;
+
+extern char *deparse_utility_command(StashedCommand *cmd);
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index bc4517d..470a325 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1079,6 +1079,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);
@@ -1210,6 +1213,10 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS);
+
+/* utils/adt/ddl_json.c */
+extern Datum pg_event_trigger_expand_command(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
-- 
2.1.4

0008-deparse-add-EventTriggerStashCommand-calls-to-Proces.patchtext/x-diff; charset=us-asciiDownload
>From 5afd8020a0edd7937a1fdf43a3d41a475959367d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:50:37 -0300
Subject: [PATCH 08/44] deparse: add EventTriggerStashCommand() calls to
 ProcessUtilitySlow

These calls add each executed command to the event trigger command
stash.
---
 src/backend/commands/schemacmds.c |  14 ++
 src/backend/tcop/utility.c        | 270 +++++++++++++++++++++++++++-----------
 2 files changed, 206 insertions(+), 78 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 77b5e21..59f50bf 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"
@@ -52,6 +53,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	Oid			saved_uid;
 	int			save_sec_context;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
 
@@ -129,6 +131,18 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	/* XXX should we clear overridePath->useTemp? */
 	PushOverrideSearchPath(overridePath);
 
+	address.classId = NamespaceRelationId;
+	address.objectId = namespaceId;
+	address.objectSubId = 0;
+
+	/*
+	 * 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(address, InvalidOid, (Node *) stmt);
+
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
 	 * reorganize them into a sequentially executable order with no forward
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index bb9ddc7..084adcb 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -888,7 +888,11 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	bool		commandStashed = false;
+	ObjectType	objectType;
+	Oid			objectId = InvalidOid;
 	ObjectAddress address;
+	Oid			secondaryOid = InvalidOid;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -907,6 +911,12 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * CreateSchemaCommand calls EventTriggerStashCommand
+				 * internally, for reasons explained there, so turn off
+				 * command collection below.
+				 */
+				commandStashed = true;
 				break;
 
 			case T_CreateStmt:
@@ -931,8 +941,10 @@ ProcessUtilitySlow(Node *parsetree,
 
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
-													 RELKIND_RELATION,
-													 InvalidOid);
+													  RELKIND_RELATION,
+													  InvalidOid);
+							EventTriggerStashCommand(address, InvalidOid,
+													 stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -965,10 +977,15 @@ ProcessUtilitySlow(Node *parsetree,
 													 InvalidOid);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
+							EventTriggerStashCommand(address, InvalidOid, 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,
@@ -977,6 +994,12 @@ ProcessUtilitySlow(Node *parsetree,
 										   NULL);
 						}
 
+						/*
+						 * The multiple commands generated here are stashed
+						 * individually, so disable collection below.
+						 */
+						commandStashed = true;
+
 						/* Need CCI between commands */
 						if (lnext(l) != NULL)
 							CommandCounterIncrement();
@@ -1039,6 +1062,9 @@ ProcessUtilitySlow(Node *parsetree,
 						  (errmsg("relation \"%s\" does not exist, skipping",
 								  atstmt->relation->relname)));
 				}
+
+				/* ALTER TABLE stashes commands internally */
+				commandStashed = true;
 				break;
 
 			case T_AlterDomainStmt:
@@ -1057,30 +1083,36 @@ ProcessUtilitySlow(Node *parsetree,
 							 * Recursively alter column default for table and,
 							 * if requested, for descendants
 							 */
-							AlterDomainDefault(stmt->typeName,
-											   stmt->def);
+							address =
+								AlterDomainDefault(stmt->typeName,
+												   stmt->def);
 							break;
 						case 'N':		/* ALTER DOMAIN DROP NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   false);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   false);
 							break;
 						case 'O':		/* ALTER DOMAIN SET NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   true);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   true);
 							break;
 						case 'C':		/* ADD CONSTRAINT */
-							AlterDomainAddConstraint(stmt->typeName,
-													 stmt->def);
+							address =
+								AlterDomainAddConstraint(stmt->typeName,
+														 stmt->def);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
-							AlterDomainDropConstraint(stmt->typeName,
-													  stmt->name,
-													  stmt->behavior,
-													  stmt->missing_ok);
+							address =
+								AlterDomainDropConstraint(stmt->typeName,
+														  stmt->name,
+														  stmt->behavior,
+														  stmt->missing_ok);
 							break;
 						case 'V':		/* VALIDATE CONSTRAINT */
-							AlterDomainValidateConstraint(stmt->typeName,
-														  stmt->name);
+							address =
+								AlterDomainValidateConstraint(stmt->typeName,
+															  stmt->name);
 							break;
 						default:		/* oops */
 							elog(ERROR, "unrecognized alter domain type: %d",
@@ -1097,43 +1129,49 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					DefineStmt *stmt = (DefineStmt *) parsetree;
 
+					objectType = stmt->kind;
 					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",
@@ -1174,203 +1212,263 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
-					DefineIndex(relid,	/* OID of heap relation */
-								stmt,
-								InvalidOid,		/* no predefined OID */
-								false,	/* is_alter_table */
-								true,	/* check_rights */
-								false,	/* skip_build */
-								false); /* quiet */
+					address =
+						DefineIndex(relid,	/* OID of heap relation */
+									stmt,
+									InvalidOid,		/* no predefined OID */
+									false,	/* is_alter_table */
+									true,	/* check_rights */
+									false,	/* skip_build */
+									false); /* quiet */
+					/*
+					 * Add the CREATE INDEX node itself to stash right away; if
+					 * there were any commands stashed in the ALTER TABLE code,
+					 * we need them to appear after this one.
+					 */
+					EventTriggerStashCommand(address,
+											 InvalidOid, parsetree);
+					commandStashed = true;
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				objectId = CreateExtension((CreateExtensionStmt *) parsetree);
+				objectType = OBJECT_EXTENSION;
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				objectId = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				objectType = OBJECT_EXTENSION;
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
-											   NULL);
+				objectId = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+														  &secondaryOid);
+				objectType = OBJECT_EXTENSION;
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				objectType = OBJECT_FDW;
 				break;
 
 			case T_AlterFdwStmt:
-				AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				objectId = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				objectType = OBJECT_FDW;
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				objectType = OBJECT_FOREIGN_SERVER;
 				break;
 
 			case T_AlterForeignServerStmt:
-				AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				objectId = AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				objectType = OBJECT_FOREIGN_SERVER;
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				objectType = OBJECT_USER_MAPPING;
 				break;
 
 			case T_AlterUserMappingStmt:
-				AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				objectId = AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				objectType = OBJECT_USER_MAPPING;
 				break;
 
 			case T_DropUserMappingStmt:
 				RemoveUserMapping((DropUserMappingStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandStashed = true;
 				break;
 
 			case T_ImportForeignSchemaStmt:
 				ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+				/* commands are stashed inside ImportForeignSchema */
+				commandStashed = true;
 				break;
 
 			case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					objectId = DefineCompositeType(stmt->typevar,
+												   stmt->coldeflist);
+					objectType = OBJECT_COMPOSITE;
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				objectId = DefineEnum((CreateEnumStmt *) parsetree);
+				objectType = OBJECT_TYPE;
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				objectId = DefineRange((CreateRangeStmt *) parsetree);
+				objectType = OBJECT_TYPE;
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				objectId = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				objectType = OBJECT_TYPE;
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				address = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerStashCommand(address, InvalidOid, parsetree);
+				/* stashed internally */
+				commandStashed = true;
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				objectType = OBJECT_FUNCTION;
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				objectId = AlterFunction((AlterFunctionStmt *) parsetree);
+				objectType = OBJECT_FUNCTION;
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				objectId = DefineRule((RuleStmt *) parsetree, queryString);
+				objectType = OBJECT_RULE;
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				address = DefineSequence((CreateSeqStmt *) parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				objectId = AlterSequence((AlterSeqStmt *) parsetree);
+				objectType = OBJECT_SEQUENCE;
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
+				objectType = OBJECT_TABLE;
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+				objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
 								   queryString, params, completionTag);
+				objectType = OBJECT_MATVIEW;
 				break;
 
 			case T_CreateTrigStmt:
-				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
-									 InvalidOid, InvalidOid, InvalidOid,
-									 InvalidOid, false);
+				objectId = CreateTrigger((CreateTrigStmt *) parsetree,
+										 queryString, InvalidOid, InvalidOid,
+										 InvalidOid, InvalidOid, false);
+				objectType = OBJECT_TRIGGER;
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				objectType = OBJECT_LANGUAGE;
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				objectId = DefineDomain((CreateDomainStmt *) parsetree);
+				objectType = OBJECT_DOMAIN;
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				objectId = CreateConversionCommand((CreateConversionStmt *) parsetree);
+				objectType = OBJECT_CONVERSION;
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				objectId = CreateCast((CreateCastStmt *) parsetree);
+				objectType = OBJECT_CAST;
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				objectId = DefineOpClass((CreateOpClassStmt *) parsetree);
+				objectType = OBJECT_OPCLASS;
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				objectType = OBJECT_OPFAMILY;
 				break;
 
 			case T_AlterOpFamilyStmt:
-				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				objectId = AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				objectType = OBJECT_OPFAMILY;
 				break;
 
 			case T_AlterTSDictionaryStmt:
-				AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				objectId = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				objectType = OBJECT_TSDICTIONARY;
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				objectId = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				objectType = OBJECT_TSCONFIGURATION;
 				break;
 
 			case T_AlterTableMoveAllStmt:
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
+				/* commands are stashed in AlterTableMoveAll */
+				commandStashed = true;
 				break;
 
 			case T_DropStmt:
 				ExecDropStmt((DropStmt *) parsetree, isTopLevel);
+				/* no commands stashed for DROP */
+				commandStashed = true;
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree);
+				address = ExecRenameStmt((RenameStmt *) parsetree);
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
+				objectId = ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
+				objectType = ((AlterObjectSchemaStmt *) parsetree)->objectType;
 				break;
 
 			case T_AlterOwnerStmt:
-				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				objectId = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				objectType = ((AlterOwnerStmt *) parsetree)->objectType;
 				break;
 
 			case T_CommentStmt:
-				CommentObject((CommentStmt *) parsetree);
+				address = CommentObject((CommentStmt *) parsetree);
 				break;
 
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
+				/* commands are stashed in ExecGrantStmt_oids */
+				commandStashed = true;
 				break;
 
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandStashed = true;
 				break;
 
 			case T_AlterDefaultPrivilegesStmt:
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
+				/* XXX FIXME WTF? */
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
-				CreatePolicy((CreatePolicyStmt *) parsetree);
+				objectId = CreatePolicy((CreatePolicyStmt *) parsetree);
+				objectType = OBJECT_POLICY;
 				break;
 
 			case T_AlterPolicyStmt:		/* ALTER POLICY */
-				AlterPolicy((AlterPolicyStmt *) parsetree);
+				objectId = AlterPolicy((AlterPolicyStmt *) parsetree);
+				objectType = OBJECT_POLICY;
 				break;
 
 			case T_SecLabelStmt:
-				ExecSecLabelStmt((SecLabelStmt *) parsetree);
+				objectId = ExecSecLabelStmt((SecLabelStmt *) parsetree);
+				objectType = ((SecLabelStmt *) parsetree)->objtype;
 				break;
 
 			default:
@@ -1379,6 +1477,22 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 		}
 
+		/*
+		 * Remember the object so that ddl_command_end event triggers have
+		 * access to it.
+		 */
+		if (!commandStashed)
+		{
+			if (objectId != InvalidOid)
+			{
+				address.classId = get_objtype_catalog_oid(objectType);
+				address.objectId = objectId;
+				address.objectSubId = 0;
+			}
+
+			EventTriggerStashCommand(address, secondaryOid, parsetree);
+		}
+
 		if (isCompleteQuery)
 		{
 			EventTriggerSQLDrop(parsetree);
-- 
2.1.4

0009-deparse-Support-CREATE-TYPE-AS.patchtext/x-diff; charset=us-asciiDownload
>From b5af7ea67087fb49649383ffaf5a11a5caaae71b Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:54:00 -0300
Subject: [PATCH 09/44] deparse: Support CREATE TYPE AS

---
 src/backend/tcop/deparse_utility.c | 264 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  18 +++
 src/include/utils/ruleutils.h      |   3 +
 3 files changed, 284 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 4aa8ba5..b21845b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -624,6 +624,268 @@ new_objtree_for_qualname_id(Oid classId, Oid objectId)
 }
 
 /*
+ * 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(Relation relation, List *dpcontext, bool composite,
+				  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);
+
+	/* Composite types use a slightly simpler format string */
+	if (composite)
+		column = new_objtree_VA("%{name}I %{coltype}T %{collation}s",
+								3,
+								"type", ObjTypeString, "column",
+								"name", ObjTypeString, coldef->colname,
+								"coltype", ObjTypeObject,
+								new_objtree_for_type(typid, typmod));
+	else
+		column = new_objtree_VA("%{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(typid, typmod));
+
+	tmp = new_objtree_VA("COLLATE %{name}D", 0);
+	if (OidIsValid(typcollation))
+	{
+		ObjTree *collname;
+
+		collname = new_objtree_for_qualname_id(CollationRelationId,
+											   typcollation);
+		append_object_object(tmp, "name", collname);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(column, "collation", tmp);
+
+	if (!composite)
+	{
+		/*
+		 * 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.  Therefore,
+		 * we scan the list of constraints attached to this column to determine
+		 * whether we need to emit anything.
+		 * (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("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(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("%{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(Relation relation, List *tableElements, List *dpcontext,
+					 bool typed, bool composite)
+{
+	List	   *elements = NIL;
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					ObjTree	   *tree;
+
+					tree = typed ?
+						deparse_ColumnDef_typed(relation, dpcontext,
+												(ColumnDef *) elt) :
+						deparse_ColumnDef(relation, dpcontext,
+										  composite, (ColumnDef *) elt);
+					if (tree != NULL)
+					{
+						ObjElem    *column;
+
+						column = new_object_object(tree);
+						elements = lappend(elements, column);
+					}
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+
+	return elements;
+}
+
+/*
+ * deparse_CompositeTypeStmt
+ *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
+{
+	CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree;
+	ObjTree	   *composite;
+	Relation	typerel = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	List	   *tableelts = NIL;
+
+	dpcontext = deparse_context_for(RelationGetRelationName(typerel),
+									objectId);
+
+	composite = new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)",
+							   0);
+	append_object_object(composite, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+
+	tableelts = deparseTableElements(typerel, node->coldeflist, dpcontext,
+									 false,		/* not typed */
+									 true);		/* composite type */
+
+	append_array_object(composite, "columns", tableelts);
+
+	heap_close(typerel, AccessShareLock);
+
+	return composite;
+}
+
+/*
  * Handle deparsing of simple commands.
  *
  * This function contains a large switch that mirrors that in
@@ -722,7 +984,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CompositeTypeStmt(objectId, parsetree);
 			break;
 
 		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c1d860c..29a7cd3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9352,3 +9352,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/utils/ruleutils.h b/src/include/utils/ruleutils.h
index fed9c7b..1673e3e 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -32,4 +32,7 @@ extern List *select_rtable_names_for_explain(List *rtable,
 								Bitmapset *rels_used);
 extern char *generate_collation_name(Oid collid);
 
+extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
+						 List *dpcontext);
+
 #endif	/* RULEUTILS_H */
-- 
2.1.4

0010-deparse-Support-CREATE-TYPE-AS-ENUM.patchtext/x-diff; charset=us-asciiDownload
>From 02692e1a62eb922b462034663441b7825885101b Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:55:43 -0300
Subject: [PATCH 10/44] deparse: Support CREATE TYPE AS ENUM

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index b21845b..50a8d10 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -886,6 +886,38 @@ deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
 }
 
 /*
+ * deparse_CreateEnumStmt
+ *		Deparse a CreateEnumStmt (CREATE TYPE AS ENUM)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
+{
+	CreateEnumStmt *node = (CreateEnumStmt *) parsetree;
+	ObjTree	   *enumtype;
+	List	   *values;
+	ListCell   *cell;
+
+	enumtype = new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)",
+							  0);
+	append_object_object(enumtype, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	values = NIL;
+	foreach(cell, node->vals)
+	{
+		Value   *val = (Value *) lfirst(cell);
+
+		values = lappend(values, new_string_object(strVal(val)));
+	}
+	append_array_object(enumtype, "values", values);
+
+	return enumtype;
+}
+
+/*
  * Handle deparsing of simple commands.
  *
  * This function contains a large switch that mirrors that in
@@ -988,7 +1020,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateEnumStmt(objectId, parsetree);
 			break;
 
 		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
-- 
2.1.4

0011-deparse-Support-CREATE-SCHEMA-TABLE-SEQUENCE-INDEX-T.patchtext/x-diff; charset=us-asciiDownload
>From 91c0b4044da9ce758b66491fc99d3b4357248df4 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:57:48 -0300
Subject: [PATCH 11/44] deparse: Support CREATE
 SCHEMA/TABLE/SEQUENCE/INDEX/TRIGGER

---
 src/backend/commands/sequence.c    |  34 ++
 src/backend/commands/tablecmds.c   |   3 +-
 src/backend/tcop/deparse_utility.c | 956 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  | 355 ++++++++++++--
 src/include/commands/sequence.h    |   1 +
 src/include/utils/ruleutils.h      |  11 +-
 6 files changed, 1300 insertions(+), 60 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index d1570be..bf1013f 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1477,6 +1477,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/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bb0874c..7591b61 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8053,7 +8053,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/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 50a8d10..e99aec4 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -624,6 +624,223 @@ new_objtree_for_qualname_id(Oid classId, Oid objectId)
 }
 
 /*
+ * 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_CreateTrigStmt
+ *		Deparse a CreateTrigStmt (CREATE TRIGGER)
+ *
+ * Given a trigger OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
+{
+	CreateTrigStmt *node = (CreateTrigStmt *) parsetree;
+	Relation	pg_trigger;
+	HeapTuple	trigTup;
+	Form_pg_trigger trigForm;
+	ObjTree	   *trigger;
+	ObjTree	   *tmp;
+	int			tgnargs;
+	List	   *list;
+	List	   *events;
+
+	pg_trigger = heap_open(TriggerRelationId, AccessShareLock);
+
+	trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+	trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+	/*
+	 * Some of the elements only make sense for CONSTRAINT TRIGGERs, but it
+	 * seems simpler to use a single fmt string for both kinds of triggers.
+	 */
+	trigger =
+		new_objtree_VA("CREATE %{constraint}s TRIGGER %{name}I %{time}s %{events: OR }s "
+					   "ON %{relation}D %{from_table}s %{constraint_attrs: }s "
+					   "FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE %{function}s",
+					   2,
+					   "name", ObjTypeString, node->trigname,
+					   "constraint", ObjTypeString,
+					   node->isconstraint ? "CONSTRAINT" : "");
+
+	if (node->timing == TRIGGER_TYPE_BEFORE)
+		append_string_object(trigger, "time", "BEFORE");
+	else if (node->timing == TRIGGER_TYPE_AFTER)
+		append_string_object(trigger, "time", "AFTER");
+	else if (node->timing == TRIGGER_TYPE_INSTEAD)
+		append_string_object(trigger, "time", "INSTEAD OF");
+	else
+		elog(ERROR, "unrecognized trigger timing value %d", node->timing);
+
+	/*
+	 * Decode the events that the trigger fires for.  The output is a list;
+	 * in most cases it will just be a string with the even name, but when
+	 * there's an UPDATE with a list of columns, we return a JSON object.
+	 */
+	events = NIL;
+	if (node->events & TRIGGER_TYPE_INSERT)
+		events = lappend(events, new_string_object("INSERT"));
+	if (node->events & TRIGGER_TYPE_DELETE)
+		events = lappend(events, new_string_object("DELETE"));
+	if (node->events & TRIGGER_TYPE_TRUNCATE)
+		events = lappend(events, new_string_object("TRUNCATE"));
+	if (node->events & TRIGGER_TYPE_UPDATE)
+	{
+		if (node->columns == NIL)
+		{
+			events = lappend(events, new_string_object("UPDATE"));
+		}
+		else
+		{
+			ObjTree	   *update;
+			ListCell   *cell;
+			List	   *cols = NIL;
+
+			/*
+			 * Currently only UPDATE OF can be objects in the output JSON, but
+			 * we add a "kind" element so that user code can distinguish
+			 * possible future new event types.
+			 */
+			update = new_objtree_VA("UPDATE OF %{columns:, }I",
+									1, "kind", ObjTypeString, "update_of");
+
+			foreach(cell, node->columns)
+			{
+				char   *colname = strVal(lfirst(cell));
+
+				cols = lappend(cols,
+							   new_string_object(colname));
+			}
+
+			append_array_object(update, "columns", cols);
+
+			events = lappend(events,
+							 new_object_object(update));
+		}
+	}
+	append_array_object(trigger, "events", events);
+
+	tmp = new_objtree_for_qualname_id(RelationRelationId,
+									  trigForm->tgrelid);
+	append_object_object(trigger, "relation", tmp);
+
+	tmp = new_objtree_VA("FROM %{relation}D", 0);
+	if (trigForm->tgconstrrelid)
+	{
+		ObjTree	   *rel;
+
+		rel = new_objtree_for_qualname_id(RelationRelationId,
+										  trigForm->tgconstrrelid);
+		append_object_object(tmp, "relation", rel);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "from_table", tmp);
+
+	list = NIL;
+	if (node->deferrable)
+		list = lappend(list,
+					   new_string_object("DEFERRABLE"));
+	if (node->initdeferred)
+		list = lappend(list,
+					   new_string_object("INITIALLY DEFERRED"));
+	append_array_object(trigger, "constraint_attrs", list);
+
+	append_string_object(trigger, "for_each",
+						 node->row ? "ROW" : "STATEMENT");
+
+	tmp = new_objtree_VA("WHEN (%{clause}s)", 0);
+	if (node->whenClause)
+	{
+		Node	   *whenClause;
+		Datum		value;
+		bool		isnull;
+
+		value = fastgetattr(trigTup, Anum_pg_trigger_tgqual,
+							RelationGetDescr(pg_trigger), &isnull);
+		if (isnull)
+			elog(ERROR, "bogus NULL tgqual");
+
+		whenClause = stringToNode(TextDatumGetCString(value));
+		append_string_object(tmp, "clause",
+							 pg_get_trigger_whenclause(trigForm,
+													   whenClause,
+													   false));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "when", tmp);
+
+	tmp = new_objtree_VA("%{funcname}D(%{args:, }L)",
+						 1, "funcname", ObjTypeObject,
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 trigForm->tgfoid));
+	list = NIL;
+	tgnargs = trigForm->tgnargs;
+	if (tgnargs > 0)
+	{
+		bytea  *tgargs;
+		char   *argstr;
+		bool	isnull;
+		int		findx;
+		int		lentgargs;
+		char   *p;
+
+		tgargs = DatumGetByteaP(fastgetattr(trigTup,
+											Anum_pg_trigger_tgargs,
+											RelationGetDescr(pg_trigger),
+											&isnull));
+		if (isnull)
+			elog(ERROR, "invalid NULL tgargs");
+		argstr = (char *) VARDATA(tgargs);
+		lentgargs = VARSIZE_ANY_EXHDR(tgargs);
+
+		p = argstr;
+		for (findx = 0; findx < tgnargs; findx++)
+		{
+			size_t	tlen;
+
+			/* verify that the argument encoding is correct */
+			tlen = strlen(p);
+			if (p + tlen >= argstr + lentgargs)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid argument string (%s) for trigger \"%s\"",
+								argstr, NameStr(trigForm->tgname))));
+
+			list = lappend(list, new_string_object(p));
+
+			p += tlen + 1;
+		}
+	}
+
+	append_array_object(tmp, "args", list);		/* might be NIL */
+
+	append_object_object(trigger, "function", tmp);
+
+	heap_close(pg_trigger, AccessShareLock);
+
+	return trigger;
+}
+
+/*
  * deparse_ColumnDef
  *		Subroutine for CREATE TABLE deparsing
  *
@@ -850,6 +1067,334 @@ deparseTableElements(Relation relation, List *tableElements, List *dpcontext,
 }
 
 /*
+ * obtainConstraints
+ *		Subroutine for CREATE TABLE/CREATE DOMAIN deparsing
+ *
+ * Given a table OID or domain OID, obtain its constraints and append them to
+ * the given elements list.  The updated list is returned.
+ *
+ * This works for typed tables, regular tables, and domains.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static List *
+obtainConstraints(List *elements, Oid relationId, Oid domainId)
+{
+	Relation	conRel;
+	ScanKeyData key;
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	ObjTree    *tmp;
+
+	/* only one may be valid */
+	Assert(OidIsValid(relationId) ^ OidIsValid(domainId));
+
+	/*
+	 * scan pg_constraint to fetch all constraints linked to the given
+	 * relation.
+	 */
+	conRel = heap_open(ConstraintRelationId, AccessShareLock);
+	if (OidIsValid(relationId))
+	{
+		ScanKeyInit(&key,
+					Anum_pg_constraint_conrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relationId));
+		scan = systable_beginscan(conRel, ConstraintRelidIndexId,
+								  true, NULL, 1, &key);
+	}
+	else
+	{
+		Assert(OidIsValid(domainId));
+		ScanKeyInit(&key,
+					Anum_pg_constraint_contypid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(domainId));
+		scan = systable_beginscan(conRel, ConstraintTypidIndexId,
+								  true, NULL, 1, &key);
+	}
+
+	/*
+	 * 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:
+				continue;	/* not here */
+			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("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, new_object_object(tmp));
+	}
+
+	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 an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *list;
+	ListCell   *cell;
+	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 %{if_not_exists}s %{identity}D "
+			"OF %{of_type}T %{table_elements}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) %{inherits}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+
+	createStmt =
+		new_objtree_VA(fmtstr, 1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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(relation->rd_rel->reloftype, -1);
+		append_object_object(createStmt, "of_type", tmp);
+
+		tableelts = deparseTableElements(relation, node->tableElts, dpcontext,
+										 true,		/* typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+		if (tableelts == NIL)
+			tmp = new_objtree_VA("", 1,
+								 "present", ObjTypeBool, false);
+		else
+			tmp = new_objtree_VA("(%{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(relation, node->tableElts, dpcontext,
+										 false,		/* not typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+		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("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(RelationRelationId,
+													 formInh->inhparent);
+				parents = lappend(parents, new_object_object(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("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("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);
+
+	/*
+	 * WITH clause.  We always emit one, containing at least the OIDS option.
+	 * That way we don't depend on the default value for default_with_oids.
+	 * We can skip emitting other options if there don't appear in the parse
+	 * node.
+	 */
+	tmp = new_objtree_VA("oids=%{value}s", 2,
+						 "option", ObjTypeString, "oids",
+						 "value", ObjTypeString,
+						 relation->rd_rel->relhasoids ? "ON" : "OFF");
+	list = list_make1(new_object_object(tmp));
+	foreach(cell, node->options)
+	{
+		DefElem	*opt = (DefElem *) lfirst(cell);
+		char   *defname;
+		char   *value;
+
+		/* already handled above */
+		if (strcmp(opt->defname, "oids") == 0)
+			continue;
+
+		if (opt->defnamespace)
+			defname = psprintf("%s.%s", opt->defnamespace, opt->defname);
+		else
+			defname = opt->defname;
+
+		value = opt->arg ? defGetString(opt) :
+			defGetBoolean(opt) ? "TRUE" : "FALSE";
+		tmp = new_objtree_VA("%{option}s=%{value}s", 2,
+							 "option", ObjTypeString, defname,
+							 "value", ObjTypeString, value);
+		list = lappend(list, new_object_object(tmp));
+	}
+	append_array_object(createStmt, "with", list);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
+/*
  * deparse_CompositeTypeStmt
  *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
  *
@@ -917,6 +1462,405 @@ deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
 	return enumtype;
 }
 
+static inline ObjElem *
+deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->cache_value);
+	tmp = new_objtree_VA("CACHE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "cache",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Cycle(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA("%{no}s CYCLE",
+						 2,
+						 "clause", ObjTypeString, "cycle",
+						 "no", ObjTypeString,
+						 seqdata->is_cycled ? "" : "NO");
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_IncrementBy(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->increment_by);
+	tmp = new_objtree_VA("INCREMENT BY %{value}s",
+						 2,
+						 "clause", ObjTypeString, "increment_by",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Minvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->min_value);
+	tmp = new_objtree_VA("MINVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "minvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Maxvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->max_value);
+	tmp = new_objtree_VA("MAXVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "maxvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Startwith(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->start_value);
+	tmp = new_objtree_VA("START WITH %{value}s",
+						 2,
+						 "clause", ObjTypeString, "start",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Restart(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->last_value);
+	tmp = new_objtree_VA("RESTART %{value}s",
+						 2,
+						 "clause", ObjTypeString, "restart",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static ObjElem *
+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(RelationRelationId, ownerId);
+		append_string_object(tmp, "attrname", colname);
+		ownedby = new_objtree_VA("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("OWNED BY %{owner}D",
+								 3,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeNull,
+								 "present", ObjTypeBool, false);
+	return new_object_object(ownedby);
+}
+
+/*
+ * deparse_CreateSeqStmt
+ *		deparse a CreateSeqStmt
+ *
+ * Given a sequence OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *createSeq;
+	ObjTree    *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+
+	seqdata = get_sequence_values(objectId);
+
+	createSeq =
+		new_objtree_VA("CREATE %{persistence}s SEQUENCE %{identity}D "
+					   "%{definition: }s",
+					   1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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);
+
+	relation_close(relation, AccessShareLock);
+
+	return createSeq;
+}
+
+/*
+ * deparse_AlterSeqStmt
+ *		deparse an AlterSeqStmt
+ *
+ * Given a sequence OID and a parsetree that modified it, return an ObjTree
+ * representing the alter command.
+ */
+static ObjTree *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *alterSeq;
+	ObjTree	   *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+	ListCell   *cell;
+
+	seqdata = get_sequence_values(objectId);
+
+	alterSeq =
+		new_objtree_VA("ALTER SEQUENCE %{identity}D %{definition: }s", 0);
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(alterSeq, "identity", tmp);
+
+	foreach(cell, ((AlterSeqStmt *) parsetree)->options)
+	{
+		DefElem *elem = (DefElem *) lfirst(cell);
+		ObjElem *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") == 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);
+
+	relation_close(relation, AccessShareLock);
+
+	return alterSeq;
+}
+
+/*
+ * deparse_IndexStmt
+ *		deparse an IndexStmt
+ *
+ * Given an index OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ */
+static ObjTree *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+	IndexStmt  *node = (IndexStmt *) parsetree;
+	ObjTree    *indexStmt;
+	ObjTree    *tmp;
+	Relation	idxrel;
+	Relation	heaprel;
+	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);
+
+	pg_get_indexdef_detailed(objectId,
+							 &index_am, &definition, &reloptions,
+							 &tablespace, &whereClause);
+
+	indexStmt =
+		new_objtree_VA("CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   6,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "if_not_exists", ObjTypeString, node->if_not_exists ? "IF NOT EXISTS" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(heaprel->rd_rel->relnamespace,
+								   RelationGetRelationName(heaprel));
+	append_object_object(indexStmt, "table", tmp);
+
+	/* reloptions */
+	tmp = new_objtree_VA("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("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("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);
+
+	heap_close(idxrel, AccessShareLock);
+	heap_close(heaprel, AccessShareLock);
+
+	return indexStmt;
+}
+
+/*
+ * deparse_CreateSchemaStmt
+ *		deparse a CreateSchemaStmt
+ *
+ * Given a schema OID and the parsetree that created it, return an ObjTree
+ * 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 ObjTree *
+deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
+{
+	CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
+	ObjTree    *createSchema;
+	ObjTree    *auth;
+
+	createSchema =
+		new_objtree_VA("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("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);
+
+	return createSchema;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -939,11 +1883,11 @@ deparse_simple_command(StashedCommand *cmd)
 	switch (nodeTag(parsetree))
 	{
 		case T_CreateSchemaStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateSchemaStmt(objectId, parsetree);
 			break;
 
 		case T_CreateStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateStmt(objectId, parsetree);
 			break;
 
 		case T_CreateForeignTableStmt:
@@ -966,7 +1910,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_IndexStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_IndexStmt(objectId, parsetree);
 			break;
 
 		case T_CreateExtensionStmt:
@@ -1048,11 +1992,11 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateSeqStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateSeqStmt(objectId, parsetree);
 			break;
 
 		case T_AlterSeqStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterSeqStmt(objectId, parsetree);
 			break;
 
 		case T_CreateTableAsStmt:
@@ -1065,7 +2009,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateTrigStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateTrigStmt(objectId, parsetree);
 			break;
 
 		case T_CreatePLangStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 29a7cd3..10b13db 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -821,59 +821,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char	   *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.ctes = NIL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
@@ -914,6 +867,63 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char		relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+	initStringInfo(&buf);
+
+	relkind = get_rel_relkind(trigrec->tgrelid);
+
+	/* Build minimal OLD and NEW RTEs for the rel */
+	oldrte = makeNode(RangeTblEntry);
+	oldrte->rtekind = RTE_RELATION;
+	oldrte->relid = trigrec->tgrelid;
+	oldrte->relkind = relkind;
+	oldrte->alias = makeAlias("old", NIL);
+	oldrte->eref = oldrte->alias;
+	oldrte->lateral = false;
+	oldrte->inh = false;
+	oldrte->inFromCl = true;
+
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_RELATION;
+	newrte->relid = trigrec->tgrelid;
+	newrte->relkind = relkind;
+	newrte->alias = makeAlias("new", NIL);
+	newrte->eref = newrte->alias;
+	newrte->lateral = false;
+	newrte->inh = false;
+	newrte->inFromCl = true;
+
+	/* Build two-element rtable */
+	memset(&dpns, 0, sizeof(dpns));
+	dpns.rtable = list_make2(oldrte, newrte);
+	dpns.ctes = NIL;
+	set_rtable_names(&dpns, NIL, NULL);
+	set_simple_column_names(&dpns);
+
+	/* Set up context with one-deep namespace stack */
+	context.buf = &buf;
+	context.namespaces = list_make1(&dpns);
+	context.windowClause = NIL;
+	context.windowTList = NIL;
+	context.varprefix = true;
+	context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+	context.wrapColumn = WRAP_COLUMN_DEFAULT;
+	context.indentLevel = PRETTYINDENT_STD;
+
+	get_rule_expr(whenClause, &context, false);
+
+	return buf.data;
+}
+
 /* ----------
  * get_indexdef			- Get the definition of an index
  *
@@ -977,6 +987,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,
@@ -1256,6 +1268,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
@@ -1290,9 +1541,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);
 }
 
 /*
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e3b5142..1b3b243 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -72,6 +72,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 ObjectAddress DefineSequence(CreateSeqStmt *stmt);
 extern Oid	AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 1673e3e..4447af9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,6 +13,7 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
@@ -20,8 +21,16 @@
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+						  Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 
-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);
-- 
2.1.4

0012-deparse-Support-CREATE-TYPE-AS-RANGE.patchtext/x-diff; charset=us-asciiDownload
>From ce622b1733821e54b656d8096da2ed9dbe1b85ad 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 12/44] deparse: Support CREATE TYPE AS RANGE

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index e99aec4..e0c01de 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"
@@ -1462,6 +1464,104 @@ deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
 	return enumtype;
 }
 
+static ObjTree *
+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;
+
+	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(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(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(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(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(tmp));
+	}
+
+	append_array_object(range, "definition", definition);
+
+	systable_endscan(scan);
+	heap_close(pg_range, RowExclusiveLock);
+
+	return range;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -1968,7 +2068,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateRangeStmt(objectId, parsetree);
 			break;
 
 		case T_AlterEnumStmt:
-- 
2.1.4

0013-deparse-Support-CREATE-EXTENSION.patchtext/x-diff; charset=us-asciiDownload
>From 17c7fdef51a6db23a57009a1da6c12de7c86ec49 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 13/44] deparse: Support CREATE EXTENSION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index e0c01de..6e76764 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"
@@ -645,6 +646,81 @@ 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 ObjTree *
+deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
+{
+	CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *extStmt;
+	ObjTree	   *tmp;
+	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(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(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(tmp));
+
+	append_array_object(extStmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return extStmt;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -2014,7 +2090,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateExtensionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionStmt:
-- 
2.1.4

0014-deparse-Support-CREATE-RULE.patchtext/x-diff; charset=us-asciiDownload
>From ec2f5be37e95363e55e5e7b527f1d8be26650436 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 14/44] deparse: Support CREATE RULE

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 6e76764..fe6ce44 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"
@@ -1998,6 +2000,95 @@ deparse_IndexStmt(Oid objectId, Node *parsetree)
 	return indexStmt;
 }
 
+static ObjTree *
+deparse_RuleStmt(Oid objectId, Node *parsetree)
+{
+	RuleStmt *node = (RuleStmt *) parsetree;
+	ObjTree	   *ruleStmt;
+	ObjTree	   *tmp;
+	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(action));
+	}
+	append_array_object(ruleStmt, "actions", list);
+
+	systable_endscan(scan);
+	heap_close(pg_rewrite, AccessShareLock);
+
+	return ruleStmt;
+}
+
+
+
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -2164,7 +2255,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RuleStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RuleStmt(objectId, parsetree);
 			break;
 
 		case T_CreateSeqStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 10b13db..ebf325b 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 4447af9..9989cdd 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -30,6 +30,8 @@ extern void pg_get_indexdef_detailed(Oid indexrelid,
 extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 						  Node *whenClause, bool pretty);
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+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);
-- 
2.1.4

0015-deparse-Support-ALTER-TYPE-ADD-VALUE-enums.patchtext/x-diff; charset=us-asciiDownload
>From ccbd6ca79efb26c387eca4c96fed7c2340117235 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 15/44] deparse: Support ALTER TYPE / ADD VALUE (enums)

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index fe6ce44..2fe3c81 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2128,6 +2128,39 @@ deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
 	return createSchema;
 }
 
+static ObjTree *
+deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
+{
+	AlterEnumStmt *node = (AlterEnumStmt *) parsetree;
+	ObjTree	   *alterEnum;
+	ObjTree	   *tmp;
+
+	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);
+
+	return alterEnum;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -2239,7 +2272,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterEnumStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterEnumStmt(objectId, parsetree);
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-- 
2.1.4

0016-deparse-Support-for-ALTER-OBJECT-RENAME.patchtext/x-diff; charset=us-asciiDownload
>From 20960ee030b0bd59c72ebb42bedb359b4cd7e31d 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 16/44] deparse: Support for ALTER <OBJECT> RENAME

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 2fe3c81..06ca71c 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -40,6 +40,8 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -126,6 +128,7 @@ static void append_integer_object(ObjTree *tree, char *name, int64 value);
 static void append_float_object(ObjTree *tree, char *name, float8 value);
 static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
 static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+static const char *stringify_objtype(ObjectType objtype);
 
 /*
  * Allocate a new object tree to store parameter values.
@@ -1640,6 +1643,414 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return range;
 }
 
+/*
+ * Return the given object type as a string.
+ */
+static const char *
+stringify_objtype(ObjectType objtype)
+{
+	switch (objtype)
+	{
+		case OBJECT_AGGREGATE:
+			return "AGGREGATE";
+		case OBJECT_CAST:
+			return "CAST";
+		case OBJECT_COLUMN:
+			return "COLUMN";
+		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 "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_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_USER_MAPPING:
+			return "USER MAPPING";
+		case OBJECT_VIEW:
+			return "VIEW";
+
+		default:
+			elog(ERROR, "unsupported objtype %d", objtype);
+	}
+}
+
+static ObjTree *
+deparse_RenameStmt(Oid objectId, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree	   *renameStmt;
+	char	   *fmtstr;
+	Relation	relation;
+	Oid			schemaId;
+
+	/*
+	 * 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:
+			relation = relation_open(objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			if (node->relationType == OBJECT_TYPE)
+			{
+
+				fmtstr = psprintf("ALTER TYPE %%{identity}D RENAME ATTRIBUTE %%{colname}I TO %%{newname}I");
+				renameStmt = new_objtree_VA(fmtstr, 0);
+			}
+			else
+			{
+				fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME COLUMN %%{colname}I TO %%{newname}I",
+								  stringify_objtype(node->relationType));
+			}
+
+			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);
+
+			/* composite types do not support IF EXISTS */
+			if (node->relationType != OBJECT_TYPE)
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+
+			break;
+
+		case OBJECT_SCHEMA:
+			{
+				renameStmt =
+					new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I",
+								   0);
+				append_string_object(renameStmt, "identity", node->subname);
+			}
+			break;
+
+		case OBJECT_FDW:
+		case OBJECT_LANGUAGE:
+		case OBJECT_FOREIGN_SERVER:
+			{
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_string_object(renameStmt, "identity",
+									 strVal(linitial(node->object)));
+			}
+			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:
+			{
+				char	   *identity;
+				HeapTuple	objTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				Oid			classId = get_objtype_catalog_oid(node->renameType);
+				AttrNumber	Anum_namespace = get_object_attnum_namespace(classId);
+
+				catalog = relation_open(classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, objectId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				identity = psprintf("%s.%s", get_namespace_name(DatumGetObjectId(objnsp)),
+									strVal(llast(node->object)));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_string_object(renameStmt, "identity", identity);
+
+				relation_close(catalog, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_OPCLASS:
+		case OBJECT_OPFAMILY:
+			{
+				char	   *identity;
+				HeapTuple	objTup;
+				HeapTuple	amTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				Oid			classId = get_objtype_catalog_oid(node->renameType);
+				AttrNumber	Anum_namespace = get_object_attnum_namespace(classId);
+				Oid			amoid;
+
+				catalog = relation_open(classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, objectId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				if (node->renameType == OBJECT_OPCLASS)
+					amoid = ((Form_pg_opclass) GETSTRUCT(objTup))->opcmethod;
+				else
+					amoid = ((Form_pg_opfamily) GETSTRUCT(objTup))->opfmethod;
+				amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+				if (!HeapTupleIsValid(amTup))
+					elog(ERROR, "cache lookup failed for access method %u", amoid);
+
+				identity = psprintf("%s.%s", get_namespace_name(DatumGetObjectId(objnsp)),
+									strVal(llast(node->object)));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s USING %%{amname}I RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_string_object(renameStmt, "identity", identity);
+				append_string_object(renameStmt, "amname",
+									 pstrdup(NameStr(((Form_pg_am) GETSTRUCT(amTup))->amname)));
+
+				ReleaseSysCache(amTup);
+				relation_close(catalog, AccessShareLock);
+			}
+
+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			{
+				char	   *newident;
+				ObjectAddress objaddr;
+				const char	   *quoted_newname;
+				StringInfoData old_ident;
+				char	   *start;
+
+				/*
+				 * Generating a function/aggregate identity is altogether too
+				 * messy, so instead of doing it ourselves, we generate one for
+				 * the renamed object, then strip out the name and replace it
+				 * with the original name from the parse node.  This is so ugly
+				 * that we don't dare do it for any other object kind.
+				 */
+
+				objaddr.classId = get_objtype_catalog_oid(node->renameType);
+				objaddr.objectId = objectId;
+				objaddr.objectSubId = 0;
+				newident = getObjectIdentity(&objaddr);
+
+				quoted_newname = quote_identifier(node->newname);
+				start = strstr(newident, quoted_newname);
+				if (!start)
+					elog(ERROR, "could not find %s in %s", start, newident);
+				initStringInfo(&old_ident);
+				appendBinaryStringInfo(&old_ident, newident, start - newident);
+				appendStringInfoString(&old_ident,
+									   quote_identifier(strVal(llast(node->object))));
+				appendStringInfoString(&old_ident, start + strlen(quoted_newname));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 1,
+											"identity", ObjTypeString, old_ident.data);
+			}
+			break;
+
+		case OBJECT_TABCONSTRAINT:
+		case OBJECT_DOMCONSTRAINT:
+			{
+				HeapTuple		conTup;
+				Form_pg_constraint	constrForm;
+				ObjTree		   *ident;
+
+				conTup = SearchSysCache1(CONSTROID, objectId);
+				constrForm = (Form_pg_constraint) GETSTRUCT(conTup);
+
+				if (node->renameType == OBJECT_TABCONSTRAINT)
+				{
+					fmtstr = "ALTER TABLE %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(RelationRelationId,
+														constrForm->conrelid);
+				}
+				else
+				{
+					fmtstr = "ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(TypeRelationId,
+														constrForm->contypid);
+				}
+				renameStmt = new_objtree_VA(fmtstr, 2,
+											"conname", ObjTypeString, node->subname,
+											"identity", ObjTypeObject, ident);
+				ReleaseSysCache(conTup);
+			}
+			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;
+
+		case OBJECT_POLICY:
+			{
+				HeapTuple	polTup;
+				Form_pg_policy polForm;
+				Relation	pg_policy;
+				ScanKeyData	key;
+				SysScanDesc	scan;
+
+				pg_policy = relation_open(PolicyRelationId, AccessShareLock);
+				ScanKeyInit(&key, ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(objectId));
+				scan = systable_beginscan(pg_policy, PolicyOidIndexId, true,
+										  NULL, 1, &key);
+				polTup = systable_getnext(scan);
+				if (!HeapTupleIsValid(polTup))
+					elog(ERROR, "cache lookup failed for policy %u", objectId);
+				polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+				renameStmt = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I on %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "policyname", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 polForm->polrelid));
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+				systable_endscan(scan);
+				relation_close(pg_policy, AccessShareLock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	append_string_object(renameStmt, "newname", node->newname);
+
+	return renameStmt;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -2354,7 +2765,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RenameStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RenameStmt(objectId, parsetree);
 			break;
 
 		case T_AlterObjectSchemaStmt:
-- 
2.1.4

0017-deparse-Support-CREATE-DOMAIN.patchtext/x-diff; charset=us-asciiDownload
>From 9cc5ed89d59464516df41f59bf5d676181d58401 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 17/44] deparse: Support CREATE DOMAIN

---
 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 06ca71c..34e214b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1643,6 +1643,59 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return range;
 }
 
+static ObjTree *
+deparse_CreateDomain(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *createDomain;
+	ObjTree	   *tmp;
+	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}T %{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_type(typForm->typbasetype, typForm->typtypmod));
+
+	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);
+
+	return createDomain;
+}
+
 /*
  * Return the given object type as a string.
  */
@@ -2728,7 +2781,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateDomainStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateDomain(objectId, parsetree);
 			break;
 
 		case T_CreateConversionStmt:
-- 
2.1.4

0018-deparse-Support-CREATE-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From c944b15befc8ef37837880ed69a2c2c9aeebd34d 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 18/44] deparse: Support CREATE FUNCTION

---
 src/backend/tcop/deparse_utility.c | 356 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  25 +++
 src/include/utils/ruleutils.h      |   1 +
 3 files changed, 381 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 34e214b..9a12b4a 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_opfamily.h"
@@ -1696,6 +1697,359 @@ deparse_CreateDomain(Oid objectId, Node *parsetree)
 	return createDomain;
 }
 
+static ObjTree *
+deparse_FunctionSet(VariableSetKind kind, char *name, char *value)
+{
+	ObjTree	   *r;
+
+	if (kind == VAR_RESET_ALL)
+	{
+		r = new_objtree_VA("RESET ALL", 0);
+	}
+	else if (value != NULL)
+	{
+		/*
+		 * Some GUC variable names are 'LIST' type and hence must not be
+		 * quoted. FIXME: shouldn't this and pg_get_functiondef() rather use
+		 * guc.c to check for GUC_LIST?
+		 */
+		if (pg_strcasecmp(name, "DateStyle") == 0
+			|| pg_strcasecmp(name, "search_path") == 0)
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}s", 0);
+		else
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}L", 0);
+
+		append_string_object(r, "set_name", name);
+		append_string_object(r, "set_value", value);
+	}
+	else
+	{
+		r = new_objtree_VA("RESET %{set_name}I", 0);
+		append_string_object(r, "set_name", name);
+	}
+
+	return r;
+}
+
+/*
+ * 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.
+ */
+static ObjTree *
+deparse_CreateFunction(Oid objectId, Node *parsetree)
+{
+	CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree;
+	ObjTree	   *createFunc;
+	ObjTree	   *sign;
+	ObjTree	   *tmp;
+	Datum		tmpdatum;
+	char	   *fmt;
+	char	   *definition;
+	char	   *source;
+	char	   *probin;
+	List	   *params;
+	List	   *defaults;
+	List	   *sets = NIL;
+	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)
+			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(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(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}n", 1,
+										"cost", ObjTypeFloat,
+										procForm->procost));
+
+	tmp = new_objtree_VA("ROWS %{rows}n", 0);
+	if (procForm->prorows == 0)
+		append_bool_object(tmp, "present", false);
+	else
+		append_float_object(tmp, "rows", procForm->prorows);
+	append_object_object(createFunc, "rows", tmp);
+
+	foreach(cell, node->options)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+			sets = lappend(sets, new_object_object(tmp));
+		}
+	}
+	append_array_object(createFunc, "set_options", sets);
+
+	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);
+
+	return createFunc;
+}
+
 /*
  * Return the given object type as a string.
  */
@@ -2744,7 +3098,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateFunctionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateFunction(objectId, parsetree);
 			break;
 
 		case T_AlterFunctionStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ebf325b..5a695c1 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9692,3 +9692,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 9989cdd..c3757de 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -42,6 +42,7 @@ extern List *set_deparse_context_planstate(List *dpcontext,
 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);
-- 
2.1.4

0019-deparse-Support-ALTER-TABLE.patchtext/x-diff; charset=us-asciiDownload
>From 1800f7a2cc78f9628dc95f57b4b1e2a4873db5b7 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 19/44] deparse: Support ALTER TABLE

---
 src/backend/commands/event_trigger.c | 143 +++++++++-
 src/backend/commands/tablecmds.c     |   8 +
 src/backend/tcop/deparse_utility.c   | 491 +++++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           |  23 +-
 src/include/commands/event_trigger.h |   5 +
 src/include/tcop/deparse_utility.h   |   8 +
 6 files changed, 676 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index aba0eb4..1024443 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -57,6 +57,7 @@ typedef struct EventTriggerQueryState
 	int			table_rewrite_reason;	/* AT_REWRITE reason */
 
 	MemoryContext cxt;
+	StashedCommand *curcmd;
 	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
@@ -1210,6 +1211,7 @@ EventTriggerBeginCompleteQuery(void)
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
+	state->curcmd = NULL;
 	state->stash = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
@@ -1573,6 +1575,138 @@ EventTriggerStashCommand(ObjectAddress address, Oid secondaryOid,
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * 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.
+ *
+ * XXX -- this API isn't considering the possibility of an ALTER TABLE command
+ * being called reentrantly by an event trigger function.  Do we need stackable
+ * commands at this level?  Perhaps at least we should detect the condition and
+ * raise an error.
+ */
+void
+EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_AlterTable;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.alterTable.objectId = InvalidOid;
+	stashed->d.alterTable.objtype = objtype;
+	stashed->d.alterTable.subcmds = NIL;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->curcmd = stashed;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remember the OID of the object being affected by a complex command.
+ *
+ * This is needed because in some cases we don't know the OID until later.
+ */
+void
+EventTriggerComplexCmdSetOid(Oid objectId)
+{
+	/*
+	 * When processing an ALTER TYPE command, the given objectId is the one
+	 * found on pg_class for the composite type; we translate it to the
+	 * corresponding pg_type OID so that it can be properly be considered the
+	 * object's canonical objectId.
+	 */
+	if (currentEventTriggerState->curcmd->d.alterTable.objtype == OBJECT_TYPE)
+	{
+		HeapTuple	relTup;
+		Form_pg_class relForm;
+
+		relTup = SearchSysCache1(RELOID, objectId);
+		if (!HeapTupleIsValid(relTup))
+			elog(ERROR, "cache lookup failed for relation %u", objectId);
+		relForm = (Form_pg_class) GETSTRUCT(relTup);
+		Assert(relForm->relkind == RELKIND_COMPOSITE_TYPE);
+		objectId = relForm->reltype;
+		ReleaseSysCache(relTup);
+	}
+
+	currentEventTriggerState->curcmd->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerRecordSubcmd
+ * 		Save data about a single part of a complex DDL command
+ *
+ * Several different commands go through this path, but apart from ALTER TABLE
+ * itself, they are all concerned with AlterTableCmd nodes that are generated
+ * internally, so that's all that this code needs to handle at the moment.
+ */
+void
+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)
 {
@@ -1652,7 +1786,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
 			MemSet(nulls, 0, sizeof(nulls));
 
-			if (cmd->type == SCT_Simple)
+			if (cmd->type == SCT_Simple ||
+				cmd->type == SCT_AlterTable)
 			{
 				Oid			classId;
 				Oid			objId;
@@ -1668,6 +1803,12 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 					objId = cmd->d.simple.address.objectId;
 					objSubId = cmd->d.simple.address.objectSubId;
 				}
+				else if (cmd->type == SCT_AlterTable)
+				{
+					classId = get_objtype_catalog_oid(cmd->d.alterTable.objtype);
+					objId = cmd->d.alterTable.objectId;
+					objSubId = 0;
+				}
 
 				tag = CreateCommandTag(cmd->parsetree);
 				addr.classId = classId;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7591b61..43f4018 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2772,6 +2772,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerComplexCmdSetOid(relid);
+
 	ATController(NULL, rel, cmds, recurse, lockmode);
 }
 
@@ -3648,6 +3650,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
@@ -9618,7 +9623,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		cmds = lappend(cmds, cmd);
 
+		EventTriggerComplexCmdStart((Node *) stmt, OBJECT_TABLE);
+		/* OID is set by AlterTableInternal */
 		AlterTableInternal(lfirst_oid(l), cmds, false);
+		EventTriggerComplexCmdEnd();
 	}
 
 	return new_tablespaceoid;
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 9a12b4a..01e3189 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2979,6 +2979,494 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 	return alterEnum;
 }
 
+static ObjTree *
+deparse_AlterTableStmt(StashedCommand *cmd)
+{
+	ObjTree	   *alterTableStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	List	   *dpcontext;
+	Relation	rel;
+	List	   *subcmds = NIL;
+	ListCell   *cell;
+	char	   *fmtstr;
+
+	Assert(cmd->type == SCT_AlterTable);
+
+	rel = relation_open(cmd->d.alterTable.objectId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									cmd->d.alterTable.objectId);
+
+	fmtstr = psprintf("ALTER %s %%{identity}D %%{subcmds:, }s",
+					  stringify_objtype(cmd->d.alterTable.objtype));
+	alterTableStmt = new_objtree_VA(fmtstr, 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(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(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(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(tmp));
+				break;
+
+			case AT_SetStatistics:
+				{
+					Assert(IsA(subcmd->def, Integer));
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n",
+										 3, "type", ObjTypeString, "set statistics",
+										 "column", ObjTypeString, subcmd->name,
+										 "statistics", ObjTypeInteger,
+										 intVal((Value *) subcmd->def));
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_SetOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE SET OPTIONS");
+				break;
+
+			case AT_ResetOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE RESET OPTIONS");
+				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(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(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(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(tmp));
+				}
+				break;
+
+			case AT_AlterConstraint:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER CONSTRAINT");
+				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(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(tmp));
+				break;
+
+			case AT_AlterColumnType:
+				{
+					TupleDesc tupdesc = RelationGetDescr(rel);
+					Form_pg_attribute att = tupdesc->attrs[substashed->attnum - 1];
+					ColumnDef	   *def;
+
+					def = (ColumnDef *) subcmd->def;
+					Assert(IsA(def, ColumnDef));
+
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DATA TYPE %{datatype}T %{collation}s %{using}s",
+										 2, "type", ObjTypeString, "alter column type",
+										 "column", ObjTypeString, subcmd->name);
+					/* add the TYPE clause */
+					append_object_object(tmp, "datatype",
+										 new_objtree_for_type(att->atttypid,
+															  att->atttypmod));
+
+					/* add a COLLATE clause, if needed */
+					tmp2 = new_objtree_VA("COLLATE %{name}D", 0);
+					if (OidIsValid(att->attcollation))
+					{
+						ObjTree *collname;
+
+						collname = new_objtree_for_qualname_id(CollationRelationId,
+															   att->attcollation);
+						append_object_object(tmp2, "name", collname);
+					}
+					else
+						append_bool_object(tmp2, "present", false);
+					append_object_object(tmp, "collation", tmp2);
+
+					/*
+					 * Error out if the USING clause was used.  We cannot use
+					 * it directly here, because it needs to run through
+					 * transformExpr() before being usable for ruleutils.c, and
+					 * we're not in a position to transform it ourselves.  To
+					 * fix this problem, tablecmds.c needs to be modified to store
+					 * the transformed expression somewhere in the StashedATSubcmd.
+					 */
+					tmp2 = new_objtree_VA("USING %{expression}s", 0);
+					if (def->raw_default)
+						elog(ERROR, "unimplemented deparse of ALTER TABLE TYPE USING");
+					else
+						append_bool_object(tmp2, "present", false);
+					append_object_object(tmp, "using", tmp2);
+
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS");
+				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(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(tmp));
+				break;
+
+			case AT_DropCluster:
+				tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+									 "type", ObjTypeString, "set without cluster");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetLogged:
+				tmp = new_objtree_VA("SET LOGGED", 1,
+									 "type", ObjTypeString, "set logged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetUnLogged:
+				tmp = new_objtree_VA("SET UNLOGGED", 1,
+									 "type", ObjTypeString, "set unlogged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOids:
+				tmp = new_objtree_VA("SET WITH OIDS", 1,
+									 "type", ObjTypeString, "set with oids");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOids:
+				tmp = new_objtree_VA("SET WITHOUT OIDS", 1,
+									 "type", ObjTypeString, "set without oids");
+				subcmds = lappend(subcmds, new_object_object(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(tmp));
+				break;
+
+			case AT_SetRelOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE SET");
+				break;
+
+			case AT_ResetRelOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE RESET");
+				break;
+
+			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(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(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(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(tmp));
+				break;
+
+			case AT_EnableTrigAll:
+				tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "enable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigAll:
+				tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "disable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableTrigUser:
+				tmp = new_objtree_VA("ENABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "enable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigUser:
+				tmp = new_objtree_VA("DISABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "disable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableRule:
+				tmp = new_objtree_VA("ENABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableAlwaysRule:
+				tmp = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable always rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableReplicaRule:
+				tmp = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable replica rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRule:
+				tmp = new_objtree_VA("DISABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "disable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddInherit:
+				tmp = new_objtree_VA("ADD INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "add inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 substashed->oid));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropInherit:
+				tmp = new_objtree_VA("NO INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "drop inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 substashed->oid));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOf:
+				tmp = new_objtree_VA("ADD OF %{type_of}T",
+									 2, "type", ObjTypeString, "add of",
+									 "type_of", ObjTypeObject,
+									 new_objtree_for_type(substashed->oid, -1));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOf:
+				tmp = new_objtree_VA("NOT OF",
+									 1, "type", ObjTypeString, "not of");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				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(tmp));
+				break;
+
+			case AT_EnableRowSecurity:
+				tmp = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "enable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRowSecurity:
+				tmp = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "disable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_GenericOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE OPTIONS (...)");
+				break;
+
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+	}
+
+	heap_close(rel, AccessShareLock);
+
+	if (list_length(subcmds) == 0)
+		return NULL;
+
+	append_array_object(alterTableStmt, "subcmds", subcmds);
+	return alterTableStmt;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -3271,6 +3759,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_Simple:
 			tree = deparse_simple_command(cmd);
 			break;
+		case SCT_AlterTable:
+			tree = deparse_AlterTableStmt(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 084adcb..9b6e1f4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1030,6 +1030,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)
 						{
@@ -1043,19 +1047,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,
@@ -1212,6 +1229,7 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
+					EventTriggerComplexCmdStart(parsetree, OBJECT_INDEX);	/* relkind? */
 					address =
 						DefineIndex(relid,	/* OID of heap relation */
 									stmt,
@@ -1228,6 +1246,7 @@ ProcessUtilitySlow(Node *parsetree,
 					EventTriggerStashCommand(address,
 											 InvalidOid, parsetree);
 					commandStashed = true;
+					EventTriggerComplexCmdEnd();
 				}
 				break;
 
@@ -1315,10 +1334,12 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
+				EventTriggerComplexCmdStart(parsetree, OBJECT_VIEW);	/* XXX relkind? */
 				address = DefineView((ViewStmt *) parsetree, queryString);
 				EventTriggerStashCommand(address, InvalidOid, parsetree);
 				/* stashed internally */
 				commandStashed = true;
+				EventTriggerComplexCmdEnd();
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index be37875..36d36ff 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -62,5 +62,10 @@ extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 
 extern void EventTriggerStashCommand(ObjectAddress address, Oid secondaryOid,
 						 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 99e130d..0986748 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -26,6 +26,7 @@
 typedef enum StashedCommandType
 {
 	SCT_Simple,
+	SCT_AlterTable
 } StashedCommandType;
 
 /*
@@ -51,6 +52,13 @@ typedef struct StashedCommand
 			ObjectAddress address;
 			Oid			secondaryOid;
 		} simple;
+
+		struct AlterTableCommand
+		{
+			Oid		objectId;
+			ObjectType objtype;
+			List   *subcmds;
+		} alterTable;
 	} d;
 } StashedCommand;
 
-- 
2.1.4

0020-deparse-Support-CREATE-VIEW.patchtext/x-diff; charset=us-asciiDownload
>From aae4140928ded535cd4cd1bf127e1c93f7cd0c7e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 14:34:00 -0300
Subject: [PATCH 20/44] deparse: Support CREATE VIEW

---
 src/backend/tcop/deparse_utility.c | 38 +++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  7 +++++++
 src/include/utils/ruleutils.h      |  1 +
 3 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 01e3189..29d4e5e 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -727,6 +727,42 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
 }
 
 /*
+ * deparse_ViewStmt
+ *		deparse a ViewStmt
+ *
+ * Given a view OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_ViewStmt(Oid objectId, Node *parsetree)
+{
+	ViewStmt   *node = (ViewStmt *) parsetree;
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	Relation	relation;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s",
+							  2,
+							  "or_replace", ObjTypeString,
+							  node->replace ? "OR REPLACE" : "",
+							  "persistence", ObjTypeString,
+					  get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(viewStmt, "identity", tmp);
+
+	append_string_object(viewStmt, "query",
+						 pg_get_viewdef_internal(objectId));
+
+	relation_close(relation, AccessShareLock);
+
+	return viewStmt;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -3582,7 +3618,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_ViewStmt(objectId, parsetree);
 			break;
 
 		case T_CreateFunctionStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5a695c1..3ec3e5f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -669,6 +669,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
  */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index c3757de..91b61f9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -32,6 +32,7 @@ extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
 					   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
 
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
-- 
2.1.4

0021-deparse-Support-CREATE-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From 0e9b1cbf77298aeced80ad97814348eea9287488 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 14:34:29 -0300
Subject: [PATCH 21/44] deparse: Support CREATE OPERATOR FAMILY

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 29d4e5e..c97d597 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3016,6 +3016,41 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateOpFamily(Oid objectId, Node *parsetree)
+{
+	HeapTuple   opfTup;
+	HeapTuple   amTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_am  amForm;
+	ObjTree	   *copfStmt;
+	ObjTree	   *tmp;
+
+	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));
+
+	ReleaseSysCache(amTup);
+	ReleaseSysCache(opfTup);
+
+	return copfStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -3675,7 +3710,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateOpFamilyStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateOpFamily(objectId, parsetree);
 			break;
 
 		case T_AlterOpFamilyStmt:
-- 
2.1.4

0022-deparse-Support-CREATE-CONVERSION.patchtext/x-diff; charset=us-asciiDownload
>From 869b8ab69e930296564503e6f8cc33b2eb4d8b45 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 22/44] deparse: Support CREATE CONVERSION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index c97d597..ff714a6 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"
@@ -53,6 +54,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"
@@ -3016,6 +3018,41 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+	HeapTuple   conTup;
+	Relation	convrel;
+	Form_pg_conversion conForm;
+	ObjTree	   *ccStmt;
+
+	convrel = heap_open(ConversionRelationId, AccessShareLock);
+	conTup = get_catalog_object_by_oid(convrel, 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));
+
+	heap_close(convrel, AccessShareLock);
+
+	return ccStmt;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -3698,7 +3735,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateConversionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateConversion(objectId, parsetree);
 			break;
 
 		case T_CreateCastStmt:
-- 
2.1.4

0023-deparse-Support-DefineStmt-commands.patchtext/x-diff; charset=utf-8Download
>From 47cf939557f37797df873394d1fa7327086287d4 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 23/44] deparse: Support DefineStmt commands

CREATE AGGREGATE
CREATE COLLATION
CREATE OPERATOR
CREATE TEXT SEARCH CONFIGURATION
CREATE TEXT SEARCH PARSER
CREATE TEXT SEARCH DICTIONARY
CREATE TEXT SEARCH TEMPLATE
CREATE TYPE
---
 src/backend/commands/typecmds.c    |   2 +-
 src/backend/tcop/deparse_utility.c | 855 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 855 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 68d73b5..a2576f9 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -229,7 +229,7 @@ DefineType(List *names, List *parameters)
 		 * creating the shell type was all we're supposed to do.
 		 */
 		if (parameters == NIL)
-			return InvalidOid;
+			return typoid;
 	}
 	else
 	{
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index ff714a6..848baad 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -48,6 +48,11 @@
 #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"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
@@ -653,6 +658,854 @@ 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(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(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(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(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(tmp));
+
+	if (agg->aggtransspace != 0)
+	{
+		tmp = new_objtree_VA("SSPACE=%{space}n", 1,
+							 "space", ObjTypeInteger,
+							 agg->aggtransspace);
+		list = lappend(list, new_object_object(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(tmp));
+	}
+
+	if (agg->aggfinalextra)
+	{
+		tmp = new_objtree_VA("FINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(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(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(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(tmp));
+	}
+
+	if (agg->aggmtransspace != 0)
+	{
+		tmp = new_objtree_VA("MSSPACE=%{space}n", 1,
+							 "space", ObjTypeInteger,
+							 agg->aggmtransspace);
+		list = lappend(list, new_object_object(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(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(tmp));
+	}
+
+	if (agg->aggmfinalextra)
+	{
+		tmp = new_objtree_VA("MFINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(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(tmp));
+	}
+
+	if (agg->aggkind == AGGKIND_HYPOTHETICAL)
+	{
+		tmp = new_objtree_VA("HYPOTHETICAL=true", 0);
+		list = lappend(list, new_object_object(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}D", 0);
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname(op->oprnamespace,
+													  NameStr(op->oprname)));
+		list = lappend(list, new_object_object(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;
+	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;
+	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(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(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(tmp));
+	}
+
+	if (OidIsValid(oprForm->oprcom))
+	{
+		tmp = new_objtree_VA("COMMUTATOR=%{oper}D", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprcom));
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	if (OidIsValid(oprForm->oprnegate))
+	{
+		tmp = new_objtree_VA("NEGATOR=%{oper}D", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprnegate));
+		list = lappend(list, new_object_object(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(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(tmp));
+	}
+
+	if (oprForm->oprcanmerge)
+		list = lappend(list, new_object_object(new_objtree_VA("MERGES", 0)));
+	if (oprForm->oprcanhash)
+		list = lappend(list, new_object_object(new_objtree_VA("HASHES", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(oprTup);
+
+	return stmt;
+}
+
+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;
+	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(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(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(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(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(tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+
+	return stmt;
+}
+
+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(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(tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tsdTup);
+
+	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(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(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tstTup);
+
+	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(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(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(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(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(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(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(tmp));
+	}
+
+	/* INTERNALLENGTH */
+	if (typForm->typlen == -1)
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=VARIABLE", 0);
+	}
+	else
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}n", 1,
+							 "typlen", ObjTypeInteger, typForm->typlen);
+	}
+	list = lappend(list, new_object_object(tmp));
+
+	/* PASSEDBYVALUE */
+	if (typForm->typbyval)
+		list = lappend(list, new_object_object(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(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(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(tmp));
+
+	/* PREFERRED */
+	if (typForm->typispreferred)
+		list = lappend(list, new_object_object(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(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(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(tmp));
+
+	/* COLLATABLE */
+	if (OidIsValid(typForm->typcollation))
+		list = lappend(list,
+					   new_object_object(new_objtree_VA("COLLATABLE=true", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(typTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt(Oid objectId, Node *parsetree)
+{
+	DefineStmt *define = (DefineStmt *) parsetree;
+	ObjTree	   *defStmt;
+
+	switch (define->kind)
+	{
+		case OBJECT_AGGREGATE:
+			defStmt = deparse_DefineStmt_Aggregate(objectId, define);
+			break;
+
+		case OBJECT_COLLATION:
+			defStmt = deparse_DefineStmt_Collation(objectId, define);
+			break;
+
+		case OBJECT_OPERATOR:
+			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;
+
+		case OBJECT_TSDICTIONARY:
+			defStmt = deparse_DefineStmt_TSDictionary(objectId, define);
+			break;
+
+		case OBJECT_TSTEMPLATE:
+			defStmt = deparse_DefineStmt_TSTemplate(objectId, define);
+			break;
+
+		case OBJECT_TYPE:
+			defStmt = deparse_DefineStmt_Type(objectId, define);
+			break;
+
+		default:
+			elog(ERROR, "unsupported object kind");
+			return NULL;
+	}
+
+	return defStmt;
+}
+
 /*
  * deparse_CreateExtensionStmt
  *		deparse a CreateExtensionStmt
@@ -3620,7 +4473,7 @@ deparse_simple_command(StashedCommand *cmd)
 
 			/* other local objects */
 		case T_DefineStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_DefineStmt(objectId, parsetree);
 			break;
 
 		case T_IndexStmt:
-- 
2.1.4

0024-deparse-support-ALTER-THING-OWNER-TO.patchtext/x-diff; charset=us-asciiDownload
>From b621c348645ba5702edfea853d944685427a0466 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 24/44] deparse: support ALTER THING OWNER TO

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 848baad..b3fe6ff 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3871,6 +3871,29 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+	ObjTree	   *ownerStmt;
+	ObjectAddress addr;
+	char	   *fmt;
+
+	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));
+
+	return ownerStmt;
+}
+
+static ObjTree *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
 	HeapTuple   conTup;
@@ -4629,7 +4652,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterOwnerStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterOwnerStmt(objectId, parsetree);
 			break;
 
 		case T_CommentStmt:
-- 
2.1.4

0025-deparse-Support-ALTER-EXTENSION-UPDATE-TO.patchtext/x-diff; charset=us-asciiDownload
>From ddb147329e9f94a8e870209ce4a06b28d1df51f7 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 25/44] deparse: Support ALTER EXTENSION / UPDATE TO

---
 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 b3fe6ff..58eb420 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1581,6 +1581,48 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
 	return extStmt;
 }
 
+static ObjTree *
+deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
+{
+	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *stmt;
+	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);
+
+	return stmt;
+}
+
 /*
  * deparse_ViewStmt
  *		deparse a ViewStmt
@@ -4508,7 +4550,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterExtensionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionContentsStmt:
-- 
2.1.4

0026-deparse-Support-GRANT-REVOKE.patchtext/x-diff; charset=us-asciiDownload
>From 5288c7da6eea6b1880c2d21bb90085bed90396f3 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 26/44] deparse: Support GRANT/REVOKE

---
 src/backend/catalog/aclchk.c         |  37 ++----
 src/backend/commands/event_trigger.c | 103 +++++++++++++++++
 src/backend/tcop/deparse_utility.c   | 215 +++++++++++++++++++++++++++++++++++
 src/include/commands/event_trigger.h |   2 +
 src/include/tcop/deparse_utility.h   |  11 +-
 src/include/utils/aclchk.h           |  45 ++++++++
 6 files changed, 386 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 1e3888e..9e79654 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,15 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	if (EventTriggerSupportsGrantObjectType(istmt->objtype))
+		EventTriggerStashGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 1024443..d424030 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1707,6 +1707,85 @@ EventTriggerComplexCmdEnd(void)
 	currentEventTriggerState->curcmd = NULL;
 }
 
+static const char *
+stringify_grantobjtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+			return "COLUMN";
+		case ACL_OBJECT_RELATION:
+			return "TABLE";
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case ACL_OBJECT_DATABASE:
+			return "DATABASE";
+		case ACL_OBJECT_DOMAIN:
+			return "DOMAIN";
+		case ACL_OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case ACL_OBJECT_FOREIGN_SERVER:
+			return "FOREIGN SERVER";
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTION";
+		case ACL_OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case ACL_OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case ACL_OBJECT_NAMESPACE:
+			return "SCHEMA";
+		case ACL_OBJECT_TABLESPACE:
+			return "TABLESPACE";
+		case ACL_OBJECT_TYPE:
+			return "TYPE";
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
+
+/*
+ * 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 ... 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 = creating_extension;
+	stashed->d.grant.type = stringify_grantobjtype(istmt->objtype);
+	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)
 {
@@ -1875,6 +1954,30 @@ 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(cmd->d.grant.istmt->is_grant ?
+												  "GRANT" : "REVOKE");
+				/* object_type */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.type);
+				/* 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 58eb420..2177eac 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"
@@ -4006,6 +4011,213 @@ deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_GrantStmt(StashedCommand *cmd)
+{
+	InternalGrant *istmt;
+	ObjTree	   *grantStmt;
+	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 = "FOREIGN 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(tmp));
+	}
+	else
+	{
+		list = NIL;
+
+		if (istmt->privileges & ACL_INSERT)
+			list = lappend(list, new_string_object("INSERT"));
+		if (istmt->privileges & ACL_SELECT)
+			list = lappend(list, new_string_object("SELECT"));
+		if (istmt->privileges & ACL_UPDATE)
+			list = lappend(list, new_string_object("UPDATE"));
+		if (istmt->privileges & ACL_DELETE)
+			list = lappend(list, new_string_object("DELETE"));
+		if (istmt->privileges & ACL_TRUNCATE)
+			list = lappend(list, new_string_object("TRUNCATE"));
+		if (istmt->privileges & ACL_REFERENCES)
+			list = lappend(list, new_string_object("REFERENCES"));
+		if (istmt->privileges & ACL_TRIGGER)
+			list = lappend(list, new_string_object("TRIGGER"));
+		if (istmt->privileges & ACL_EXECUTE)
+			list = lappend(list, new_string_object("EXECUTE"));
+		if (istmt->privileges & ACL_USAGE)
+			list = lappend(list, new_string_object("USAGE"));
+		if (istmt->privileges & ACL_CREATE)
+			list = lappend(list, new_string_object("CREATE"));
+		if (istmt->privileges & ACL_CREATE_TEMP)
+			list = lappend(list, new_string_object("TEMPORARY"));
+		if (istmt->privileges & ACL_CONNECT)
+			list = lappend(list, new_string_object("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(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(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(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(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", "");
+	}
+
+	return grantStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -4788,6 +5000,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterTable:
 			tree = deparse_AlterTableStmt(cmd);
 			break;
+		case SCT_Grant:
+			tree = 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 36d36ff..12e920c 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
 {
@@ -62,6 +63,7 @@ extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 
 extern void EventTriggerStashCommand(ObjectAddress address, Oid secondaryOid,
 						 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 0986748..5a9b5d7 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_Simple,
-	SCT_AlterTable
+	SCT_AlterTable,
+	SCT_Grant
 } StashedCommandType;
 
 /*
@@ -59,6 +62,12 @@ typedef struct StashedCommand
 			ObjectType objtype;
 			List   *subcmds;
 		} alterTable;
+
+		struct GrantCommand
+		{
+			InternalGrant *istmt;
+			const char *type;
+		} 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 */
-- 
2.1.4

0027-deparse-Support-ALTER-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From 9aebb56bde723f7dce7d932b8005bc41bd094a21 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 8 Aug 2014 14:55:47 +0200
Subject: [PATCH 27/44] deparse: Support ALTER FUNCTION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 2177eac..b208f56 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2989,6 +2989,118 @@ deparse_CreateFunction(Oid objectId, Node *parsetree)
 }
 
 /*
+ * deparse_AlterFunctionStmt
+ *		Deparse a AlterFunctionStmt (ALTER FUNCTION)
+ *
+ * Given a function OID and the parsetree that created it, return the JSON
+ * blob representing the alter command.
+ *
+ * XXX this is missing the per-function custom-GUC thing.
+ */
+static ObjTree *
+deparse_AlterFunction(Oid objectId, Node *parsetree)
+{
+	AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree;
+	ObjTree	   *alterFunc;
+	ObjTree	   *sign;
+	HeapTuple	procTup;
+	Form_pg_proc procForm;
+	List	   *params;
+	List	   *elems = NIL;
+	ListCell   *cell;
+	int			i;
+
+	/* 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);
+
+	alterFunc = new_objtree_VA("ALTER FUNCTION %{signature}s %{definition: }s", 0);
+
+	sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0);
+
+	params = NIL;
+
+	/*
+	 * ALTER FUNCTION does not change signature so we can use catalog
+	 * to get input type Oids.
+	 */
+	for (i = 0; i < procForm->pronargs; i++)
+	{
+		ObjTree	   *tmp = new_objtree_VA("%{type}T", 0);
+
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(procForm->proargtypes.values[i], -1));
+		params = lappend(params, new_object_object(tmp));
+	}
+
+	append_array_object(sign, "arguments", params);
+	append_object_object(sign, "identity",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 objectId));
+	append_object_object(alterFunc, "signature", sign);
+
+	foreach(cell, node->actions)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "volatility") == 0)
+		{
+			tmp = new_objtree_VA(strVal(defel->arg), 0);
+		}
+		else if (strcmp(defel->defname, "strict") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "RETURNS NULL ON NULL INPUT" :
+								 "CALLED ON NULL INPUT", 0);
+		}
+		else if (strcmp(defel->defname, "security") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "SECURITY DEFINER" : "SECURITY INVOKER", 0);
+		}
+		else if (strcmp(defel->defname, "leakproof") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "LEAKPROOF" : "", 0);
+		}
+		else if (strcmp(defel->defname, "cost") == 0)
+		{
+			tmp = new_objtree_VA("COST %{cost}n", 1,
+								 "cost", ObjTypeFloat,
+								 defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "rows") == 0)
+		{
+			tmp = new_objtree_VA("ROWS %{rows}n", 0);
+			if (defGetNumeric(defel) == 0)
+				append_bool_object(tmp, "present", false);
+			else
+				append_float_object(tmp, "rows",
+									defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+		}
+
+		elems = lappend(elems, new_object_object(tmp));
+	}
+
+	append_array_object(alterFunc, "definition", elems);
+
+	ReleaseSysCache(procTup);
+
+	return alterFunc;
+}
+
+/*
  * Return the given object type as a string.
  */
 static const char *
@@ -4828,7 +4940,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterFunctionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterFunction(objectId, parsetree);
 			break;
 
 		case T_RuleStmt:
-- 
2.1.4

0028-deparse-support-COMMENT-ON.patchtext/x-diff; charset=us-asciiDownload
>From f2c6d0dd6dcb28389d6885632623f5e4dc209f28 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:45:04 -0300
Subject: [PATCH 28/44] deparse: support COMMENT ON

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index b208f56..7132534 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4052,6 +4052,94 @@ deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
 	return ownerStmt;
 }
 
+/*
+ * Deparse a CommentStmt when it pertains to a constraint.
+ */
+static ObjTree *
+deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	HeapTuple	constrTup;
+	Form_pg_constraint constrForm;
+	char	   *fmt;
+	ObjectAddress addr;
+
+	Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
+
+	if (node->comment)
+		fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}L",
+					   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	else
+		fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS NULL",
+					   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	comment = new_objtree_VA(fmt, 0);
+	if (node->comment)
+		append_string_object(comment, "comment", node->comment);
+
+	constrTup = SearchSysCache1(CONSTROID, objectId);
+	if (!HeapTupleIsValid(constrTup))
+		elog(ERROR, "cache lookup failed for constraint %u", objectId);
+	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
+
+	append_string_object(comment, "identity", pstrdup(NameStr(constrForm->conname)));
+
+	if (OidIsValid(constrForm->conrelid))
+	{
+		addr.classId = RelationRelationId;
+		addr.objectId = constrForm->conrelid;
+		addr.objectSubId = 0;
+	}
+	else
+	{
+		addr.classId = TypeRelationId;
+		addr.objectId = constrForm->contypid;
+		addr.objectSubId = 0;
+	}
+
+	append_string_object(comment, "parentobj",
+						 getObjectIdentity(&addr));
+
+	ReleaseSysCache(constrTup);
+
+	return comment;
+}
+
+static ObjTree *
+deparse_CommentStmt(ObjectAddress address, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	char	   *fmt;
+
+	/*
+	 * Constraints are sufficiently different that it is easier to handle them
+	 * separately.
+	 */
+	if (node->objtype == OBJECT_DOMCONSTRAINT ||
+		node->objtype == OBJECT_TABCONSTRAINT)
+	{
+		Assert(address.classId == ConstraintRelationId);
+		return deparse_CommentOnConstraintSmt(address.objectId, parsetree);
+	}
+
+	if (node->comment)
+		fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}L",
+					   stringify_objtype(node->objtype));
+	else
+		fmt = psprintf("COMMENT ON %s %%{identity}s IS NULL",
+					   stringify_objtype(node->objtype));
+
+	comment = new_objtree_VA(fmt, 0);
+	if (node->comment)
+		append_string_object(comment, "comment", node->comment);
+
+	append_string_object(comment, "identity",
+						 getObjectIdentity(&address));
+
+	return comment;
+}
+
 static ObjTree *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
@@ -5022,7 +5110,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CommentStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CommentStmt(cmd->d.simple.address, parsetree);
 			break;
 
 		case T_GrantStmt:
-- 
2.1.4

0029-deparse-support-SECURITY-LABEL.patchtext/x-diff; charset=us-asciiDownload
>From 4bc5bc2e24304c716618b23ea3a70324dab262cf Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 10 Feb 2015 17:35:51 -0300
Subject: [PATCH 29/44] deparse: support SECURITY LABEL

---
 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 7132534..f680733 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4141,6 +4141,36 @@ deparse_CommentStmt(ObjectAddress address, Node *parsetree)
 }
 
 static ObjTree *
+deparse_SecLabelStmt(ObjectAddress address, Node *parsetree)
+{
+	SecLabelStmt *node = (SecLabelStmt *) parsetree;
+	ObjTree	   *label;
+	char	   *fmt;
+
+	if (node->label)
+	{
+		fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}L",
+				   stringify_objtype(node->objtype));
+		label = new_objtree_VA(fmt, 0);
+
+		append_string_object(label, "label", node->label);
+	}
+	else
+	{
+		fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS NULL",
+				   stringify_objtype(node->objtype));
+		label = new_objtree_VA(fmt, 0);
+	}
+
+	append_string_object(label, "provider", node->provider);
+
+	append_string_object(label, "identity",
+						 getObjectIdentity(&address));
+
+	return label;
+}
+
+static ObjTree *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
 	HeapTuple   conTup;
@@ -5136,7 +5166,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_SecLabelStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_SecLabelStmt(cmd->d.simple.address, parsetree);
 			break;
 
 		default:
-- 
2.1.4

0030-deparse-support-ALTER-DOMAIN.patchtext/x-diff; charset=us-asciiDownload
>From 80b255e12f3f6319b4591b9f8d1a13654b2f812d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 9 Feb 2015 15:38:07 -0300
Subject: [PATCH 30/44] deparse: support ALTER DOMAIN

---
 src/backend/commands/typecmds.c    | 70 +++++++++++++++++---------------
 src/backend/tcop/deparse_utility.c | 83 +++++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c         |  3 +-
 src/backend/utils/adt/ruleutils.c  | 20 +++++++++
 src/include/commands/typecmds.h    |  3 +-
 src/include/utils/ruleutils.h      |  2 +
 6 files changed, 145 insertions(+), 36 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index a2576f9..1de37eb 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -108,7 +108,7 @@ static void checkEnumOwner(HeapTuple tup);
 static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
 					Oid baseTypeOid,
 					int typMod, Constraint *constr,
-					char *domainName);
+					char *domainName, Oid *constrOid);
 
 
 /*
@@ -1075,7 +1075,7 @@ DefineDomain(CreateDomainStmt *stmt)
 			case CONSTR_CHECK:
 				domainAddConstraint(domainoid, domainNamespace,
 									basetypeoid, basetypeMod,
-									constr, domainName);
+									constr, domainName, NULL);
 				break;
 
 				/* Other constraint types were fully processed above */
@@ -2482,7 +2482,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
  * Implements the ALTER DOMAIN .. ADD CONSTRAINT statement.
  */
 ObjectAddress
-AlterDomainAddConstraint(List *names, Node *newConstraint)
+AlterDomainAddConstraint(List *names, Node *newConstraint, Oid *constrOid)
 {
 	TypeName   *typename;
 	Oid			domainoid;
@@ -2567,7 +2567,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
 
 	ccbin = domainAddConstraint(domainoid, typTup->typnamespace,
 								typTup->typbasetype, typTup->typtypmod,
-								constr, NameStr(typTup->typname));
+								constr, NameStr(typTup->typname), constrOid);
 
 	/*
 	 * If requested to validate the constraint, test all values stored in the
@@ -2990,13 +2990,14 @@ checkDomainOwner(HeapTuple tup)
 static char *
 domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 					int typMod, Constraint *constr,
-					char *domainName)
+					char *domainName, Oid *constrOid)
 {
 	Node	   *expr;
 	char	   *ccsrc;
 	char	   *ccbin;
 	ParseState *pstate;
 	CoerceToDomainValue *domVal;
+	Oid			ccoid;
 
 	/*
 	 * Assign or validate constraint name
@@ -3075,34 +3076,37 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	/*
 	 * Store the constraint in pg_constraint
 	 */
-	CreateConstraintEntry(constr->conname,		/* Constraint Name */
-						  domainNamespace,		/* namespace */
-						  CONSTRAINT_CHECK,		/* Constraint Type */
-						  false,	/* Is Deferrable */
-						  false,	/* Is Deferred */
-						  !constr->skip_validation,		/* Is Validated */
-						  InvalidOid,	/* not a relation constraint */
-						  NULL,
-						  0,
-						  domainOid,	/* domain constraint */
-						  InvalidOid,	/* no associated index */
-						  InvalidOid,	/* Foreign key fields */
-						  NULL,
-						  NULL,
-						  NULL,
-						  NULL,
-						  0,
-						  ' ',
-						  ' ',
-						  ' ',
-						  NULL, /* not an exclusion constraint */
-						  expr, /* Tree form of check constraint */
-						  ccbin,	/* Binary form of check constraint */
-						  ccsrc,	/* Source form of check constraint */
-						  true, /* is local */
-						  0,	/* inhcount */
-						  false,	/* connoinherit */
-						  false);		/* is_internal */
+	ccoid =
+		CreateConstraintEntry(constr->conname,		/* Constraint Name */
+							  domainNamespace,		/* namespace */
+							  CONSTRAINT_CHECK,		/* Constraint Type */
+							  false,	/* Is Deferrable */
+							  false,	/* Is Deferred */
+							  !constr->skip_validation,		/* Is Validated */
+							  InvalidOid,	/* not a relation constraint */
+							  NULL,
+							  0,
+							  domainOid,	/* domain constraint */
+							  InvalidOid,	/* no associated index */
+							  InvalidOid,	/* Foreign key fields */
+							  NULL,
+							  NULL,
+							  NULL,
+							  NULL,
+							  0,
+							  ' ',
+							  ' ',
+							  ' ',
+							  NULL, /* not an exclusion constraint */
+							  expr, /* Tree form of check constraint */
+							  ccbin,	/* Binary form of check constraint */
+							  ccsrc,	/* Source form of check constraint */
+							  true, /* is local */
+							  0,	/* inhcount */
+							  false,	/* connoinherit */
+							  false);		/* is_internal */
+	if (constrOid)
+		*constrOid = ccoid;
 
 	/*
 	 * Return the compiled constraint expression so the calling routine can
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index f680733..5e89514 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1587,6 +1587,86 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_AlterDomainStmt(Oid objectId, Node *parsetree, Oid secondaryOid)
+{
+	AlterDomainStmt *node = (AlterDomainStmt *) parsetree;
+	HeapTuple	domTup;
+	Form_pg_type domForm;
+	ObjTree	   *alterDom;
+	char	   *fmt;
+
+	/* ALTER DOMAIN DROP CONSTRAINT is handled by the DROP support code */
+	if (node->subtype == 'X')
+		return NULL;
+
+	domTup = SearchSysCache1(TYPEOID, objectId);
+	if (!HeapTupleIsValid(domTup))
+		elog(ERROR, "cache lookup failed for domain with OID %u",
+			 objectId);
+	domForm = (Form_pg_type) GETSTRUCT(domTup);
+
+	switch (node->subtype)
+	{
+		case 'T':
+			/* SET DEFAULT / DROP DEFAULT */
+			if (node->def == NULL)
+				fmt = "ALTER DOMAIN %{identity}D DROP DEFAULT";
+			else
+				fmt = "ALTER DOMAIN %{identity}D SET DEFAULT %{default}s";
+			break;
+		case 'N':
+			/* DROP NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D DROP NOT NULL";
+			break;
+		case 'O':
+			/* SET NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D SET NOT NULL";
+			break;
+		case 'C':
+			/* ADD CONSTRAINT.  Only CHECK constraints are supported by domains */
+			fmt = "ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}I %{definition}s";
+			break;
+		case 'V':
+			/* VALIDATE CONSTRAINT */
+			fmt = "ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}I";
+			break;
+		default:
+			elog(ERROR, "invalid subtype %c", node->subtype);
+	}
+
+	alterDom = new_objtree_VA(fmt, 0);
+	append_object_object(alterDom, "identity",
+						 new_objtree_for_qualname(domForm->typnamespace,
+												  NameStr(domForm->typname)));
+
+	/*
+	 * Process subtype-specific options.  Validating a constraint
+	 * requires its name ...
+	 */
+	if (node->subtype == 'V')
+		append_string_object(alterDom, "constraint_name", node->name);
+
+	/* ... a new constraint has a name and definition ... */
+	if (node->subtype == 'C')
+	{
+		append_string_object(alterDom, "definition",
+							 pg_get_constraintdef_string(secondaryOid, false));
+		/* can't rely on node->name here; might not be defined */
+		append_string_object(alterDom, "constraint_name",
+							 get_constraint_name(secondaryOid));
+	}
+
+	/* ... and setting a default has a definition only. */
+	if (node->subtype == 'T' && node->def != NULL)
+		append_string_object(alterDom, "default", DomainGetDefault(domTup));
+
+	/* done */
+	ReleaseSysCache(domTup);
+
+	return alterDom;
+}
+
+static ObjTree *
 deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
 {
 	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
@@ -4975,7 +5055,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterDomainStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterDomainStmt(objectId, parsetree,
+											  cmd->d.simple.secondaryOid);
 			break;
 
 			/* other local objects */
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 9b6e1f4..b45965c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1117,7 +1117,8 @@ ProcessUtilitySlow(Node *parsetree,
 						case 'C':		/* ADD CONSTRAINT */
 							address =
 								AlterDomainAddConstraint(stmt->typeName,
-														 stmt->def);
+														 stmt->def,
+														 &secondaryOid);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
 							address =
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3ec3e5f..ffc8c1b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9700,6 +9700,26 @@ RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
 	return defstr;
 }
 
+char *
+DomainGetDefault(HeapTuple domTup)
+{
+	Datum	def;
+	Node   *defval;
+	char   *defstr;
+	bool	isnull;
+
+	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+							 &isnull);
+	if (isnull)
+		elog(ERROR, "domain \"%s\" does not have a default value",
+			 NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+	defval = stringToNode(TextDatumGetCString(def));
+	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
+									   false, false, 0, 0);
+
+	return defstr;
+}
+
 /*
  * Return the defaults values of arguments to a function, as a list of
  * deparsed expressions.
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index a0082de..a9e1449 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -32,7 +32,8 @@ extern Oid	AssignTypeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
-extern ObjectAddress AlterDomainAddConstraint(List *names, Node *constr);
+extern ObjectAddress AlterDomainAddConstraint(List *names, Node *constr,
+						 Oid *constrOid);
 extern ObjectAddress AlterDomainValidateConstraint(List *names, char *constrName);
 extern ObjectAddress AlterDomainDropConstraint(List *names, const char *constrName,
 						  DropBehavior behavior, bool missing_ok);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 91b61f9..7b22ea0 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -48,4 +48,6 @@ extern List *FunctionGetDefaults(text *proargdefaults);
 extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
 						 List *dpcontext);
 
+extern char *DomainGetDefault(HeapTuple domTup);
+
 #endif	/* RULEUTILS_H */
-- 
2.1.4

0031-deparse-support-ALTER-.-SET-SCHEMA.patchtext/x-diff; charset=us-asciiDownload
>From a0fe2f007420254d908b64c266e8956d042c1c43 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 9 Feb 2015 19:17:21 -0300
Subject: [PATCH 31/44] deparse: support ALTER ... SET SCHEMA

---
 src/backend/commands/alter.c       | 16 ++++++++------
 src/backend/commands/extension.c   |  6 +++++-
 src/backend/commands/tablecmds.c   |  5 ++++-
 src/backend/commands/typecmds.c    |  9 ++++++--
 src/backend/tcop/deparse_utility.c | 44 +++++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c         |  5 +++--
 src/include/commands/alter.h       |  3 ++-
 src/include/commands/extension.h   |  3 ++-
 src/include/commands/tablecmds.h   |  2 +-
 src/include/commands/typecmds.h    |  3 ++-
 10 files changed, 79 insertions(+), 17 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c8901be..bdc79c9 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -394,24 +394,24 @@ ExecRenameStmt(RenameStmt *stmt)
  * type, the function appropriate to that type is executed.
  */
 Oid
-ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
+ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, Oid *oldschema)
 {
 	switch (stmt->objectType)
 	{
 		case OBJECT_EXTENSION:
-			return AlterExtensionNamespace(stmt->object, stmt->newschema);
+			return AlterExtensionNamespace(stmt->object, stmt->newschema, oldschema);
 
 		case OBJECT_FOREIGN_TABLE:
 		case OBJECT_SEQUENCE:
 		case OBJECT_TABLE:
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
-			return AlterTableNamespace(stmt);
+			return AlterTableNamespace(stmt, oldschema);
 
 		case OBJECT_DOMAIN:
 		case OBJECT_TYPE:
 			return AlterTypeNamespace(stmt->object, stmt->newschema,
-									  stmt->objectType);
+									  stmt->objectType, oldschema);
 
 			/* generic code path */
 		case OBJECT_AGGREGATE:
@@ -431,6 +431,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
 				Oid			classId;
 				Oid			nspOid;
 				ObjectAddress address;
+				Oid			oldNspOid;
 
 				address = get_object_address(stmt->objectType,
 											 stmt->object,
@@ -443,10 +444,13 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
 				catalog = heap_open(classId, RowExclusiveLock);
 				nspOid = LookupCreationNamespace(stmt->newschema);
 
-				AlterObjectNamespace_internal(catalog, address.objectId,
-											  nspOid);
+				oldNspOid = AlterObjectNamespace_internal(catalog, address.objectId,
+														  nspOid);
 				heap_close(catalog, RowExclusiveLock);
 
+				if (oldschema)
+					*oldschema = oldNspOid;
+
 				return address.objectId;
 			}
 			break;
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 73f8bea..4b28a8d 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2400,7 +2400,7 @@ extension_config_remove(Oid extensionoid, Oid tableoid)
  * Execute ALTER EXTENSION SET SCHEMA
  */
 Oid
-AlterExtensionNamespace(List *names, const char *newschema)
+AlterExtensionNamespace(List *names, const char *newschema, Oid *oldschema)
 {
 	char	   *extensionName;
 	Oid			extensionOid;
@@ -2557,6 +2557,10 @@ AlterExtensionNamespace(List *names, const char *newschema)
 							   get_namespace_name(oldNspOid))));
 	}
 
+	/* report old schema, if caller wants it */
+	if (oldschema)
+		*oldschema = oldNspOid;
+
 	systable_endscan(depScan);
 
 	relation_close(depRel, AccessShareLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 43f4018..5be235c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11052,7 +11052,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
  * Execute ALTER TABLE SET SCHEMA
  */
 Oid
-AlterTableNamespace(AlterObjectSchemaStmt *stmt)
+AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
 {
 	Relation	rel;
 	Oid			relid;
@@ -11104,6 +11104,9 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt)
 	AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
 	free_object_addresses(objsMoved);
 
+	if (oldschema)
+		*oldschema = oldNspOid;
+
 	/* close rel, but keep lock until commit */
 	relation_close(rel, NoLock);
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 1de37eb..1d58b9d 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3540,11 +3540,13 @@ AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
  * Execute ALTER TYPE SET SCHEMA
  */
 Oid
-AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype)
+AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype,
+				   Oid *oldschema)
 {
 	TypeName   *typename;
 	Oid			typeOid;
 	Oid			nspOid;
+	Oid			oldNspOid;
 	ObjectAddresses *objsMoved;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
@@ -3562,9 +3564,12 @@ AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype)
 	nspOid = LookupCreationNamespace(newschema);
 
 	objsMoved = new_object_addresses();
-	AlterTypeNamespace_oid(typeOid, nspOid, objsMoved);
+	oldNspOid = AlterTypeNamespace_oid(typeOid, nspOid, objsMoved);
 	free_object_addresses(objsMoved);
 
+	if (oldschema)
+		*oldschema = oldNspOid;
+
 	return typeOid;
 }
 
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 5e89514..17e6c08 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4110,6 +4110,47 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_AlterObjectSchemaStmt(Oid objectId, Node *parsetree, Oid oldschema)
+{
+	AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree;
+	ObjTree	   *alterStmt;
+	ObjectAddress addr;
+	char	   *fmt;
+	char	   *identity;
+	char	   *newschema;
+	char	   *oldschname;
+	char	   *ident;
+
+	newschema = node->newschema;
+
+	fmt = psprintf("ALTER %s %%{identity}s SET SCHEMA %%{newschema}I",
+				   stringify_objtype(node->objectType));
+	alterStmt = new_objtree_VA(fmt, 0);
+	append_string_object(alterStmt, "newschema", newschema);
+
+	/*
+	 * Since the command has already taken place from the point of view of
+	 * catalogs, getObjectIdentity returns the object name with the already
+	 * changed schema.  The output of our deparsing must return the original
+	 * schema name however, so we chop the schema name off the identity string
+	 * and then prepend the quoted schema name.
+	 */
+	addr.classId = get_objtype_catalog_oid(node->objectType);
+	addr.objectId = objectId;
+	addr.objectSubId = 0;
+	identity = getObjectIdentity(&addr);
+	oldschname = get_namespace_name(oldschema);
+	if (!oldschname)
+		elog(ERROR, "cache lookup failed for schema with OID %u", oldschema);
+	ident = psprintf("%s%s",
+					 quote_identifier(oldschname),
+					 identity + strlen(quote_identifier(newschema)));
+	append_string_object(alterStmt, "identity", ident);
+
+	return alterStmt;
+}
+
+static ObjTree *
 deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
 {
 	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
@@ -5213,7 +5254,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterObjectSchemaStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterObjectSchemaStmt(objectId, parsetree,
+													cmd->d.simple.secondaryOid);
 			break;
 
 		case T_AlterOwnerStmt:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b45965c..f8476d2 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -818,7 +818,7 @@ standard_ProcessUtility(Node *parsetree,
 									   context, params,
 									   dest, completionTag);
 				else
-					ExecAlterObjectSchemaStmt(stmt);
+					ExecAlterObjectSchemaStmt(stmt, NULL);
 			}
 			break;
 
@@ -1448,7 +1448,8 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				objectId = ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
+				objectId = ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
+													 &secondaryOid);
 				objectType = ((AlterObjectSchemaStmt *) parsetree)->objectType;
 				break;
 
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index 5df28d3..7c4dc4b 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -21,7 +21,8 @@
 
 extern ObjectAddress ExecRenameStmt(RenameStmt *stmt);
 
-extern Oid	ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
+extern Oid	ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
+						  Oid *oldschema);
 extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 						 ObjectAddresses *objsMoved);
 
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 965f96b..b3b73cc 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -44,7 +44,8 @@ extern Oid	ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 extern Oid	get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
 
-extern Oid	AlterExtensionNamespace(List *names, const char *newschema);
+extern Oid	AlterExtensionNamespace(List *names, const char *newschema,
+						Oid *oldschema);
 
 extern void AlterExtensionOwner_oid(Oid extensionOid, Oid newOwnerId);
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 876fbfd..73e9cf1 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -38,7 +38,7 @@ extern void AlterTableInternal(Oid relid, List *cmds, bool recurse);
 
 extern Oid	AlterTableMoveAll(AlterTableMoveAllStmt *stmt);
 
-extern Oid	AlterTableNamespace(AlterObjectSchemaStmt *stmt);
+extern Oid	AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema);
 
 extern void AlterTableNamespaceInternal(Relation rel, Oid oldNspOid,
 							Oid nspOid, ObjectAddresses *objsMoved);
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index a9e1449..19f7afc 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -46,7 +46,8 @@ extern ObjectAddress RenameType(RenameStmt *stmt);
 extern Oid	AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype);
 extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
 					   bool hasDependEntry);
-extern Oid	AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype);
+extern Oid	AlterTypeNamespace(List *names, const char *newschema,
+				   ObjectType objecttype, Oid *oldschema);
 extern Oid	AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, ObjectAddresses *objsMoved);
 extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
 						   bool isImplicitArray,
-- 
2.1.4

0032-deparse-support-ALTER-EXTENSION-ADD-DROP.patchtext/x-diff; charset=us-asciiDownload
>From f25904bd6e05292652fb6d04e8497f62e4408e98 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 9 Feb 2015 20:41:51 -0300
Subject: [PATCH 32/44] deparse: support ALTER EXTENSION ADD/DROP

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 17e6c08..75e3c31 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1667,6 +1667,30 @@ deparse_AlterDomainStmt(Oid objectId, Node *parsetree, Oid secondaryOid)
 }
 
 static ObjTree *
+deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree, Oid secondaryOid)
+{
+	AlterExtensionContentsStmt *node = (AlterExtensionContentsStmt *) parsetree;
+	ObjTree	   *stmt;
+	char	   *fmt;
+	ObjectAddress addr;
+
+	Assert(node->action == +1 || node->action == -1);
+
+	fmt = psprintf("ALTER EXTENSION %%{extidentity}I %s %s %%{objidentity}s",
+				   node->action == +1 ? "ADD" : "DROP",
+				   stringify_objtype(node->objtype));
+	addr.classId = get_objtype_catalog_oid(node->objtype);
+	addr.objectId = secondaryOid;
+	addr.objectSubId = 0;
+
+	stmt = new_objtree_VA(fmt, 2, "extidentity", ObjTypeString, node->extname,
+						  "objidentity", ObjTypeString,
+						  getObjectIdentity(&addr));
+
+	return stmt;
+}
+
+static ObjTree *
 deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
 {
 	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
@@ -5118,7 +5142,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterExtensionContentsStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterExtensionContentsStmt(objectId, parsetree,
+														 cmd->d.simple.secondaryOid);
 			break;
 
 		case T_CreateFdwStmt:
-- 
2.1.4

0033-deparse-support-CREATE-CAST.patchtext/x-diff; charset=us-asciiDownload
>From 22dcac16ad0dceca1bb772a32568ed9d5d2f1ed6 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 10 Feb 2015 14:09:51 -0300
Subject: [PATCH 33/44] deparse: support CREATE CAST

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 75e3c31..5ea9622 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_cast.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -4351,6 +4352,81 @@ deparse_CreateConversion(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateCastStmt(Oid objectId, Node *parsetree)
+{
+	CreateCastStmt *node = (CreateCastStmt *) parsetree;
+	Relation	castrel;
+	HeapTuple	castTup;
+	Form_pg_cast castForm;
+	ObjTree	   *createCast;
+	char	   *context;
+
+	castrel = heap_open(CastRelationId, AccessShareLock);
+	castTup = get_catalog_object_by_oid(castrel, objectId);
+	if (!HeapTupleIsValid(castTup))
+		elog(ERROR, "cache lookup failed for cast with OID %u", objectId);
+	castForm = (Form_pg_cast) GETSTRUCT(castTup);
+
+	createCast = new_objtree_VA("CREATE CAST (%{sourcetype}T AS %{targettype}T) %{mechanism}s %{context}s",
+								2, "sourcetype", ObjTypeObject,
+								new_objtree_for_type(castForm->castsource, -1),
+								"targettype", ObjTypeObject,
+								new_objtree_for_type(castForm->casttarget, -1));
+
+	if (node->inout)
+		append_string_object(createCast, "mechanism", "WITH INOUT");
+	else if (node->func == NULL)
+		append_string_object(createCast, "mechanism", "WITHOUT FUNCTION");
+	else
+	{
+		ObjTree	   *tmp;
+		StringInfoData func;
+		HeapTuple	funcTup;
+		Form_pg_proc funcForm;
+		int			i;
+
+		funcTup = SearchSysCache1(PROCOID, castForm->castfunc);
+		funcForm = (Form_pg_proc) GETSTRUCT(funcTup);
+
+		initStringInfo(&func);
+		appendStringInfo(&func, "%s(",
+						quote_qualified_identifier(get_namespace_name(funcForm->pronamespace),
+												   NameStr(funcForm->proname)));
+		for (i = 0; i < funcForm->pronargs; i++)
+			appendStringInfoString(&func,
+								   format_type_be_qualified(funcForm->proargtypes.values[i]));
+		appendStringInfoChar(&func, ')');
+
+		tmp = new_objtree_VA("WITH FUNCTION %{castfunction}s", 1,
+							 "castfunction", ObjTypeString, func.data);
+		append_object_object(createCast, "mechanism", tmp);
+
+		ReleaseSysCache(funcTup);
+	}
+
+	switch (node->context)
+	{
+		case COERCION_IMPLICIT:
+			context = "AS IMPLICIT";
+			break;
+		case COERCION_ASSIGNMENT:
+			context = "AS ASSIGNMENT";
+			break;
+		case COERCION_EXPLICIT:
+			context = "";
+			break;
+		default:
+			elog(ERROR, "invalid coercion code %c", node->context);
+			return NULL;	/* keep compiler quiet */
+	}
+	append_string_object(createCast, "context", context);
+
+	heap_close(castrel, AccessShareLock);
+
+	return createCast;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -5246,7 +5322,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateCastStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateCastStmt(objectId, parsetree);
 			break;
 
 		case T_CreateOpClassStmt:
-- 
2.1.4

0034-deparse-support-CREATE-TABLE-AS.patchtext/x-diff; charset=us-asciiDownload
>From d70f5a9d0ec0575144e70ad83a8045bb2d8558e7 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:30:04 -0300
Subject: [PATCH 34/44] deparse: support CREATE TABLE AS

---
 src/backend/tcop/deparse_utility.c | 142 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  15 ++++
 src/include/utils/ruleutils.h      |   1 +
 3 files changed, 156 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 5ea9622..a500795 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4060,7 +4060,146 @@ deparse_RuleStmt(Oid objectId, Node *parsetree)
 	return ruleStmt;
 }
 
+static ObjTree *
+deparse_CreateTableAsStmt(Oid objectId, Node *parsetree)
+{
+	CreateTableAsStmt *node = (CreateTableAsStmt *) parsetree;
+	ObjTree	   *createStmt;
+	ObjTree	   *tmp;
+	char	   *fmt;
+	List	   *list;
 
+	/*
+	 * Reject unsupported case right away.
+	 */
+	if (((Query *) (node->query))->commandType == CMD_UTILITY)
+		elog(ERROR, "unimplemented deparse of CREATE TABLE AS EXECUTE");
+
+	/*
+	 * Note that INSERT INTO is deparsed as CREATE TABLE AS.  They are
+	 * functionally equivalent synonyms so there is no harm from this.
+	 */
+	if (node->relkind == OBJECT_MATVIEW)
+		fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+	else
+		fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with}s %{on_commit}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+
+	createStmt =
+		new_objtree_VA(fmt, 2,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(node->into->rel->relpersistence),
+					   "if_not_exists", ObjTypeString,
+					   node->if_not_exists ? "IF NOT EXISTS" : "");
+	append_object_object(createStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+
+	/* Add an ON COMMIT clause.  CREATE MATERIALIZED VIEW doesn't have one */
+	if (node->relkind == OBJECT_TABLE)
+	{
+		tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0);
+		switch (node->into->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);
+	}
+
+	/* add a TABLESPACE clause */
+	tmp = new_objtree_VA("TABLESPACE %{tablespace}I", 0);
+	if (node->into->tableSpaceName)
+		append_string_object(tmp, "tablespace", node->into->tableSpaceName);
+	else
+	{
+		append_null_object(tmp, "tablespace");
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(createStmt, "tablespace", tmp);
+
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->into->skipData ? true : false);
+	append_object_object(createStmt, "with_no_data", tmp);
+
+	/* add the column list, if any */
+	if (node->into->colNames == NIL)
+		tmp = new_objtree_VA("", 1,
+							 "present", ObjTypeBool, false);
+	else
+	{
+		ListCell *cell;
+
+		list = NIL;
+		foreach (cell, node->into->colNames)
+			list = lappend(list, new_string_object(strVal(lfirst(cell))));
+
+		tmp = new_objtree_VA("(%{columns:, }I)", 1,
+							 "columns", ObjTypeArray, list);
+	}
+	append_object_object(createStmt, "columns", tmp);
+
+	/* add the query */
+	Assert(IsA(node->query, Query));
+	append_string_object(createStmt, "query",
+						 pg_get_createtableas_def((Query *) node->query));
+
+	/* add a WITH clause, but only if any options are set */
+	tmp = new_objtree_VA("WITH (%{with_options:, }s)", 0);
+	if (node->into->options == NIL)
+	{
+		append_null_object(tmp, "with_options");
+		append_bool_object(tmp, "present", false);
+	}
+	else
+	{
+		ListCell   *cell;
+
+		list = NIL;
+		foreach (cell, node->into->options)
+		{
+			DefElem *opt = (DefElem *) lfirst(cell);
+			char   *defname;
+			char   *value;
+			ObjTree *tmp2;
+
+			if (opt->defnamespace)
+				defname = psprintf("%s.%s", opt->defnamespace, opt->defname);
+			else
+				defname = opt->defname;
+
+			value = opt->arg ? defGetString(opt) :
+				defGetBoolean(opt) ? "TRUE" : "FALSE";
+			tmp2 = new_objtree_VA("%{option}s=%{value}s", 2,
+								  "option", ObjTypeString, defname,
+								  "value", ObjTypeString, value);
+			list = lappend(list, new_object_object(tmp2));
+		}
+		append_array_object(tmp, "with_options", list);
+	}
+	append_object_object(createStmt, "with", tmp);
+
+	return createStmt;
+}
 
 /*
  * deparse_CreateSchemaStmt
@@ -5297,8 +5436,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateTableAsStmt:
-			/* XXX handle at least the CREATE MATVIEW case? */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateTableAsStmt(objectId, parsetree);
 			break;
 
 		case T_RefreshMatViewStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ffc8c1b..134531c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -750,6 +750,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def	- Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * get_triggerdef			- Get the definition of a trigger
  * ----------
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 7b22ea0..2c020e9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -33,6 +33,7 @@ extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
 					   char **whereClause, List **actions);
 extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
 
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
-- 
2.1.4

0035-deparse-support-CREATE-POLICY.patchtext/x-diff; charset=us-asciiDownload
>From 87a4fa55a975f6152d97fb7d04abf8b7f7b36e8d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:30:49 -0300
Subject: [PATCH 35/44] deparse: support CREATE POLICY

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index a500795..e0798b4 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4426,6 +4426,91 @@ deparse_CommentStmt(ObjectAddress address, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreatePolicyStmt(Oid objectId, Node *parsetree)
+{
+	CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree;
+	ObjTree	   *policy;
+	ObjTree	   *tmp;
+	Relation	polRel = heap_open(PolicyRelationId, AccessShareLock);
+	HeapTuple	polTup = get_catalog_object_by_oid(polRel, objectId);
+	Form_pg_policy polForm;
+	ListCell   *cell;
+	List	   *list;
+
+	if (!HeapTupleIsValid(polTup))
+		elog(ERROR, "cache lookup failed for policy %u", objectId);
+	polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+	policy = new_objtree_VA("CREATE POLICY %{identity}I ON %{table}D %{for_command}s "
+							"%{to_role}s %{using}s %{with_check}s", 1,
+							"identity", ObjTypeString, node->policy_name);
+
+	/* add the "ON table" clause */
+	append_object_object(policy, "table",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 polForm->polrelid));
+	/* add "FOR command" clause */
+	tmp = new_objtree_VA("FOR %{command}s", 1,
+						 "command", ObjTypeString, node->cmd);
+	append_object_object(policy, "for_command", tmp);
+
+	/* add the "TO role" clause. Always contains at least PUBLIC */
+	tmp = new_objtree_VA("TO %{role:, }I", 0);
+	list = NIL;
+	foreach (cell, node->roles)
+	{
+		list = lappend(list,
+					   new_string_object(strVal(lfirst(cell))));
+	}
+	append_array_object(tmp, "role", list);
+	append_object_object(policy, "to_role", tmp);
+
+	/* add the USING clause, if any */
+	tmp = new_objtree_VA("USING (%{expression}s)", 0);
+	if (node->qual)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polqual expression in policy %u", objectId);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policy, "using", tmp);
+
+	/* add the WITH CHECK clause, if any */
+	tmp = new_objtree_VA("WITH CHECK (%{expression}s)", 0);
+	if (node->with_check)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polwithcheck expression in policy %u", objectId);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policy, "with_check", tmp);
+
+	heap_close(polRel, AccessShareLock);
+
+	return policy;
+}
+
+static ObjTree *
 deparse_SecLabelStmt(ObjectAddress address, Node *parsetree)
 {
 	SecLabelStmt *node = (SecLabelStmt *) parsetree;
@@ -5520,7 +5605,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreatePolicyStmt:	/* CREATE POLICY */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreatePolicyStmt(objectId, parsetree);
 			break;
 
 		case T_AlterPolicyStmt:		/* ALTER POLICY */
-- 
2.1.4

0036-deparse-support-CREATE-SERVER.patchtext/x-diff; charset=us-asciiDownload
>From e746a981d9e788d4a597d6f20a536dd875ccc75a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:37:57 -0300
Subject: [PATCH 36/44] deparse: support CREATE SERVER

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index e0798b4..6882261 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1733,6 +1733,45 @@ deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
 	return stmt;
 }
 
+static ObjTree *
+deparse_CreateForeignServerStmt(Oid objectId, Node *parsetree)
+{
+	CreateForeignServerStmt *node = (CreateForeignServerStmt *) parsetree;
+	ObjTree	   *createServer;
+	ObjTree	   *tmp;
+
+	createServer = new_objtree_VA("CREATE SERVER %{identity}I %{type}s %{version}s "
+								  "FOREIGN DATA WRAPPER %{fdw}I %{options}s", 2,
+								  "identity", ObjTypeString, node->servername,
+								  "fdw",  ObjTypeString, node->fdwname);
+
+	/* add a TYPE clause, if any */
+	tmp = new_objtree_VA("TYPE %{type}L", 0);
+	if (node->servertype)
+		append_string_object(tmp, "type", node->servertype);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "type", tmp);
+
+	/* add a VERSION clause, if any */
+	tmp = new_objtree_VA("VERSION %{version}L", 0);
+	if (node->version)
+		append_string_object(tmp, "version", node->version);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "version", tmp);
+
+	/* add an OPTIONS clause, if any */
+	tmp = new_objtree_VA("OPTIONS (%{option:, }s)", 0);
+	if (node->options)
+		elog(ERROR, "unimplemented deparse of CREATE SERVER ... OPTIONS");
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "options", tmp);
+
+	return createServer;
+}
+
 /*
  * deparse_ViewStmt
  *		deparse a ViewStmt
@@ -5455,7 +5494,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateForeignServerStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateForeignServerStmt(objectId, parsetree);
 			break;
 
 		case T_AlterForeignServerStmt:
-- 
2.1.4

0037-deparse-support-REFRESH-MATERIALIZED-VIEW.patchtext/x-diff; charset=us-asciiDownload
>From e6f43c35ae8d705745895e3284a1a83ce965307a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:38:28 -0300
Subject: [PATCH 37/44] deparse: support REFRESH MATERIALIZED VIEW

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 6882261..18fdd10 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2006,6 +2006,31 @@ deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
 	return trigger;
 }
 
+static ObjTree *
+deparse_RefreshMatViewStmt(Oid objectId, Node *parsetree)
+{
+	RefreshMatViewStmt *node = (RefreshMatViewStmt *) parsetree;
+	ObjTree	   *refreshStmt;
+	ObjTree	   *tmp;
+
+	refreshStmt = new_objtree_VA("REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D "
+								 "%{with_no_data}s", 0);
+	/* add a CONCURRENTLY clause */
+	append_string_object(refreshStmt, "concurrently",
+						 node->concurrent ? "CONCURRENTLY" : "");
+	/* add the matview name */
+	append_object_object(refreshStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->skipData ? true : false);
+	append_object_object(refreshStmt, "with_no_data", tmp);
+
+	return refreshStmt;
+}
+
 /*
  * deparse_ColumnDef
  *		Subroutine for CREATE TABLE deparsing
@@ -5564,7 +5589,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RefreshMatViewStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RefreshMatViewStmt(objectId, parsetree);
 			break;
 
 		case T_CreateTrigStmt:
-- 
2.1.4

0038-deparse-Handle-default-security-provider.patchtext/x-diff; charset=us-asciiDownload
>From 0626235c3e281de5a86a3e45f1acc8bc55a437b1 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Sat, 20 Dec 2014 08:56:54 +0100
Subject: [PATCH 38/44] deparse: Handle default security provider.

---
 src/backend/commands/seclabel.c    | 5 +++++
 src/backend/tcop/deparse_utility.c | 2 ++
 2 files changed, 7 insertions(+)

diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 6e15bc8..d74d857 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -61,6 +61,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("must specify provider when multiple security label providers have been loaded")));
 		provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
 	}
 	else
 	{
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 18fdd10..d31cb5b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4581,6 +4581,8 @@ deparse_SecLabelStmt(ObjectAddress address, Node *parsetree)
 	ObjTree	   *label;
 	char	   *fmt;
 
+	Assert(node->provider);
+
 	if (node->label)
 	{
 		fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}L",
-- 
2.1.4

0039-fixup-deparse-infrastructure-needed-for-command-depa.patchtext/x-diff; charset=us-asciiDownload
>From ccbb04379d61cdbf0626f8aba8f13c2cf664de7c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 24 Feb 2015 12:35:49 -0300
Subject: [PATCH 39/44] fixup! deparse: infrastructure needed for command
 deparsing

Change is_array to typarray (per Andres).  Also, fix handling of
timestamp, timestamptz without typmods, and arrays of all those special
types.
---
 src/backend/tcop/deparse_utility.c  |  6 +++---
 src/backend/utils/adt/ddl_json.c    |  4 ++--
 src/backend/utils/adt/format_type.c | 42 ++++++++++++++++++++++++-------------
 3 files changed, 32 insertions(+), 20 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index d31cb5b..e9d0a28 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -549,10 +549,10 @@ new_objtree_for_type(Oid typeId, int32 typmod)
 	char	   *typnsp;
 	char	   *typename;
 	char	   *typmodstr;
-	bool		is_array;
+	bool		typarray;
 
 	format_type_detailed(typeId, typmod,
-						 &typnspid, &typename, &typmodstr, &is_array);
+						 &typnspid, &typename, &typmodstr, &typarray);
 
 	if (!OidIsValid(typnspid))
 		typnsp = pstrdup("");
@@ -566,7 +566,7 @@ new_objtree_for_type(Oid typeId, int32 typmod)
 	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);
+	append_bool_object(typeParam, "typarray", typarray);
 
 	return typeParam;
 }
diff --git a/src/backend/utils/adt/ddl_json.c b/src/backend/utils/adt/ddl_json.c
index 5b0326c..9f6f42e 100644
--- a/src/backend/utils/adt/ddl_json.c
+++ b/src/backend/utils/adt/ddl_json.c
@@ -335,14 +335,14 @@ expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval)
 	typmodstr = find_string_in_jsonbcontainer(jsonval->val.binary.data,
 											  "typmod", true, NULL);
 	is_array = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
-										   "is_array");
+										   "typarray");
 	switch (is_array)
 	{
 		default:
 		case tv_absent:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("missing is_array element")));
+					 errmsg("missing typarray element")));
 			break;
 		case tv_true:
 			array_decor = "[]";
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index dbf6b60..5eb13ff 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -332,25 +332,21 @@ format_type_internal(Oid type_oid, int32 typemod,
  *
  * - nspid is the schema OID.  For certain SQL-standard types which have weird
  *   typmod rules, we return InvalidOid; caller is expected to not schema-
- *   qualify the name nor add quotes to the type name.
+ *   qualify the name nor add quotes to the type name in this case.
  *
  * - 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
+ * - typarray indicates whether []s must be added
  *
- * Also, we don't try to decode type names to their standard-mandated names,
- * except in the cases of unusual typmod rules, as specified above.
- *
- * XXX there is a lot of code duplication between this routine and
- * format_type_internal.  (One thing that doesn't quite match is the whole
- * allow_invalid business.)
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
  */
 void
 format_type_detailed(Oid type_oid, int32 typemod,
 					 Oid *nspid, char **typname, char **typemodstr,
-					 bool *is_array)
+					 bool *typarray)
 {
 	HeapTuple	tuple;
 	Form_pg_type typeform;
@@ -369,14 +365,22 @@ format_type_detailed(Oid type_oid, int32 typemod,
 		type_oid == TIMESTAMPOID ||
 		type_oid == TIMESTAMPTZOID)
 	{
+		*typarray = false;
+
+peculiar_typmod:
 		switch (type_oid)
 		{
 			case INTERVALOID:
 				*typname = pstrdup("INTERVAL");
 				break;
-			case TIMESTAMPOID:
 			case TIMESTAMPTZOID:
-				/* the TZ part is added by typmod */
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmod, so fall through */
+			case TIMESTAMPOID:
 				*typname = pstrdup("TIMESTAMP");
 				break;
 		}
@@ -387,8 +391,6 @@ format_type_detailed(Oid type_oid, int32 typemod,
 		else
 			*typemodstr = pstrdup("");
 
-		*is_array = false;
-
 		ReleaseSysCache(tuple);
 		return;
 	}
@@ -410,10 +412,20 @@ format_type_detailed(Oid type_oid, int32 typemod,
 
 		typeform = (Form_pg_type) GETSTRUCT(tuple);
 		type_oid = array_base_type;
-		*is_array = true;
+		*typarray = true;
+
+		/*
+		 * If it's an array of one of the types with special typmod rules,
+		 * have the element type be processed as above, but now with typarray
+		 * set to true.
+		 */
+		if (type_oid == INTERVALOID ||
+			type_oid == TIMESTAMPTZOID ||
+			type_oid == TIMESTAMPOID)
+			goto peculiar_typmod;
 	}
 	else
-		*is_array = false;
+		*typarray = false;
 
 	*nspid = typeform->typnamespace;
 	*typname = pstrdup(NameStr(typeform->typname));
-- 
2.1.4

0040-fixup-deparse-support-COMMENT-ON.patchtext/x-diff; charset=us-asciiDownload
>From d8bd060c5df7eef6eaa0a2c5f59391270bfdacf3 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 24 Feb 2015 12:50:42 -0300
Subject: [PATCH 40/44] fixup! deparse: support COMMENT ON

simplify format string building

per Andres
---
 src/backend/tcop/deparse_utility.c | 73 ++++++++++++++++++++++----------------
 1 file changed, 42 insertions(+), 31 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index e9d0a28..dda0f2f 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4402,6 +4402,31 @@ deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
 }
 
 /*
+ * Append a NULL-or-quoted-literal clause.  Useful for COMMENT and SECURITY
+ * LABEL.
+ */
+static void
+append_literal_or_null(ObjTree *mainobj, char *elemname, char *value)
+{
+	ObjTree	*top;
+	ObjTree *part;
+
+	top = new_objtree_VA("%{null}s%{literal}s", 0);
+	part = new_objtree_VA("NULL", 1,
+						  "present", ObjTypeBool,
+						  !value);
+	append_object_object(top, "null", part);
+	part = new_objtree_VA("%{value}L", 1,
+						  "present", ObjTypeBool,
+						  !!value);
+	if (value)
+		append_string_object(part, "value", value);
+
+	append_object_object(mainobj, elemname, top);
+}
+
+
+/*
  * Deparse a CommentStmt when it pertains to a constraint.
  */
 static ObjTree *
@@ -4416,35 +4441,24 @@ deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
 
 	Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
 
-	if (node->comment)
-		fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}L",
-					   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
-	else
-		fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS NULL",
-					   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
-	comment = new_objtree_VA(fmt, 0);
-	if (node->comment)
-		append_string_object(comment, "comment", node->comment);
-
 	constrTup = SearchSysCache1(CONSTROID, objectId);
 	if (!HeapTupleIsValid(constrTup))
 		elog(ERROR, "cache lookup failed for constraint %u", objectId);
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
-	append_string_object(comment, "identity", pstrdup(NameStr(constrForm->conname)));
-
 	if (OidIsValid(constrForm->conrelid))
-	{
-		addr.classId = RelationRelationId;
-		addr.objectId = constrForm->conrelid;
-		addr.objectSubId = 0;
-	}
+		ObjectAddressSet(addr, RelationRelationId, constrForm->conrelid);
 	else
-	{
-		addr.classId = TypeRelationId;
-		addr.objectId = constrForm->contypid;
-		addr.objectSubId = 0;
-	}
+		ObjectAddressSet(addr, TypeRelationId, constrForm->contypid);
+
+	fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}s",
+				   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	comment = new_objtree_VA(fmt, 0);
+
+	/* Add the comment clause */
+	append_literal_or_null(comment, "comment", node->comment);
+
+	append_string_object(comment, "identity", pstrdup(NameStr(constrForm->conname)));
 
 	append_string_object(comment, "parentobj",
 						 getObjectIdentity(&addr));
@@ -4472,17 +4486,14 @@ deparse_CommentStmt(ObjectAddress address, Node *parsetree)
 		return deparse_CommentOnConstraintSmt(address.objectId, parsetree);
 	}
 
-	if (node->comment)
-		fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}L",
-					   stringify_objtype(node->objtype));
-	else
-		fmt = psprintf("COMMENT ON %s %%{identity}s IS NULL",
-					   stringify_objtype(node->objtype));
-
+	fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}s",
+				   stringify_objtype(node->objtype));
 	comment = new_objtree_VA(fmt, 0);
-	if (node->comment)
-		append_string_object(comment, "comment", node->comment);
 
+	/* Add the comment clause; can be either NULL or a quoted literal.  */
+	append_literal_or_null(comment, "comment", node->comment);
+
+	/* Add the object identity clause */
 	append_string_object(comment, "identity",
 						 getObjectIdentity(&address));
 
-- 
2.1.4

0041-fixup-deparse-support-SECURITY-LABEL.patchtext/x-diff; charset=us-asciiDownload
>From 33d3329613a55cd4148ac54a7b4717fb44a14c01 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 24 Feb 2015 13:21:35 -0300
Subject: [PATCH 41/44] fixup! deparse: support SECURITY LABEL

use append_literal_or_null from COMMENT
---
 src/backend/tcop/deparse_utility.c | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index dda0f2f..3e2b98b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4594,23 +4594,17 @@ deparse_SecLabelStmt(ObjectAddress address, Node *parsetree)
 
 	Assert(node->provider);
 
-	if (node->label)
-	{
-		fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}L",
+	fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}s",
 				   stringify_objtype(node->objtype));
-		label = new_objtree_VA(fmt, 0);
+	label = new_objtree_VA(fmt, 0);
 
-		append_string_object(label, "label", node->label);
-	}
-	else
-	{
-		fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS NULL",
-				   stringify_objtype(node->objtype));
-		label = new_objtree_VA(fmt, 0);
-	}
+	/* Add the label clause; can be either NULL or a quoted literal. */
+	append_literal_or_null(label, "label", node->label);
 
+	/* Add the security provider clause */
 	append_string_object(label, "provider", node->provider);
 
+	/* Add the object identity clause */
 	append_string_object(label, "identity",
 						 getObjectIdentity(&address));
 
-- 
2.1.4

0042-fixup-deparse-support-ALTER-THING-OWNER-TO.patchtext/x-diff; charset=us-asciiDownload
>From 26e9f2d8461f3d7b958ea7de78503cc2e0dcc29a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 24 Feb 2015 13:24:23 -0300
Subject: [PATCH 42/44] fixup! deparse: support ALTER THING OWNER TO

newname -> newowner

per Andres
---
 src/backend/tcop/deparse_utility.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 3e2b98b..b54dde4 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4386,10 +4386,10 @@ deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
 	ObjectAddress addr;
 	char	   *fmt;
 
-	fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newname}I",
+	fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newowner}I",
 				   stringify_objtype(node->objectType));
 	ownerStmt = new_objtree_VA(fmt, 0);
-	append_string_object(ownerStmt, "newname", node->newowner);
+	append_string_object(ownerStmt, "newowner", node->newowner);
 
 	addr.classId = get_objtype_catalog_oid(node->objectType);
 	addr.objectId = objectId;
-- 
2.1.4

0043-fixup-deparse-Support-CREATE-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From 42c1c18bef87a81ee9029455a7e3f2876f958a79 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 24 Feb 2015 13:25:36 -0300
Subject: [PATCH 43/44] fixup! deparse: Support CREATE OPERATOR FAMILY

quote access method name

per Andres
---
 src/backend/tcop/deparse_utility.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index b54dde4..34ca4dd 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4742,7 +4742,7 @@ deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 			 opfForm->opfmethod);
 	amForm = (Form_pg_am) GETSTRUCT(amTup);
 
-	copfStmt = new_objtree_VA("CREATE OPERATOR FAMILY %{identity}D USING %{amname}s",
+	copfStmt = new_objtree_VA("CREATE OPERATOR FAMILY %{identity}D USING %{amname}I",
 							  0);
 
 	tmp = new_objtree_for_qualname(opfForm->opfnamespace,
-- 
2.1.4

0044-fixup-deparse-Support-for-ALTER-OBJECT-RENAME.patchtext/x-diff; charset=us-asciiDownload
>From e3a1973a82e418b5b646a990b246c12978ccfe1f Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 24 Feb 2015 14:10:05 -0300
Subject: [PATCH 44/44] fixup! deparse: Support for ALTER <OBJECT> RENAME

Use another method to determine a function's identity, per Andres.
---
 src/backend/tcop/deparse_utility.c |  47 +++++++----------
 src/backend/utils/adt/regproc.c    | 101 +++++++++++++++++++++++++++----------
 src/include/utils/builtins.h       |   1 +
 3 files changed, 93 insertions(+), 56 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 34ca4dd..f8d94a4 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3531,39 +3531,28 @@ deparse_RenameStmt(Oid objectId, Node *parsetree)
 		case OBJECT_AGGREGATE:
 		case OBJECT_FUNCTION:
 			{
-				char	   *newident;
-				ObjectAddress objaddr;
-				const char	   *quoted_newname;
-				StringInfoData old_ident;
-				char	   *start;
-
-				/*
-				 * Generating a function/aggregate identity is altogether too
-				 * messy, so instead of doing it ourselves, we generate one for
-				 * the renamed object, then strip out the name and replace it
-				 * with the original name from the parse node.  This is so ugly
-				 * that we don't dare do it for any other object kind.
-				 */
-
-				objaddr.classId = get_objtype_catalog_oid(node->renameType);
-				objaddr.objectId = objectId;
-				objaddr.objectSubId = 0;
-				newident = getObjectIdentity(&objaddr);
-
-				quoted_newname = quote_identifier(node->newname);
-				start = strstr(newident, quoted_newname);
-				if (!start)
-					elog(ERROR, "could not find %s in %s", start, newident);
-				initStringInfo(&old_ident);
-				appendBinaryStringInfo(&old_ident, newident, start - newident);
-				appendStringInfoString(&old_ident,
-									   quote_identifier(strVal(llast(node->object))));
-				appendStringInfoString(&old_ident, start + strlen(quoted_newname));
+				char	*ident;
+				HeapTuple proctup;
+				Form_pg_proc procform;
+
+				proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+				if (!HeapTupleIsValid(proctup))
+					elog(ERROR, "cache lookup failed for procedure %u",
+						 objectId);
+				procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+				/* XXX does this work for ordered-set aggregates? */
+				ident = psprintf("%s%s",
+								 quote_qualified_identifier(get_namespace_name(procform->pronamespace),
+															NameStr(procform->proname)),
+								 format_procedure_args(objectId, true));
 
 				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
 								  stringify_objtype(node->renameType));
 				renameStmt = new_objtree_VA(fmtstr, 1,
-											"identity", ObjTypeString, old_ident.data);
+											"identity", ObjTypeString, ident);
+
+				ReleaseSysCache(proctup);
 			}
 			break;
 
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 3d1bb32..68447ca 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -42,7 +42,11 @@
 #include "utils/tqual.h"
 
 static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
+static char *format_procedure_internal(Oid procedure_oid, bool force_qualify,
+						  bool args_only);
+static void format_procedure_args_internal(Form_pg_proc procform,
+							   StringInfo buf, bool force_qualify);
+
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -363,13 +367,36 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_internal(procedure_oid, false, false);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_internal(procedure_oid, true, false);
+}
+
+/*
+ * format_procedure_args	- converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+	StringInfoData buf;
+	HeapTuple	proctup;
+	Form_pg_proc procform;
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+	initStringInfo(&buf);
+	format_procedure_args_internal(procform, &buf, force_qualify);
+
+	ReleaseSysCache(proctup);
+
+	return buf.data;
 }
 
 /*
@@ -380,7 +407,7 @@ format_procedure_qualified(Oid procedure_oid)
  * qualified if the function is not in path.
  */
 static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+format_procedure_internal(Oid procedure_oid, bool force_qualify, bool args_only)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -391,8 +418,6 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 	{
 		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
 		char	   *proname = NameStr(procform->proname);
-		int			nargs = procform->pronargs;
-		int			i;
 		char	   *nspname;
 		StringInfoData buf;
 
@@ -400,29 +425,24 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		initStringInfo(&buf);
 
-		/*
-		 * Would this proc be found (given the right args) by regprocedurein?
-		 * If not, or if caller requests it, we need to qualify it.
-		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
-			nspname = NULL;
-		else
-			nspname = get_namespace_name(procform->pronamespace);
-
-		appendStringInfo(&buf, "%s(",
-						 quote_qualified_identifier(nspname, proname));
-		for (i = 0; i < nargs; i++)
+		if (!args_only)
 		{
-			Oid			thisargtype = procform->proargtypes.values[i];
-
-			if (i > 0)
-				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf,
-								   force_qualify ?
-								   format_type_be_qualified(thisargtype) :
-								   format_type_be(thisargtype));
+			/*
+			 * Would this proc be found (given the right args) by
+			 * regprocedurein?  If not, or if caller requests it, we need to
+			 * qualify it.
+			 */
+			if (!force_qualify && FunctionIsVisible(procedure_oid))
+				nspname = NULL;
+			else
+				nspname = get_namespace_name(procform->pronamespace);
+
+			appendStringInfo(&buf, "%s",
+							 quote_qualified_identifier(nspname, proname));
 		}
-		appendStringInfoChar(&buf, ')');
+
+		/* add the attributes */
+		format_procedure_args_internal(procform, &buf, force_qualify);
 
 		result = buf.data;
 
@@ -439,6 +459,33 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 }
 
 /*
+ * Append the parenthised arguments of the given pg_proc row into the output
+ * buffer.  force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+							   bool force_qualify)
+{
+	int			i;
+	int			nargs = procform->pronargs;
+
+	appendStringInfoChar(buf, '(');
+	for (i = 0; i < nargs; i++)
+	{
+		Oid			thisargtype = procform->proargtypes.values[i];
+
+		if (i > 0)
+			appendStringInfoChar(buf, ',');
+		appendStringInfoString(buf,
+							   force_qualify ?
+							   format_type_be_qualified(thisargtype) :
+							   format_type_be(thisargtype));
+	}
+	appendStringInfoChar(buf, ')');
+}
+
+/*
  * Output a objname/objargs representation for the procedure with the
  * given OID.  If it doesn't exist, an error is thrown.
  *
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 470a325..dd539fe 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -644,6 +644,7 @@ extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
 					  List **objargs);
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-- 
2.1.4

#27Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#23)
Re: deparsing utility commands

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Stephen Frost wrote:

Regardless, as I've tried to point out above, the
changes which I'm actually suggesting for this initial body of work are
just to avoid the parsetree and go based off of what the catalog has.
I'm hopeful that's a small enough and reasonable enough change to happen
during this commitfest. I don't have any issue tabling the rest of the
discussion and future work based on that to a later time.

At some point I did consider the idea that the CREATE cases of the
deparse code could be usable without a parse tree. For the specific
case of deparsing a DDL command as it's being ran, it's important to
have the parse tree: options such as IF EXISTS, OR REPLACE and others
don't live in the catalogs and so they must be obtained from the parse
tree. I think it is possible to adjust some of the code so that it can
run with a NULL parse tree, and set the options to empty in those cases;
that should return a usable command to re-create the object.

Right, being able to run it with a NULL parse tree would absolutely work
for the use-cases that I'm thinking of. I suggested somewhere up-thread
that those bits of information (IF EXISTS, OR REPLACE) might be possible
to pass in as independent arguments or through a compound argument (eg:
a List or struct or something), instead of coming from the parse tree,
but that's clearly something which could be done later.

One point to bear in mind is that there is still some work left; and if
I need to additionally add extra interfaces so that this code can be
called from outside event triggers, I will not have any time to review
other people's patches from the commitfest. So here's my suggestion:
let this be pushed with the current interfaces only, with an eye towards
not adding obstacles to future support for a different interface.
That's more in the spirit of incremental improvement than trying to cram
everything in the initial commit.

I'm perfectly fine with having the initial commit of this work using the
existing interfaces and further that we only need one set of interfaces
for 9.5, but, for my part, I'd love to be able to build an extension on
9.5 which calls these functions and passes in a NULL parsetree. Perhaps
that's wishful thinking at this part and maybe I'll have to wait til
9.6, but that's why I've brought this up.

I'm thinking something like
SELECT * FROM pg_creation_commands({'pg_class'::regclass, 'sometable'::pg_class});
would return a set of commands in the JSON-blob format that creates the
table. The input argument is an array of object addresses, so that you
can request creation commands for multiple objects. (It's not my
intention to provide this functionality right away, but if somebody else
wants to work on top of the current deparse patch, by my guest; if it
proves simple enough we can still get it into 9.5 as part of this
patch.)

I'm all for this, but it seems too late for it to get into 9.5. I'd be
happy with just the functions existing and available to extensions for
9.5.

Thanks!

Stephen

#28Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#9)
7 attachment(s)
Re: deparsing utility commands

Stephen Frost wrote:

and at the bottom of the loop we would transform the objid/type into
address for the cases that need it:

if (!commandStashed)
{
if (objectId != InvalidOid)
{
address.classId = get_objtype_catalog_oid(objectType);
address.objectId = objectId;
address.objectSubId = 0;
}
EventTriggerStashCommand(address, secondaryOid, parsetree);
}

That'd be fine with me, though for my 2c, I wouldn't object to changing
them all to return ObjectAddress either. I agree that it'd cause a fair
bit of code churn to do so, but there's a fair bit of code churn
happening here anyway (looking at what 0008 does to ProcessUtilitySlow
anyway).

Thanks. I have adjusted the code to use ObjectAddress in all functions
called by ProcessUtilitySlow; the patch is a bit tedious but seems
pretty reasonable to me. As I said, there is quite a lot of churn.

Also attached are some patches to make some commands return info about
a secondary object regarding the execution, which can be used to know
more about what's being executed:

1) ALTER DOMAIN ADD CONSTRAINT returns (in addition to the address of
the doamin) the address of the new constraint.

2) ALTER OBJECT SET SCHEMA returns (in addition to the address of the
object) the address of the old schema.

3) ALTER EXTENSION ADD/DROP OBJECT returns (in addition to the address
of the extension) the address of the added/dropped object.

Also attached is the part of these patches that have ALTER TABLE return
the AttrNumber and OID of the affected object.

I think this is all mostly uninteresting, but thought I'd throw it out
for public review before pushing, just in case. I have fixed many
issues in the rest of the branch meanwhile, and it's looking much better
IMO, but much work remains there and will leave them for later.

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

Attachments:

0001-Have-RENAME-routines-return-ObjectAddress-rather-tha.patchtext/x-diff; charset=us-asciiDownload
>From f67ba2157ed1e9d3a8c09812895c6a290e0176f8 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Feb 2015 17:24:04 -0300
Subject: [PATCH 1/7] Have RENAME routines return ObjectAddress rather than OID

This lets them include an objectSubId when appropriate (i.e. when
renaming columns), and additionally they make the containing catalog OID
available for further processing.  The object OID that was previously
returned is still available in the objectId field of the returned
ObjectAddress.

This isn't terribly exciting in itself but it supports future event
trigger changes.

Discussion: 20150218213255.GC6717@tamriel.snowman.net
Reviewed-By: Andres Freund, Stephen Frost
---
 src/backend/catalog/objectaddress.c |  6 ++++
 src/backend/commands/alter.c        |  8 ++++--
 src/backend/commands/dbcommands.c   |  7 +++--
 src/backend/commands/policy.c       |  7 +++--
 src/backend/commands/schemacmds.c   |  7 +++--
 src/backend/commands/tablecmds.c    | 55 +++++++++++++++++++++++--------------
 src/backend/commands/tablespace.c   |  7 +++--
 src/backend/commands/trigger.c      |  7 +++--
 src/backend/commands/typecmds.c     |  6 ++--
 src/backend/commands/user.c         |  7 +++--
 src/backend/rewrite/rewriteDefine.c |  7 +++--
 src/include/catalog/objectaddress.h | 12 ++++++++
 src/include/commands/alter.h        |  3 +-
 src/include/commands/dbcommands.h   |  3 +-
 src/include/commands/policy.h       |  3 +-
 src/include/commands/schemacmds.h   |  3 +-
 src/include/commands/tablecmds.h    |  9 ++++--
 src/include/commands/tablespace.h   |  3 +-
 src/include/commands/trigger.h      |  3 +-
 src/include/commands/typecmds.h     |  2 +-
 src/include/commands/user.h         |  3 +-
 src/include/rewrite/rewriteDefine.h |  3 +-
 22 files changed, 120 insertions(+), 51 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d899dd7..70043fc 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -531,6 +531,12 @@ ObjectTypeMap[] =
 	{ "policy", OBJECT_POLICY }
 };
 
+const ObjectAddress InvalidObjectAddress =
+{
+	InvalidOid,
+	InvalidOid,
+	0
+};
 
 static ObjectAddress get_object_address_unqualified(ObjectType objtype,
 							   List *qualname, bool missing_ok);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 78b54b4..361d918 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -299,8 +299,10 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 /*
  * Executes an ALTER OBJECT / RENAME TO statement.  Based on the object
  * type, the function appropriate to that type is executed.
+ *
+ * Return value is the address of the renamed object.
  */
-Oid
+ObjectAddress
 ExecRenameStmt(RenameStmt *stmt)
 {
 	switch (stmt->renameType)
@@ -378,13 +380,13 @@ ExecRenameStmt(RenameStmt *stmt)
 										   stmt->newname);
 				heap_close(catalog, RowExclusiveLock);
 
-				return address.objectId;
+				return address;
 			}
 
 		default:
 			elog(ERROR, "unrecognized rename stmt type: %d",
 				 (int) stmt->renameType);
-			return InvalidOid;	/* keep compiler happy */
+			return InvalidObjectAddress;	/* keep compiler happy */
 	}
 }
 
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 5e66961..9747dd0 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -938,7 +938,7 @@ dropdb(const char *dbname, bool missing_ok)
 /*
  * Rename database
  */
-Oid
+ObjectAddress
 RenameDatabase(const char *oldname, const char *newname)
 {
 	Oid			db_id;
@@ -946,6 +946,7 @@ RenameDatabase(const char *oldname, const char *newname)
 	Relation	rel;
 	int			notherbackends;
 	int			npreparedxacts;
+	ObjectAddress address;
 
 	/*
 	 * Look up the target database's OID, and get exclusive lock on it. We
@@ -1013,12 +1014,14 @@ RenameDatabase(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(DatabaseRelationId, db_id, 0);
 
+	ObjectAddressSet(address, DatabaseRelationId, db_id);
+
 	/*
 	 * Close pg_database, but keep lock till commit.
 	 */
 	heap_close(rel, NoLock);
 
-	return db_id;
+	return address;
 }
 
 
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d98da0d..68bad97 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -837,7 +837,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
  * rename_policy -
  *   change the name of a policy on a relation
  */
-Oid
+ObjectAddress
 rename_policy(RenameStmt *stmt)
 {
 	Relation		pg_policy_rel;
@@ -847,6 +847,7 @@ rename_policy(RenameStmt *stmt)
 	ScanKeyData		skey[2];
 	SysScanDesc		sscan;
 	HeapTuple		policy_tuple;
+	ObjectAddress	address;
 
 	/* Get id of table.  Also handles permissions checks. */
 	table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
@@ -925,6 +926,8 @@ rename_policy(RenameStmt *stmt)
 	InvokeObjectPostAlterHook(PolicyRelationId,
 							  HeapTupleGetOid(policy_tuple), 0);
 
+	ObjectAddressSet(address, PolicyRelationId, opoloid);
+
 	/*
 	 * Invalidate relation's relcache entry so that other backends (and
 	 * this one too!) are sent SI message to make them rebuild relcache
@@ -937,7 +940,7 @@ rename_policy(RenameStmt *stmt)
 	heap_close(pg_policy_rel, RowExclusiveLock);
 	relation_close(target_table, NoLock);
 
-	return opoloid;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index a44dbf4..77b5e21 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -195,13 +195,14 @@ RemoveSchemaById(Oid schemaOid)
 /*
  * Rename schema
  */
-Oid
+ObjectAddress
 RenameSchema(const char *oldname, const char *newname)
 {
 	Oid			nspOid;
 	HeapTuple	tup;
 	Relation	rel;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	rel = heap_open(NamespaceRelationId, RowExclusiveLock);
 
@@ -243,10 +244,12 @@ RenameSchema(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(NamespaceRelationId, HeapTupleGetOid(tup), 0);
 
+	ObjectAddressSet(address, NamespaceRelationId, nspOid);
+
 	heap_close(rel, NoLock);
 	heap_freetuple(tup);
 
-	return nspOid;
+	return address;
 }
 
 void
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 07ab4b4..b7ddda9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2158,8 +2158,10 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 
 /*
  *		renameatt_internal		- workhorse for renameatt
+ *
+ * Return value is the attribute number in the 'myrelid' relation.
  */
-static void
+static AttrNumber
 renameatt_internal(Oid myrelid,
 				   const char *oldattname,
 				   const char *newattname,
@@ -2172,7 +2174,7 @@ renameatt_internal(Oid myrelid,
 	Relation	attrelation;
 	HeapTuple	atttup;
 	Form_pg_attribute attform;
-	int			attnum;
+	AttrNumber	attnum;
 
 	/*
 	 * Grab an exclusive lock on the target table, which we will NOT release
@@ -2300,6 +2302,8 @@ renameatt_internal(Oid myrelid,
 	heap_close(attrelation, RowExclusiveLock);
 
 	relation_close(targetrelation, NoLock);		/* close rel but keep lock */
+
+	return attnum;
 }
 
 /*
@@ -2322,11 +2326,15 @@ RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 /*
  *		renameatt		- changes the name of a attribute in a relation
+ *
+ * The returned ObjectAddress is that of the pg_class address of the column.
  */
-Oid
+ObjectAddress
 renameatt(RenameStmt *stmt)
 {
 	Oid			relid;
+	AttrNumber	attnum;
+	ObjectAddress address;
 
 	/* lock level taken here should match renameatt_internal */
 	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
@@ -2339,26 +2347,27 @@ renameatt(RenameStmt *stmt)
 		ereport(NOTICE,
 				(errmsg("relation \"%s\" does not exist, skipping",
 						stmt->relation->relname)));
-		return InvalidOid;
+		return InvalidObjectAddress;
 	}
 
-	renameatt_internal(relid,
-					   stmt->subname,	/* old att name */
-					   stmt->newname,	/* new att name */
-					   interpretInhOption(stmt->relation->inhOpt),		/* recursive? */
-					   false,	/* recursing? */
-					   0,		/* expected inhcount */
-					   stmt->behavior);
+	attnum =
+		renameatt_internal(relid,
+						   stmt->subname,	/* old att name */
+						   stmt->newname,	/* new att name */
+						   interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+						   false,	/* recursing? */
+						   0,		/* expected inhcount */
+						   stmt->behavior);
 
-	/* This is an ALTER TABLE command so it's about the relid */
-	return relid;
-}
+	ObjectAddressSubSet(address, RelationRelationId, relid, attnum);
 
+	return address;
+}
 
 /*
  * same logic as renameatt_internal
  */
-static Oid
+static ObjectAddress
 rename_constraint_internal(Oid myrelid,
 						   Oid mytypid,
 						   const char *oldconname,
@@ -2371,6 +2380,7 @@ rename_constraint_internal(Oid myrelid,
 	Oid			constraintOid;
 	HeapTuple	tuple;
 	Form_pg_constraint con;
+	ObjectAddress	address;
 
 	AssertArg(!myrelid || !mytypid);
 
@@ -2446,15 +2456,17 @@ rename_constraint_internal(Oid myrelid,
 	else
 		RenameConstraintById(constraintOid, newconname);
 
+	ObjectAddressSet(address, ConstraintRelationId, constraintOid);
+
 	ReleaseSysCache(tuple);
 
 	if (targetrelation)
 		relation_close(targetrelation, NoLock); /* close rel but keep lock */
 
-	return constraintOid;
+	return address;
 }
 
-Oid
+ObjectAddress
 RenameConstraint(RenameStmt *stmt)
 {
 	Oid			relid = InvalidOid;
@@ -2497,10 +2509,11 @@ RenameConstraint(RenameStmt *stmt)
  * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE
  * RENAME
  */
-Oid
+ObjectAddress
 RenameRelation(RenameStmt *stmt)
 {
 	Oid			relid;
+	ObjectAddress address;
 
 	/*
 	 * Grab an exclusive lock on the target table, index, sequence, view,
@@ -2520,13 +2533,15 @@ RenameRelation(RenameStmt *stmt)
 		ereport(NOTICE,
 				(errmsg("relation \"%s\" does not exist, skipping",
 						stmt->relation->relname)));
-		return InvalidOid;
+		return InvalidObjectAddress;
 	}
 
 	/* Do the work */
 	RenameRelationInternal(relid, stmt->newname, false);
 
-	return relid;
+	ObjectAddressSet(address, RelationRelationId, relid);
+
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 03cc8fe..68b6917 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -846,7 +846,7 @@ directory_is_empty(const char *path)
 /*
  * Rename a tablespace
  */
-Oid
+ObjectAddress
 RenameTableSpace(const char *oldname, const char *newname)
 {
 	Oid			tspId;
@@ -856,6 +856,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	HeapTuple	tup;
 	HeapTuple	newtuple;
 	Form_pg_tablespace newform;
+	ObjectAddress address;
 
 	/* Search pg_tablespace */
 	rel = heap_open(TableSpaceRelationId, RowExclusiveLock);
@@ -912,9 +913,11 @@ RenameTableSpace(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(TableSpaceRelationId, tspId, 0);
 
+	ObjectAddressSet(address, TableSpaceRelationId, tspId);
+
 	heap_close(rel, NoLock);
 
-	return tspId;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index a84e86e..041ae8d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1249,7 +1249,7 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
  *		modify tgname in trigger tuple
  *		update row in catalog
  */
-Oid
+ObjectAddress
 renametrig(RenameStmt *stmt)
 {
 	Oid			tgoid;
@@ -1259,6 +1259,7 @@ renametrig(RenameStmt *stmt)
 	SysScanDesc tgscan;
 	ScanKeyData key[2];
 	Oid			relid;
+	ObjectAddress address;
 
 	/*
 	 * Look up name, check permissions, and acquire lock (which we will NOT
@@ -1351,6 +1352,8 @@ renametrig(RenameStmt *stmt)
 						stmt->subname, RelationGetRelationName(targetrel))));
 	}
 
+	ObjectAddressSet(address, TriggerRelationId, tgoid);
+
 	systable_endscan(tgscan);
 
 	heap_close(tgrel, RowExclusiveLock);
@@ -1360,7 +1363,7 @@ renametrig(RenameStmt *stmt)
 	 */
 	relation_close(targetrel, NoLock);
 
-	return tgoid;
+	return address;
 }
 
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index b77e1b4..b02abfe 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3205,7 +3205,7 @@ GetDomainConstraints(Oid typeOid)
 /*
  * Execute ALTER TYPE RENAME
  */
-Oid
+ObjectAddress
 RenameType(RenameStmt *stmt)
 {
 	List	   *names = stmt->object;
@@ -3215,6 +3215,7 @@ RenameType(RenameStmt *stmt)
 	Relation	rel;
 	HeapTuple	tup;
 	Form_pg_type typTup;
+	ObjectAddress address;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -3272,10 +3273,11 @@ RenameType(RenameStmt *stmt)
 		RenameTypeInternal(typeOid, newTypeName,
 						   typTup->typnamespace);
 
+	ObjectAddressSet(address, TypeRelationId, typeOid);
 	/* Clean up */
 	heap_close(rel, RowExclusiveLock);
 
-	return typeOid;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 2210eed..0d30838 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1114,7 +1114,7 @@ DropRole(DropRoleStmt *stmt)
 /*
  * Rename role
  */
-Oid
+ObjectAddress
 RenameRole(const char *oldname, const char *newname)
 {
 	HeapTuple	oldtuple,
@@ -1128,6 +1128,7 @@ RenameRole(const char *oldname, const char *newname)
 	bool		repl_repl[Natts_pg_authid];
 	int			i;
 	Oid			roleid;
+	ObjectAddress address;
 
 	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
 	dsc = RelationGetDescr(rel);
@@ -1216,6 +1217,8 @@ RenameRole(const char *oldname, const char *newname)
 
 	InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
 
+	ObjectAddressSet(address, AuthIdRelationId, roleid);
+
 	ReleaseSysCache(oldtuple);
 
 	/*
@@ -1223,7 +1226,7 @@ RenameRole(const char *oldname, const char *newname)
 	 */
 	heap_close(rel, NoLock);
 
-	return roleid;
+	return address;
 }
 
 /*
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index eab4d73..75b6d80 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -897,7 +897,7 @@ RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
 /*
  * Rename an existing rewrite rule.
  */
-Oid
+ObjectAddress
 RenameRewriteRule(RangeVar *relation, const char *oldName,
 				  const char *newName)
 {
@@ -907,6 +907,7 @@ RenameRewriteRule(RangeVar *relation, const char *oldName,
 	HeapTuple	ruletup;
 	Form_pg_rewrite ruleform;
 	Oid			ruleOid;
+	ObjectAddress address;
 
 	/*
 	 * Look up name, check permissions, and acquire lock (which we will NOT
@@ -969,10 +970,12 @@ RenameRewriteRule(RangeVar *relation, const char *oldName,
 	 */
 	CacheInvalidateRelcache(targetrel);
 
+	ObjectAddressSet(address, RewriteRelationId, ruleOid);
+
 	/*
 	 * Close rel, but keep exclusive lock!
 	 */
 	relation_close(targetrel, NoLock);
 
-	return ruleOid;
+	return address;
 }
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6f4dbab..619b2f5 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -28,6 +28,18 @@ typedef struct ObjectAddress
 	int32		objectSubId;	/* Subitem within object (eg column), or 0 */
 } ObjectAddress;
 
+extern const ObjectAddress InvalidObjectAddress;
+
+#define ObjectAddressSubSet(addr, class_id, object_id, object_sub_id) \
+	do { \
+		(addr).classId = (class_id); \
+		(addr).objectId = (object_id); \
+		(addr).objectSubId = (object_sub_id); \
+	} while (0)
+
+#define ObjectAddressSet(addr, class_id, object_id) \
+	ObjectAddressSubSet(addr, class_id, object_id, 0)
+
 extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
 				   List *objargs, Relation *relp,
 				   LOCKMODE lockmode, bool missing_ok);
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index 14d24f1..5df28d3 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -15,10 +15,11 @@
 #define ALTER_H
 
 #include "catalog/dependency.h"
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "utils/relcache.h"
 
-extern Oid	ExecRenameStmt(RenameStmt *stmt);
+extern ObjectAddress ExecRenameStmt(RenameStmt *stmt);
 
 extern Oid	ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
 extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 4b60cdb..42bc5fa 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -15,6 +15,7 @@
 #define DBCOMMANDS_H
 
 #include "access/xlogreader.h"
+#include "catalog/objectaddress.h"
 #include "lib/stringinfo.h"
 #include "nodes/parsenodes.h"
 
@@ -40,7 +41,7 @@ typedef struct xl_dbase_drop_rec
 
 extern Oid	createdb(const CreatedbStmt *stmt);
 extern void dropdb(const char *dbname, bool missing_ok);
-extern Oid	RenameDatabase(const char *oldname, const char *newname);
+extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
 extern Oid	AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel);
 extern Oid	AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
 extern Oid	AlterDatabaseOwner(const char *dbname, Oid newOwnerId);
diff --git a/src/include/commands/policy.h b/src/include/commands/policy.h
index e911fcc..0848a18 100644
--- a/src/include/commands/policy.h
+++ b/src/include/commands/policy.h
@@ -15,6 +15,7 @@
 #ifndef POLICY_H
 #define POLICY_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "utils/relcache.h"
 
@@ -28,7 +29,7 @@ extern Oid AlterPolicy(AlterPolicyStmt *stmt);
 extern Oid get_relation_policy_oid(Oid relid, const char *policy_name,
 						bool missing_ok);
 
-extern Oid rename_policy(RenameStmt *stmt);
+extern ObjectAddress rename_policy(RenameStmt *stmt);
 
 
 #endif   /* POLICY_H */
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index d08fdd4..1c0f3ca 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -15,6 +15,7 @@
 #ifndef SCHEMACMDS_H
 #define SCHEMACMDS_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
 extern Oid CreateSchemaCommand(CreateSchemaStmt *parsetree,
@@ -22,7 +23,7 @@ extern Oid CreateSchemaCommand(CreateSchemaStmt *parsetree,
 
 extern void RemoveSchemaById(Oid schemaOid);
 
-extern Oid	RenameSchema(const char *oldname, const char *newname);
+extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern Oid	AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaOid, Oid newOwnerId);
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index a55e8d4..f529337 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -16,6 +16,7 @@
 
 #include "access/htup.h"
 #include "catalog/dependency.h"
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
@@ -53,11 +54,13 @@ extern void ExecuteTruncate(TruncateStmt *stmt);
 
 extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass);
 
-extern Oid	renameatt(RenameStmt *stmt);
+extern ObjectAddress renameatt(RenameStmt *stmt);
 
-extern Oid	RenameConstraint(RenameStmt *stmt);
+extern ObjectAddress renameatt_type(RenameStmt *stmt);
 
-extern Oid	RenameRelation(RenameStmt *stmt);
+extern ObjectAddress RenameConstraint(RenameStmt *stmt);
+
+extern ObjectAddress RenameRelation(RenameStmt *stmt);
 
 extern void RenameRelationInternal(Oid myrelid,
 					   const char *newrelname, bool is_internal);
diff --git a/src/include/commands/tablespace.h b/src/include/commands/tablespace.h
index 70734d6..86b0477 100644
--- a/src/include/commands/tablespace.h
+++ b/src/include/commands/tablespace.h
@@ -15,6 +15,7 @@
 #define TABLESPACE_H
 
 #include "access/xlogreader.h"
+#include "catalog/objectaddress.h"
 #include "lib/stringinfo.h"
 #include "nodes/parsenodes.h"
 
@@ -42,7 +43,7 @@ typedef struct TableSpaceOpts
 
 extern Oid	CreateTableSpace(CreateTableSpaceStmt *stmt);
 extern void DropTableSpace(DropTableSpaceStmt *stmt);
-extern Oid	RenameTableSpace(const char *oldname, const char *newname);
+extern ObjectAddress RenameTableSpace(const char *oldname, const char *newname);
 extern Oid	AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt);
 
 extern void TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo);
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index d0c0dcc..e09ac29 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -13,6 +13,7 @@
 #ifndef TRIGGER_H
 #define TRIGGER_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 
@@ -115,7 +116,7 @@ extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 extern void RemoveTriggerById(Oid trigOid);
 extern Oid	get_trigger_oid(Oid relid, const char *name, bool missing_ok);
 
-extern Oid	renametrig(RenameStmt *stmt);
+extern ObjectAddress renametrig(RenameStmt *stmt);
 
 extern void EnableDisableTrigger(Relation rel, const char *tgname,
 					 char fires_when, bool skip_system);
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index e18a714..daeabfe 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -41,7 +41,7 @@ extern void checkDomainOwner(HeapTuple tup);
 
 extern List *GetDomainConstraints(Oid typeOid);
 
-extern Oid	RenameType(RenameStmt *stmt);
+extern ObjectAddress RenameType(RenameStmt *stmt);
 extern Oid	AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype);
 extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
 					   bool hasDependEntry);
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d766851..ccadb04 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -11,6 +11,7 @@
 #ifndef USER_H
 #define USER_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
 
@@ -27,7 +28,7 @@ extern Oid	AlterRole(AlterRoleStmt *stmt);
 extern Oid	AlterRoleSet(AlterRoleSetStmt *stmt);
 extern void DropRole(DropRoleStmt *stmt);
 extern void GrantRole(GrantRoleStmt *stmt);
-extern Oid	RenameRole(const char *oldname, const char *newname);
+extern ObjectAddress RenameRole(const char *oldname, const char *newname);
 extern void DropOwnedObjects(DropOwnedStmt *stmt);
 extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
 extern List *roleNamesToIds(List *memberNames);
diff --git a/src/include/rewrite/rewriteDefine.h b/src/include/rewrite/rewriteDefine.h
index d384552..c112b85 100644
--- a/src/include/rewrite/rewriteDefine.h
+++ b/src/include/rewrite/rewriteDefine.h
@@ -14,6 +14,7 @@
 #ifndef REWRITEDEFINE_H
 #define REWRITEDEFINE_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "utils/relcache.h"
 
@@ -32,7 +33,7 @@ extern Oid DefineQueryRewrite(char *rulename,
 				   bool replace,
 				   List *action);
 
-extern Oid RenameRewriteRule(RangeVar *relation, const char *oldName,
+extern ObjectAddress RenameRewriteRule(RangeVar *relation, const char *oldName,
 				  const char *newName);
 
 extern void setRuleCheckAsUser(Node *node, Oid userid);
-- 
2.1.4

0002-deparse-core-have-COMMENT-return-an-ObjectAddress-no.patchtext/x-diff; charset=us-asciiDownload
>From 338eaac959d46551d14701151dce6beeb49af54a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 19 Feb 2015 16:29:57 -0300
Subject: [PATCH 2/7] deparse/core: have COMMENT return an ObjectAddress, not
 OID

---
 src/backend/commands/comment.c | 8 ++++----
 src/include/commands/comment.h | 3 ++-
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index ed1e46e..6d8c006 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -36,11 +36,11 @@
  * This routine is used to add the associated comment into
  * pg_description for the object specified by the given SQL command.
  */
-Oid
+ObjectAddress
 CommentObject(CommentStmt *stmt)
 {
-	ObjectAddress address;
 	Relation	relation;
+	ObjectAddress address = InvalidObjectAddress;
 
 	/*
 	 * When loading a dump, we may see a COMMENT ON DATABASE for the old name
@@ -60,7 +60,7 @@ CommentObject(CommentStmt *stmt)
 			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_DATABASE),
 					 errmsg("database \"%s\" does not exist", database)));
-			return InvalidOid;
+			return address;
 		}
 	}
 
@@ -126,7 +126,7 @@ CommentObject(CommentStmt *stmt)
 	if (relation != NULL)
 		relation_close(relation, NoLock);
 
-	return address.objectId;
+	return address;
 }
 
 /*
diff --git a/src/include/commands/comment.h b/src/include/commands/comment.h
index 3d61b44..990d362 100644
--- a/src/include/commands/comment.h
+++ b/src/include/commands/comment.h
@@ -15,6 +15,7 @@
 #ifndef COMMENT_H
 #define COMMENT_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
 /*------------------------------------------------------------------
@@ -29,7 +30,7 @@
  *------------------------------------------------------------------
  */
 
-extern Oid	CommentObject(CommentStmt *stmt);
+extern ObjectAddress CommentObject(CommentStmt *stmt);
 
 extern void DeleteComments(Oid oid, Oid classoid, int32 subid);
 
-- 
2.1.4

0003-deparse-core-have-ALTER-TABLE-return-table-OID-and-o.patchtext/x-diff; charset=us-asciiDownload
>From 07e1bacc01bb0a97a094722f947187332d775a40 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 3/7] deparse/core: have ALTER TABLE return table OID and other
 data

Currently, most if not all CREATE commands return the OID of the created
object.  This is useful for event triggers to try to figure out exactly
what happened.  This patch extends this idea a bit further to cover
ALTER TABLE as well: auxilliary info such as a second OID or a column
number are also returned for each subcommand of the ALTER TABLE.  This
OID or attnum can later be used to determine, for instance, which
constraint was added by ALTER TABLE ADD CONSTRAINT, or which parent got
dropped from the inherit-from parents list by NO INHERIT.  This makes it
possible to decode with precision exactly what happened during execution
of any ALTER TABLE command.

There is no user-visible change here; this patch makes the info
available for later.
---
 src/backend/catalog/heap.c          |  40 +++--
 src/backend/catalog/index.c         |   7 +-
 src/backend/catalog/pg_constraint.c |   2 +
 src/backend/commands/tablecmds.c    | 307 ++++++++++++++++++++++++------------
 src/include/catalog/heap.h          |   3 +-
 src/include/catalog/index.h         |   2 +-
 6 files changed, 244 insertions(+), 117 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 17f7266..0247da6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -97,7 +97,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,
@@ -1844,7 +1844,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)
 {
@@ -1949,6 +1949,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	 */
 	InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
 								  RelationGetRelid(rel), attnum, is_internal);
+
+	return attrdefOid;
 }
 
 /*
@@ -1956,8 +1958,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)
@@ -1967,6 +1971,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
+	Oid			constrOid;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2018,7 +2023,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 */
@@ -2049,11 +2054,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
@@ -2065,7 +2074,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 */
 
 	/*
@@ -2082,12 +2091,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:
@@ -2175,6 +2186,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,
@@ -2195,10 +2207,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;
@@ -2218,6 +2231,7 @@ AddRelationNewConstraints(Relation rel,
 	{
 		Constraint *cdef = (Constraint *) lfirst(cell);
 		char	   *ccname;
+		Oid			constrOid;
 
 		if (cdef->contype != CONSTR_CHECK)
 			continue;
@@ -2327,13 +2341,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 f85ed93..5773298 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1111,7 +1111,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
@@ -1128,7 +1129,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,
@@ -1316,6 +1317,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 77fe918..3c756f8 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 b7ddda9..8b40ddc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -282,9 +282,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);
@@ -326,26 +326,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,
 				bool is_view, 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);
@@ -353,20 +353,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,
@@ -377,9 +377,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);
@@ -392,7 +392,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,
+static Oid ATExecClusterOn(Relation rel, const char *indexName,
 				LOCKMODE lockmode);
 static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
 static bool ATPrepChangePersistence(Relation rel, bool toLogged);
@@ -407,10 +407,10 @@ static void ATExecEnableDisableTrigger(Relation rel, char *trigname,
 static void ATExecEnableDisableRule(Relation rel, char *rulename,
 						char fires_when, LOCKMODE lockmode);
 static void ATPrepAddInherit(Relation child_rel);
-static void ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
-static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
+static Oid ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
+static Oid ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
-static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
+static Oid ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
 static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
 static void ATExecGenericOptions(Relation rel, List *options);
@@ -620,6 +620,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;
@@ -1735,6 +1736,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;
@@ -3410,78 +3412,95 @@ static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
+	AttrNumber	colno = InvalidAttrNumber;
+	Oid			newoid = InvalidOid;
+
+	/* temporary measure to silence unused-variable compiler warnings */
+	(void) colno;
+	(void) newoid;
+
 	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,
@@ -3494,10 +3513,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),
@@ -3505,7 +3526,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);
@@ -3594,19 +3615,20 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 
 		case AT_AddInherit:
-			ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
+			newoid = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
 			break;
 		case AT_DropInherit:
-			ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
+			newoid = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
 			break;
 		case AT_AddOf:
-			ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
+			newoid = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
 			break;
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
 		case AT_ReplicaIdentity:
-			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def,
+								  lockmode);
 			break;
 		case AT_EnableRowSecurity:
 			ATExecEnableRowSecurity(rel);
@@ -4607,7 +4629,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)
@@ -4692,7 +4714,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					  colDef->colname, RelationGetRelationName(rel))));
 
 			heap_close(attrdesc, RowExclusiveLock);
-			return;
+			return InvalidAttrNumber;
 		}
 	}
 
@@ -4938,6 +4960,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 		heap_close(childrel, NoLock);
 	}
+
+	return newattnum;
 }
 
 /*
@@ -5052,7 +5076,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;
@@ -5137,18 +5161,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)
 {
@@ -5192,18 +5220,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)
 {
@@ -5254,6 +5286,8 @@ ATExecColumnDefault(Relation rel, const char *colName,
 		AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
 								  false, true, false);
 	}
+
+	return attnum;
 }
 
 /*
@@ -5283,13 +5317,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);
@@ -5324,7 +5359,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\"",
@@ -5343,9 +5379,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)
 {
@@ -5353,6 +5391,7 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	HeapTuple	tuple,
 				newtuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 	Datum		datum,
 				newOptions;
 	bool		isnull;
@@ -5371,7 +5410,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\"",
@@ -5410,12 +5450,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;
@@ -5423,6 +5465,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);
@@ -5455,7 +5498,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\"",
@@ -5485,6 +5529,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return attnum;
 }
 
 
@@ -5709,8 +5755,10 @@ 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 void
+static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
@@ -5753,12 +5801,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)
 {
@@ -5768,6 +5818,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	IndexInfo  *indexInfo;
 	char	   *constraintName;
 	char		constraintType;
+	Oid			conOid;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(OidIsValid(index_oid));
@@ -5812,30 +5863,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));
 
 	/*
@@ -5846,9 +5901,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:
@@ -5879,17 +5935,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.
  *
@@ -5908,7 +5969,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)
@@ -5917,6 +5978,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)
@@ -5959,6 +6021,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 */
@@ -5974,7 +6043,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.
@@ -5982,7 +6051,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
@@ -6020,16 +6089,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)
 {
@@ -6428,6 +6500,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Close pk table, but keep lock until we've committed.
 	 */
 	heap_close(pkrel, NoLock);
+
+	return constrOid;
 }
 
 /*
@@ -6440,7 +6514,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)
 {
@@ -6451,6 +6525,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;
@@ -6510,6 +6585,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);
 
@@ -6557,6 +6634,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 /*
@@ -6567,7 +6646,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)
 {
@@ -6577,6 +6656,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);
 
@@ -6618,9 +6698,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;
 
 			/*
@@ -6635,7 +6716,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 conid);
+										 constrOid);
 			heap_close(refrel, NoLock);
 
 			/*
@@ -6716,6 +6797,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 
@@ -7802,7 +7885,7 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
 	}
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
@@ -8196,9 +8279,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 	/* Cleanup */
 	heap_freetuple(heapTup);
+
+	return attnum;
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnGenericOptions(Relation rel,
 								const char *colName,
 								List *options,
@@ -8217,9 +8302,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);
@@ -8246,7 +8332,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)));
@@ -8295,6 +8382,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	heap_close(attrel, RowExclusiveLock);
 
 	heap_freetuple(newtuple);
+
+	return attnum;
 }
 
 /*
@@ -8936,7 +9025,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;
@@ -8954,6 +9043,8 @@ ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 
 	/* And do the work */
 	mark_index_clustered(rel, indexOid, false);
+
+	return indexOid;
 }
 
 /*
@@ -9654,11 +9745,12 @@ ATPrepAddInherit(Relation child_rel)
 				 errmsg("cannot change inheritance of typed table")));
 }
 
-static void
+static Oid
 ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 {
 	Relation	parent_rel,
 				catalogRelation;
+	Oid			parent_oid;
 	SysScanDesc scan;
 	ScanKeyData key;
 	HeapTuple	inheritsTuple;
@@ -9778,11 +9870,15 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 							 inhseqno + 1,
 							 catalogRelation);
 
+	parent_oid = RelationGetRelid(parent_rel);
+
 	/* Now we're done with pg_inherits */
 	heap_close(catalogRelation, RowExclusiveLock);
 
 	/* keep our lock on the parent relation until commit */
 	heap_close(parent_rel, NoLock);
+
+	return parent_oid;
 }
 
 /*
@@ -10051,10 +10147,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
  * coninhcount and conislocal for inherited constraints are adjusted in
  * exactly the same way.
  */
-static void
+static Oid
 ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 {
 	Relation	parent_rel;
+	Oid			parent_oid;
 	Relation	catalogRelation;
 	SysScanDesc scan;
 	ScanKeyData key[3];
@@ -10223,6 +10320,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 		}
 	}
 
+	parent_oid = RelationGetRelid(parent_rel);
+
 	systable_endscan(scan);
 	heap_close(catalogRelation, RowExclusiveLock);
 
@@ -10241,6 +10340,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 
 	/* keep our lock on the parent relation until commit */
 	heap_close(parent_rel, NoLock);
+
+	return parent_oid;
 }
 
 /*
@@ -10298,8 +10399,10 @@ drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid)
  * TABLE OF.  All attname, atttypid, atttypmod and attcollation must match.  The
  * subject table must not have inheritance parents.  These restrictions ensure
  * that you cannot create a configuration impossible with CREATE TABLE OF alone.
+ *
+ * The OID of the type is returned.
  */
-static void
+static Oid
 ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 {
 	Oid			relid = RelationGetRelid(rel);
@@ -10428,6 +10531,8 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	heap_close(relationRelation, RowExclusiveLock);
 
 	ReleaseSysCache(typetuple);
+
+	return typeid;
 }
 
 /*
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index e5c204d..fe1ddd9 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 e7cc7a0..e22ef8f 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -63,7 +63,7 @@ extern Oid index_create(Relation heapRelation,
 			 bool is_internal,
 			 bool if_not_exists);
 
-extern void index_constraint_create(Relation heapRelation,
+extern Oid index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
 						const char *constraintName,
-- 
2.1.4

0004-deparse-core-return-ObjAddress-rather-than-Oid.patchtext/x-diff; charset=us-asciiDownload
>From fb907bf76aa8f061f00cb1917c2bb9440106f489 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 19 Feb 2015 16:06:30 -0300
Subject: [PATCH 4/7] deparse/core: return ObjAddress rather than Oid

---
 src/backend/bootstrap/bootparse.y      |   3 +-
 src/backend/catalog/heap.c             |  29 ++++---
 src/backend/catalog/pg_aggregate.c     |  10 +--
 src/backend/catalog/pg_conversion.c    |   4 +-
 src/backend/catalog/pg_operator.c      |  13 ++--
 src/backend/catalog/pg_proc.c          |   4 +-
 src/backend/catalog/pg_type.c          |  16 ++--
 src/backend/catalog/toasting.c         |   3 +-
 src/backend/commands/aggregatecmds.c   |   2 +-
 src/backend/commands/alter.c           |  13 ++--
 src/backend/commands/cluster.c         |   3 +-
 src/backend/commands/collationcmds.c   |   7 +-
 src/backend/commands/conversioncmds.c  |   2 +-
 src/backend/commands/createas.c        |  40 +++++-----
 src/backend/commands/dbcommands.c      |   7 +-
 src/backend/commands/event_trigger.c   |   7 +-
 src/backend/commands/extension.c       |  36 +++++----
 src/backend/commands/foreigncmds.c     |  51 +++++++-----
 src/backend/commands/functioncmds.c    |  17 ++--
 src/backend/commands/indexcmds.c       |  15 ++--
 src/backend/commands/matview.c         |   7 +-
 src/backend/commands/opclasscmds.c     |  21 ++---
 src/backend/commands/operatorcmds.c    |   2 +-
 src/backend/commands/policy.c          |   8 +-
 src/backend/commands/proclang.c        |  18 +++--
 src/backend/commands/schemacmds.c      |   7 +-
 src/backend/commands/seclabel.c        |   6 +-
 src/backend/commands/sequence.c        |  19 +++--
 src/backend/commands/tablecmds.c       |  47 ++++++-----
 src/backend/commands/trigger.c         |  14 ++--
 src/backend/commands/tsearchcmds.c     |  62 ++++++++++-----
 src/backend/commands/typecmds.c        | 137 +++++++++++++++++++++------------
 src/backend/commands/view.c            |  27 ++++---
 src/backend/rewrite/rewriteDefine.c    |   9 ++-
 src/backend/tcop/utility.c             |  19 ++---
 src/include/catalog/heap.h             |   6 +-
 src/include/catalog/pg_aggregate.h     |   3 +-
 src/include/catalog/pg_conversion_fn.h |   5 +-
 src/include/catalog/pg_operator.h      |   3 +-
 src/include/catalog/pg_proc_fn.h       |   3 +-
 src/include/catalog/pg_type_fn.h       |   5 +-
 src/include/commands/alter.h           |   4 +-
 src/include/commands/collationcmds.h   |   3 +-
 src/include/commands/conversioncmds.h  |   3 +-
 src/include/commands/createas.h        |   3 +-
 src/include/commands/dbcommands.h      |   2 +-
 src/include/commands/defrem.h          |  47 +++++------
 src/include/commands/event_trigger.h   |   2 +-
 src/include/commands/extension.h       |  11 +--
 src/include/commands/matview.h         |   3 +-
 src/include/commands/policy.h          |   4 +-
 src/include/commands/proclang.h        |   3 +-
 src/include/commands/schemacmds.h      |   2 +-
 src/include/commands/seclabel.h        |   2 +-
 src/include/commands/sequence.h        |   5 +-
 src/include/commands/tablecmds.h       |   5 +-
 src/include/commands/trigger.h         |   2 +-
 src/include/commands/typecmds.h        |  27 +++----
 src/include/commands/view.h            |   3 +-
 src/include/rewrite/rewriteDefine.h    |   4 +-
 60 files changed, 509 insertions(+), 336 deletions(-)

diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index fdb1f7f..6e563b6 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -251,7 +251,8 @@ Boot_CreateStmt:
 													  (Datum) 0,
 													  false,
 													  true,
-													  false);
+													  false,
+													  NULL);
 						elog(DEBUG4, "relation created with OID %u", id);
 					}
 					do_end();
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0247da6..9675d21 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -89,7 +89,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 					char relkind,
 					Datum relacl,
 					Datum reloptions);
-static Oid AddNewRelationType(const char *typeName,
+static ObjectAddress AddNewRelationType(const char *typeName,
 				   Oid typeNamespace,
 				   Oid new_rel_oid,
 				   char new_rel_kind,
@@ -935,7 +935,7 @@ AddNewRelationTuple(Relation pg_class_desc,
  *		define a composite type corresponding to the new relation
  * --------------------------------
  */
-static Oid
+static ObjectAddress
 AddNewRelationType(const char *typeName,
 				   Oid typeNamespace,
 				   Oid new_rel_oid,
@@ -1004,6 +1004,10 @@ AddNewRelationType(const char *typeName,
  *	use_user_acl: TRUE if should look for user-defined default permissions;
  *		if FALSE, relacl is always set NULL
  *	allow_system_table_mods: TRUE to allow creation in system namespaces
+ *	is_internal: is this a system-generated catalog?
+ *
+ * Output parameters:
+ *	typaddress: if not null, gets the object address of the new pg_type entry
  *
  * Returns the OID of the new relation
  * --------------------------------
@@ -1028,7 +1032,8 @@ heap_create_with_catalog(const char *relname,
 						 Datum reloptions,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
-						 bool is_internal)
+						 bool is_internal,
+						 ObjectAddress *typaddress)
 {
 	Relation	pg_class_desc;
 	Relation	new_rel_desc;
@@ -1036,6 +1041,7 @@ heap_create_with_catalog(const char *relname,
 	Oid			existing_relid;
 	Oid			old_type_oid;
 	Oid			new_type_oid;
+	ObjectAddress new_type_addr;
 	Oid			new_array_oid = InvalidOid;
 
 	pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock);
@@ -1186,13 +1192,16 @@ heap_create_with_catalog(const char *relname,
 	 * creating the same type name in parallel but hadn't committed yet when
 	 * we checked for a duplicate name above.
 	 */
-	new_type_oid = AddNewRelationType(relname,
-									  relnamespace,
-									  relid,
-									  relkind,
-									  ownerid,
-									  reltypeid,
-									  new_array_oid);
+	new_type_addr = AddNewRelationType(relname,
+									   relnamespace,
+									   relid,
+									   relkind,
+									   ownerid,
+									   reltypeid,
+									   new_array_oid);
+	new_type_oid = new_type_addr.objectId;
+	if (typaddress)
+		*typaddress = new_type_addr;
 
 	/*
 	 * Now make the array type if wanted.
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index b56cf28..a6dba0b 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -43,7 +43,7 @@ static Oid lookup_agg_function(List *fnName, int nargs, Oid *input_types,
 /*
  * AggregateCreate
  */
-Oid
+ObjectAddress
 AggregateCreate(const char *aggName,
 				Oid aggNamespace,
 				char aggKind,
@@ -522,7 +522,7 @@ AggregateCreate(const char *aggName,
 	 * aggregate.  (This could fail if there's already a conflicting entry.)
 	 */
 
-	procOid = ProcedureCreate(aggName,
+	myself = ProcedureCreate(aggName,
 							  aggNamespace,
 							  false,	/* no replacement */
 							  false,	/* doesn't return a set */
@@ -548,6 +548,7 @@ AggregateCreate(const char *aggName,
 							  PointerGetDatum(NULL),	/* proconfig */
 							  1,	/* procost */
 							  0);		/* prorows */
+	procOid = myself.objectId;
 
 	/*
 	 * Okay to create the pg_aggregate entry.
@@ -599,9 +600,6 @@ AggregateCreate(const char *aggName,
 	 * on aggTransType since we depend on it indirectly through transfn.
 	 * Likewise for aggmTransType if any.
 	 */
-	myself.classId = ProcedureRelationId;
-	myself.objectId = procOid;
-	myself.objectSubId = 0;
 
 	/* Depends on transition function */
 	referenced.classId = ProcedureRelationId;
@@ -654,7 +652,7 @@ AggregateCreate(const char *aggName,
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
-	return procOid;
+	return myself;
 }
 
 /*
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index c3a4039..7de7387 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -37,7 +37,7 @@
  *
  * Add a new tuple to pg_conversion.
  */
-Oid
+ObjectAddress
 ConversionCreate(const char *conname, Oid connamespace,
 				 Oid conowner,
 				 int32 conforencoding, int32 contoencoding,
@@ -141,7 +141,7 @@ ConversionCreate(const char *conname, Oid connamespace,
 	heap_freetuple(tup);
 	heap_close(rel, RowExclusiveLock);
 
-	return oid;
+	return myself;
 }
 
 /*
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index fbc173b..072f530 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -61,7 +61,7 @@ static Oid get_other_operator(List *otherOp,
 				   Oid leftTypeId, Oid rightTypeId,
 				   bool isCommutator);
 
-static void makeOperatorDependencies(HeapTuple tuple);
+static ObjectAddress makeOperatorDependencies(HeapTuple tuple);
 
 
 /*
@@ -325,7 +325,7 @@ OperatorShellMake(const char *operatorName,
  * Forward declaration is used only for this purpose, it is
  * not available to the user as it is for type definition.
  */
-Oid
+ObjectAddress
 OperatorCreate(const char *operatorName,
 			   Oid operatorNamespace,
 			   Oid leftTypeId,
@@ -352,6 +352,7 @@ OperatorCreate(const char *operatorName,
 	NameData	oname;
 	TupleDesc	tupDesc;
 	int			i;
+	ObjectAddress address;
 
 	/*
 	 * Sanity checks
@@ -540,7 +541,7 @@ OperatorCreate(const char *operatorName,
 	CatalogUpdateIndexes(pg_operator_desc, tup);
 
 	/* Add dependencies for the entry */
-	makeOperatorDependencies(tup);
+	address = makeOperatorDependencies(tup);
 
 	/* Post creation hook for new operator */
 	InvokeObjectPostCreateHook(OperatorRelationId, operatorObjectId, 0);
@@ -564,7 +565,7 @@ OperatorCreate(const char *operatorName,
 	if (OidIsValid(commutatorId) || OidIsValid(negatorId))
 		OperatorUpd(operatorObjectId, commutatorId, negatorId);
 
-	return operatorObjectId;
+	return address;
 }
 
 /*
@@ -764,7 +765,7 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
  * NB: the OidIsValid tests in this routine are necessary, in case
  * the given operator is a shell.
  */
-static void
+static ObjectAddress
 makeOperatorDependencies(HeapTuple tuple)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
@@ -860,4 +861,6 @@ makeOperatorDependencies(HeapTuple tuple)
 
 	/* Dependency on extension */
 	recordDependencyOnCurrentExtension(&myself, true);
+
+	return myself;
 }
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 542539f..fd5060a 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -64,7 +64,7 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
  * not "ArrayType *", to avoid importing array.h into pg_proc_fn.h.
  * ----------------------------------------------------------------
  */
-Oid
+ObjectAddress
 ProcedureCreate(const char *procedureName,
 				Oid procNamespace,
 				bool replace,
@@ -703,7 +703,7 @@ ProcedureCreate(const char *procedureName,
 			AtEOXact_GUC(true, save_nestlevel);
 	}
 
-	return retval;
+	return myself;
 }
 
 
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index ed8259d..bee0059 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -52,7 +52,7 @@ Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
  *		with correct ones, and "typisdefined" will be set to true.
  * ----------------------------------------------------------------
  */
-Oid
+ObjectAddress
 TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 {
 	Relation	pg_type_desc;
@@ -63,6 +63,7 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	bool		nulls[Natts_pg_type];
 	Oid			typoid;
 	NameData	name;
+	ObjectAddress address;
 
 	Assert(PointerIsValid(typeName));
 
@@ -171,13 +172,15 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	/* Post creation hook for new shell type */
 	InvokeObjectPostCreateHook(TypeRelationId, typoid, 0);
 
+	ObjectAddressSet(address, TypeRelationId, typoid);
+
 	/*
 	 * clean up and return the type-oid
 	 */
 	heap_freetuple(tup);
 	heap_close(pg_type_desc, RowExclusiveLock);
 
-	return typoid;
+	return address;
 }
 
 /* ----------------------------------------------------------------
@@ -185,12 +188,12 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
  *
  *		This does all the necessary work needed to define a new type.
  *
- *		Returns the OID assigned to the new type.  If newTypeOid is
+ *		Returns the ObjectAddress assigned to the new type.  If newTypeOid is
  *		zero (the normal case), a new OID is created; otherwise we
  *		use exactly that OID.
  * ----------------------------------------------------------------
  */
-Oid
+ObjectAddress
 TypeCreate(Oid newTypeOid,
 		   const char *typeName,
 		   Oid typeNamespace,
@@ -233,6 +236,7 @@ TypeCreate(Oid newTypeOid,
 	NameData	name;
 	int			i;
 	Acl		   *typacl = NULL;
+	ObjectAddress address;
 
 	/*
 	 * We assume that the caller validated the arguments individually, but did
@@ -488,12 +492,14 @@ TypeCreate(Oid newTypeOid,
 	/* Post creation hook for new type */
 	InvokeObjectPostCreateHook(TypeRelationId, typeObjectId, 0);
 
+	ObjectAddressSet(address, TypeRelationId, typeObjectId);
+
 	/*
 	 * finish up
 	 */
 	heap_close(pg_type_desc, RowExclusiveLock);
 
-	return typeObjectId;
+	return address;
 }
 
 /*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index a1efddb..d14c33c 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -289,7 +289,8 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   reloptions,
 										   false,
 										   true,
-										   true);
+										   true,
+										   NULL);
 	Assert(toast_relid != InvalidOid);
 
 	/* make the toast relation visible, else heap_open will fail */
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 70a5b8a..894c89d 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -51,7 +51,7 @@
  * isn't an ordered-set aggregate.
  * "parameters" is a list of DefElem representing the agg's definition clauses.
  */
-Oid
+ObjectAddress
 DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 				const char *queryString)
 {
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 361d918..e4d6d8c 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -394,7 +394,7 @@ ExecRenameStmt(RenameStmt *stmt)
  * Executes an ALTER OBJECT / SET SCHEMA statement.  Based on the object
  * type, the function appropriate to that type is executed.
  */
-Oid
+ObjectAddress
 ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
 {
 	switch (stmt->objectType)
@@ -448,14 +448,14 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
 											  nspOid);
 				heap_close(catalog, RowExclusiveLock);
 
-				return address.objectId;
+				return address;
 			}
 			break;
 
 		default:
 			elog(ERROR, "unrecognized AlterObjectSchemaStmt type: %d",
 				 (int) stmt->objectType);
-			return InvalidOid;	/* keep compiler happy */
+			return InvalidObjectAddress;	/* keep compiler happy */
 	}
 }
 
@@ -678,7 +678,7 @@ AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid)
  * Executes an ALTER OBJECT / OWNER TO statement.  Based on the object
  * type, the function appropriate to that type is executed.
  */
-Oid
+ObjectAddress
 ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 {
 	Oid			newowner = get_role_oid(stmt->newowner, false);
@@ -749,15 +749,14 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 				AlterObjectOwner_internal(catalog, address.objectId, newowner);
 				heap_close(catalog, RowExclusiveLock);
 
-				return address.objectId;
+				return address;
 			}
 			break;
 
 		default:
 			elog(ERROR, "unrecognized AlterOwnerStmt type: %d",
 				 (int) stmt->objectType);
-
-			return InvalidOid;	/* keep compiler happy */
+			return InvalidObjectAddress;	/* keep compiler happy */
 	}
 }
 
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1b37c..3febdd5 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -677,7 +677,8 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  reloptions,
 										  false,
 										  true,
-										  true);
+										  true,
+										  NULL);
 	Assert(OIDNewHeap != InvalidOid);
 
 	ReleaseSysCache(tuple);
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index 4334eb9..df67e50 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -37,7 +37,7 @@
 /*
  * CREATE COLLATION
  */
-Oid
+ObjectAddress
 DefineCollation(List *names, List *parameters)
 {
 	char	   *collName;
@@ -51,6 +51,7 @@ DefineCollation(List *names, List *parameters)
 	char	   *collcollate = NULL;
 	char	   *collctype = NULL;
 	Oid			newoid;
+	ObjectAddress address;
 
 	collNamespace = QualifiedNameGetCreationNamespace(names, &collName);
 
@@ -137,11 +138,13 @@ DefineCollation(List *names, List *parameters)
 							 collcollate,
 							 collctype);
 
+	ObjectAddressSet(address, CollationRelationId, newoid);
+
 	/* check that the locales can be loaded */
 	CommandCounterIncrement();
 	(void) pg_newlocale_from_collation(newoid);
 
-	return newoid;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/conversioncmds.c b/src/backend/commands/conversioncmds.c
index f58b0a9..9cd5ced 100644
--- a/src/backend/commands/conversioncmds.c
+++ b/src/backend/commands/conversioncmds.c
@@ -34,7 +34,7 @@
 /*
  * CREATE CONVERSION
  */
-Oid
+ObjectAddress
 CreateConversionCommand(CreateConversionStmt *stmt)
 {
 	Oid			namespaceId;
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index c961429..54b2f38 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -58,8 +58,8 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
-/* the OID of the created table, for ExecCreateTableAs consumption */
-static Oid	CreateAsRelid = InvalidOid;
+/* the address of the created table, for ExecCreateTableAs consumption */
+static ObjectAddress CreateAsReladdr = {InvalidOid, InvalidOid, 0};
 
 static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
 static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
@@ -70,7 +70,7 @@ static void intorel_destroy(DestReceiver *self);
 /*
  * ExecCreateTableAs -- execute a CREATE TABLE AS command
  */
-Oid
+ObjectAddress
 ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 				  ParamListInfo params, char *completionTag)
 {
@@ -81,7 +81,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	Oid			save_userid = InvalidOid;
 	int			save_sec_context = 0;
 	int			save_nestlevel = 0;
-	Oid			relOid;
+	ObjectAddress address;
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
@@ -99,7 +99,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 					(errcode(ERRCODE_DUPLICATE_TABLE),
 					 errmsg("relation \"%s\" already exists, skipping",
 							stmt->into->rel->relname)));
-			return InvalidOid;
+			return InvalidObjectAddress;
 		}
 	}
 
@@ -121,9 +121,9 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		Assert(!is_matview);	/* excluded by syntax */
 		ExecuteQuery(estmt, into, queryString, params, dest, completionTag);
 
-		relOid = CreateAsRelid;
-		CreateAsRelid = InvalidOid;
-		return relOid;
+		address = CreateAsReladdr;
+		CreateAsReladdr = InvalidObjectAddress;
+		return address;
 	}
 	Assert(query->commandType == CMD_SELECT);
 
@@ -216,10 +216,10 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		SetUserIdAndSecContext(save_userid, save_sec_context);
 	}
 
-	relOid = CreateAsRelid;
-	CreateAsRelid = InvalidOid;
+	address = CreateAsReladdr;
+	CreateAsReladdr = InvalidObjectAddress;
 
-	return relOid;
+	return address;
 }
 
 /*
@@ -288,7 +288,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	bool		is_matview;
 	char		relkind;
 	CreateStmt *create;
-	Oid			intoRelationId;
+	ObjectAddress intoRelationAddr;
 	Relation	intoRelationDesc;
 	RangeTblEntry *rte;
 	Datum		toast_options;
@@ -385,7 +385,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	/*
 	 * Actually create the target table
 	 */
-	intoRelationId = DefineRelation(create, relkind, InvalidOid);
+	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
 
 	/*
 	 * If necessary, create a TOAST table for the target table.  Note that
@@ -403,7 +403,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 
 	(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
 
-	NewRelationCreateToastTable(intoRelationId, toast_options);
+	NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
 
 	/* Create the "view" part of a materialized view. */
 	if (is_matview)
@@ -411,14 +411,14 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 		/* StoreViewQuery scribbles on tree, so make a copy */
 		Query	   *query = (Query *) copyObject(into->viewQuery);
 
-		StoreViewQuery(intoRelationId, query, false);
+		StoreViewQuery(intoRelationAddr.objectId, query, false);
 		CommandCounterIncrement();
 	}
 
 	/*
 	 * Finally we can open the target table
 	 */
-	intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
+	intoRelationDesc = heap_open(intoRelationAddr.objectId, AccessExclusiveLock);
 
 	/*
 	 * Check INSERT permission on the constructed table.
@@ -428,7 +428,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	rte = makeNode(RangeTblEntry);
 	rte->rtekind = RTE_RELATION;
-	rte->relid = intoRelationId;
+	rte->relid = intoRelationAddr.objectId;
 	rte->relkind = relkind;
 	rte->requiredPerms = ACL_INSERT;
 
@@ -446,7 +446,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 * be enabled here.  We don't actually support that currently, so throw
 	 * our own ereport(ERROR) if that happens.
 	 */
-	if (check_enable_rls(intoRelationId, InvalidOid, false) == RLS_ENABLED)
+	if (check_enable_rls(intoRelationAddr.objectId, InvalidOid, false) == RLS_ENABLED)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 (errmsg("policies not yet implemented for this command"))));
@@ -464,8 +464,8 @@ 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);
+	/* and remember the new relation's address for ExecCreateTableAs */
+	CreateAsReladdr = intoRelationAddr;
 
 	/*
 	 * We can skip WAL-logging the insertions, unless PITR or streaming
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 9747dd0..8579136 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -1563,7 +1563,7 @@ AlterDatabaseSet(AlterDatabaseSetStmt *stmt)
 /*
  * ALTER DATABASE name OWNER TO newowner
  */
-Oid
+ObjectAddress
 AlterDatabaseOwner(const char *dbname, Oid newOwnerId)
 {
 	Oid			db_id;
@@ -1572,6 +1572,7 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId)
 	ScanKeyData scankey;
 	SysScanDesc scan;
 	Form_pg_database datForm;
+	ObjectAddress address;
 
 	/*
 	 * Get the old tuple.  We don't need a lock on the database per se,
@@ -1666,12 +1667,14 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId)
 
 	InvokeObjectPostAlterHook(DatabaseRelationId, HeapTupleGetOid(tuple), 0);
 
+	ObjectAddressSet(address, DatabaseRelationId, db_id);
+
 	systable_endscan(scan);
 
 	/* Close pg_database, but keep lock till commit */
 	heap_close(rel, NoLock);
 
-	return db_id;
+	return address;
 }
 
 
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index deeb8dc..f573c9c 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -518,12 +518,13 @@ AlterEventTrigger(AlterEventTrigStmt *stmt)
 /*
  * Change event trigger's owner -- by name
  */
-Oid
+ObjectAddress
 AlterEventTriggerOwner(const char *name, Oid newOwnerId)
 {
 	Oid			evtOid;
 	HeapTuple	tup;
 	Relation	rel;
+	ObjectAddress address;
 
 	rel = heap_open(EventTriggerRelationId, RowExclusiveLock);
 
@@ -538,11 +539,13 @@ AlterEventTriggerOwner(const char *name, Oid newOwnerId)
 
 	AlterEventTriggerOwner_internal(rel, tup, newOwnerId);
 
+	ObjectAddressSet(address, EventTriggerRelationId, evtOid);
+
 	heap_freetuple(tup);
 
 	heap_close(rel, RowExclusiveLock);
 
-	return evtOid;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 3b95552..8a47b12 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1170,7 +1170,7 @@ find_update_path(List *evi_list,
 /*
  * CREATE EXTENSION
  */
-Oid
+ObjectAddress
 CreateExtension(CreateExtensionStmt *stmt)
 {
 	DefElem    *d_schema = NULL;
@@ -1188,6 +1188,7 @@ CreateExtension(CreateExtensionStmt *stmt)
 	List	   *requiredSchemas;
 	Oid			extensionOid;
 	ListCell   *lc;
+	ObjectAddress address;
 
 	/* Check extension name validity before any filesystem access */
 	check_valid_extension_name(stmt->extname);
@@ -1206,7 +1207,7 @@ CreateExtension(CreateExtensionStmt *stmt)
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("extension \"%s\" already exists, skipping",
 							stmt->extname)));
-			return InvalidOid;
+			return InvalidObjectAddress;
 		}
 		else
 			ereport(ERROR,
@@ -1443,12 +1444,13 @@ CreateExtension(CreateExtensionStmt *stmt)
 	/*
 	 * Insert new tuple into pg_extension, and create dependency entries.
 	 */
-	extensionOid = InsertExtensionTuple(control->name, extowner,
+	address = InsertExtensionTuple(control->name, extowner,
 										schemaOid, control->relocatable,
 										versionName,
 										PointerGetDatum(NULL),
 										PointerGetDatum(NULL),
 										requiredExtensions);
+	extensionOid = address.objectId;
 
 	/*
 	 * Apply any control-file comment on extension
@@ -1471,7 +1473,7 @@ CreateExtension(CreateExtensionStmt *stmt)
 	ApplyExtensionUpdates(extensionOid, pcontrol,
 						  versionName, updateVersions);
 
-	return extensionOid;
+	return address;
 }
 
 /*
@@ -1487,7 +1489,7 @@ CreateExtension(CreateExtensionStmt *stmt)
  * extConfig and extCondition should be arrays or PointerGetDatum(NULL).
  * We declare them as plain Datum to avoid needing array.h in extension.h.
  */
-Oid
+ObjectAddress
 InsertExtensionTuple(const char *extName, Oid extOwner,
 					 Oid schemaOid, bool relocatable, const char *extVersion,
 					 Datum extConfig, Datum extCondition,
@@ -1564,7 +1566,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	/* Post creation hook for new extension */
 	InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
 
-	return extensionOid;
+	return myself;
 }
 
 /*
@@ -2399,7 +2401,7 @@ extension_config_remove(Oid extensionoid, Oid tableoid)
 /*
  * Execute ALTER EXTENSION SET SCHEMA
  */
-Oid
+ObjectAddress
 AlterExtensionNamespace(List *names, const char *newschema)
 {
 	char	   *extensionName;
@@ -2416,6 +2418,7 @@ AlterExtensionNamespace(List *names, const char *newschema)
 	SysScanDesc depScan;
 	HeapTuple	depTup;
 	ObjectAddresses *objsMoved;
+	ObjectAddress extAddr;
 
 	if (list_length(names) != 1)
 		ereport(ERROR,
@@ -2480,7 +2483,7 @@ AlterExtensionNamespace(List *names, const char *newschema)
 	if (extForm->extnamespace == nspOid)
 	{
 		heap_close(extRel, RowExclusiveLock);
-		return InvalidOid;
+		return InvalidObjectAddress;
 	}
 
 	/* Check extension is supposed to be relocatable */
@@ -2575,13 +2578,15 @@ AlterExtensionNamespace(List *names, const char *newschema)
 
 	InvokeObjectPostAlterHook(ExtensionRelationId, extensionOid, 0);
 
-	return extensionOid;
+	ObjectAddressSet(extAddr, ExtensionRelationId, extensionOid);
+
+	return extAddr;
 }
 
 /*
  * Execute ALTER EXTENSION UPDATE
  */
-Oid
+ObjectAddress
 ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
 {
 	DefElem    *d_new_version = NULL;
@@ -2597,6 +2602,7 @@ ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
 	Datum		datum;
 	bool		isnull;
 	ListCell   *lc;
+	ObjectAddress address;
 
 	/*
 	 * We use global variables to track the extension being created, so we can
@@ -2698,7 +2704,7 @@ ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
 		ereport(NOTICE,
 		   (errmsg("version \"%s\" of extension \"%s\" is already installed",
 				   versionName, stmt->extname)));
-		return InvalidOid;
+		return InvalidObjectAddress;
 	}
 
 	/*
@@ -2715,7 +2721,9 @@ ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
 	ApplyExtensionUpdates(extensionOid, control,
 						  oldVersionName, updateVersions);
 
-	return extensionOid;
+	ObjectAddressSet(address, ExtensionRelationId, extensionOid);
+
+	return address;
 }
 
 /*
@@ -2880,7 +2888,7 @@ ApplyExtensionUpdates(Oid extensionOid,
 /*
  * Execute ALTER EXTENSION ADD/DROP
  */
-Oid
+ObjectAddress
 ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt)
 {
 	ObjectAddress extension;
@@ -2984,5 +2992,5 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt)
 	if (relation != NULL)
 		relation_close(relation, NoLock);
 
-	return extension.objectId;
+	return extension;
 }
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 537e31c..bd48391 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -292,12 +292,13 @@ AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerI
  *
  * Note restrictions in the "_internal" function, above.
  */
-Oid
+ObjectAddress
 AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId)
 {
 	Oid			fdwId;
 	HeapTuple	tup;
 	Relation	rel;
+	ObjectAddress address;
 
 	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
 
@@ -312,11 +313,13 @@ AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId)
 
 	AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId);
 
+	ObjectAddressSet(address, ForeignDataWrapperRelationId, fdwId);
+
 	heap_freetuple(tup);
 
 	heap_close(rel, RowExclusiveLock);
 
-	return fdwId;
+	return address;
 }
 
 /*
@@ -427,12 +430,13 @@ AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 /*
  * Change foreign server owner -- by name
  */
-Oid
+ObjectAddress
 AlterForeignServerOwner(const char *name, Oid newOwnerId)
 {
 	Oid			servOid;
 	HeapTuple	tup;
 	Relation	rel;
+	ObjectAddress address;
 
 	rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
 
@@ -447,11 +451,13 @@ AlterForeignServerOwner(const char *name, Oid newOwnerId)
 
 	AlterForeignServerOwner_internal(rel, tup, newOwnerId);
 
+	ObjectAddressSet(address, ForeignServerRelationId, servOid);
+
 	heap_freetuple(tup);
 
 	heap_close(rel, RowExclusiveLock);
 
-	return servOid;
+	return address;
 }
 
 /*
@@ -569,7 +575,7 @@ parse_func_options(List *func_options,
 /*
  * Create a foreign-data wrapper
  */
-Oid
+ObjectAddress
 CreateForeignDataWrapper(CreateFdwStmt *stmt)
 {
 	Relation	rel;
@@ -676,14 +682,14 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
 
 	heap_close(rel, RowExclusiveLock);
 
-	return fdwId;
+	return myself;
 }
 
 
 /*
  * Alter foreign-data wrapper
  */
-Oid
+ObjectAddress
 AlterForeignDataWrapper(AlterFdwStmt *stmt)
 {
 	Relation	rel;
@@ -699,6 +705,7 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
 	bool		validator_given;
 	Oid			fdwhandler;
 	Oid			fdwvalidator;
+	ObjectAddress myself;
 
 	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
 
@@ -801,10 +808,11 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
 
 	heap_freetuple(tp);
 
+	ObjectAddressSet(myself, ForeignDataWrapperRelationId, fdwId);
+
 	/* Update function dependencies if we changed them */
 	if (handler_given || validator_given)
 	{
-		ObjectAddress myself;
 		ObjectAddress referenced;
 
 		/*
@@ -817,9 +825,6 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
 										DEPENDENCY_NORMAL);
 
 		/* And build new ones. */
-		myself.classId = ForeignDataWrapperRelationId;
-		myself.objectId = fdwId;
-		myself.objectSubId = 0;
 
 		if (OidIsValid(fdwhandler))
 		{
@@ -842,7 +847,7 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
 
 	heap_close(rel, RowExclusiveLock);
 
-	return fdwId;
+	return myself;
 }
 
 
@@ -873,7 +878,7 @@ RemoveForeignDataWrapperById(Oid fdwId)
 /*
  * Create a foreign server
  */
-Oid
+ObjectAddress
 CreateForeignServer(CreateForeignServerStmt *stmt)
 {
 	Relation	rel;
@@ -979,14 +984,14 @@ CreateForeignServer(CreateForeignServerStmt *stmt)
 
 	heap_close(rel, RowExclusiveLock);
 
-	return srvId;
+	return myself;
 }
 
 
 /*
  * Alter foreign server
  */
-Oid
+ObjectAddress
 AlterForeignServer(AlterForeignServerStmt *stmt)
 {
 	Relation	rel;
@@ -996,6 +1001,7 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
 	bool		repl_repl[Natts_pg_foreign_server];
 	Oid			srvId;
 	Form_pg_foreign_server srvForm;
+	ObjectAddress address;
 
 	rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
 
@@ -1072,11 +1078,13 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
 
 	InvokeObjectPostAlterHook(ForeignServerRelationId, srvId, 0);
 
+	ObjectAddressSet(address, ForeignServerRelationId, srvId);
+
 	heap_freetuple(tp);
 
 	heap_close(rel, RowExclusiveLock);
 
-	return srvId;
+	return address;
 }
 
 
@@ -1134,7 +1142,7 @@ user_mapping_ddl_aclcheck(Oid umuserid, Oid serverid, const char *servername)
 /*
  * Create user mapping
  */
-Oid
+ObjectAddress
 CreateUserMapping(CreateUserMappingStmt *stmt)
 {
 	Relation	rel;
@@ -1225,14 +1233,14 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
 
 	heap_close(rel, RowExclusiveLock);
 
-	return umId;
+	return myself;
 }
 
 
 /*
  * Alter user mapping
  */
-Oid
+ObjectAddress
 AlterUserMapping(AlterUserMappingStmt *stmt)
 {
 	Relation	rel;
@@ -1243,6 +1251,7 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
 	Oid			useId;
 	Oid			umId;
 	ForeignServer *srv;
+	ObjectAddress address;
 
 	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
 
@@ -1309,11 +1318,13 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
 	simple_heap_update(rel, &tp->t_self, tp);
 	CatalogUpdateIndexes(rel, tp);
 
+	ObjectAddressSet(address, UserMappingRelationId, umId);
+
 	heap_freetuple(tp);
 
 	heap_close(rel, RowExclusiveLock);
 
-	return umId;
+	return address;
 }
 
 
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index b3a91fc..f472505 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -112,6 +112,7 @@ compute_return_type(TypeName *returnType, Oid languageOid,
 		Oid			namespaceId;
 		AclResult	aclresult;
 		char	   *typname;
+		ObjectAddress address;
 
 		/*
 		 * Only C-coded functions can be I/O functions.  We enforce this
@@ -144,7 +145,8 @@ compute_return_type(TypeName *returnType, Oid languageOid,
 		if (aclresult != ACLCHECK_OK)
 			aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
 						   get_namespace_name(namespaceId));
-		rettype = TypeShellMake(typname, namespaceId, GetUserId());
+		address = TypeShellMake(typname, namespaceId, GetUserId());
+		rettype = address.objectId;
 		Assert(OidIsValid(rettype));
 	}
 
@@ -810,7 +812,7 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
  * CreateFunction
  *	 Execute a CREATE FUNCTION utility statement.
  */
-Oid
+ObjectAddress
 CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
 {
 	char	   *probin_str;
@@ -1071,7 +1073,7 @@ RemoveFunctionById(Oid funcOid)
  * RENAME and OWNER clauses, which are handled as part of the generic
  * ALTER framework).
  */
-Oid
+ObjectAddress
 AlterFunction(AlterFunctionStmt *stmt)
 {
 	HeapTuple	tup;
@@ -1086,6 +1088,7 @@ AlterFunction(AlterFunctionStmt *stmt)
 	List	   *set_items = NIL;
 	DefElem    *cost_item = NULL;
 	DefElem    *rows_item = NULL;
+	ObjectAddress address;
 
 	rel = heap_open(ProcedureRelationId, RowExclusiveLock);
 
@@ -1201,10 +1204,12 @@ AlterFunction(AlterFunctionStmt *stmt)
 
 	InvokeObjectPostAlterHook(ProcedureRelationId, funcOid, 0);
 
+	ObjectAddressSet(address, ProcedureRelationId, funcOid);
+
 	heap_close(rel, NoLock);
 	heap_freetuple(tup);
 
-	return funcOid;
+	return address;
 }
 
 /*
@@ -1282,7 +1287,7 @@ SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType)
 /*
  * CREATE CAST
  */
-Oid
+ObjectAddress
 CreateCast(CreateCastStmt *stmt)
 {
 	Oid			sourcetypeid;
@@ -1596,7 +1601,7 @@ CreateCast(CreateCastStmt *stmt)
 
 	heap_close(relation, RowExclusiveLock);
 
-	return castid;
+	return myself;
 }
 
 /*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index e859669..5fcff50 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -291,9 +291,9 @@ CheckIndexCompatible(Oid oldId,
  *		it will be filled later.
  * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
  *
- * Returns the OID of the created index.
+ * Returns the object address of the created index.
  */
-Oid
+ObjectAddress
 DefineIndex(Oid relationId,
 			IndexStmt *stmt,
 			Oid indexRelationId,
@@ -323,6 +323,7 @@ DefineIndex(Oid relationId,
 	int			numberOfAttributes;
 	TransactionId limitXmin;
 	VirtualTransactionId *old_snapshots;
+	ObjectAddress address;
 	int			n_old_snapshots;
 	LockRelId	heaprelid;
 	LOCKTAG		heaplocktag;
@@ -613,10 +614,14 @@ DefineIndex(Oid relationId,
 					 stmt->concurrent, !check_rights,
 					 stmt->if_not_exists);
 
+	address.classId = RelationRelationId;
+	address.objectId = indexRelationId;
+	address.objectSubId = 0;
+
 	if (!OidIsValid(indexRelationId))
 	{
 		heap_close(rel, NoLock);
-		return indexRelationId;
+		return address;
 	}
 
 	/* Add any requested comment */
@@ -628,7 +633,7 @@ DefineIndex(Oid relationId,
 	{
 		/* Close the heap and we're done, in the non-concurrent case */
 		heap_close(rel, NoLock);
-		return indexRelationId;
+		return address;
 	}
 
 	/* save lockrelid and locktag for below, then close rel */
@@ -873,7 +878,7 @@ DefineIndex(Oid relationId,
 	 */
 	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
 
-	return indexRelationId;
+	return address;
 }
 
 
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 92d9032..eb16bb3 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -134,7 +134,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.
  */
-Oid
+ObjectAddress
 ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				   ParamListInfo params, char *completionTag)
 {
@@ -153,6 +153,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
+	ObjectAddress address;
 
 	/* Determine strength of lock needed. */
 	concurrent = stmt->concurrent;
@@ -311,7 +312,9 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	/* Restore userid and security context */
 	SetUserIdAndSecContext(save_userid, save_sec_context);
 
-	return matviewOid;
+	ObjectAddressSet(address, RelationRelationId, matviewOid);
+
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 16f6f05..c61cb98 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -246,7 +246,7 @@ get_opclass_oid(Oid amID, List *opclassname, bool missing_ok)
  *
  * Caller must have done permissions checks etc. already.
  */
-static Oid
+static ObjectAddress
 CreateOpFamily(char *amname, char *opfname, Oid namespaceoid, Oid amoid)
 {
 	Oid			opfamilyoid;
@@ -319,14 +319,14 @@ CreateOpFamily(char *amname, char *opfname, Oid namespaceoid, Oid amoid)
 
 	heap_close(rel, RowExclusiveLock);
 
-	return opfamilyoid;
+	return myself;
 }
 
 /*
  * DefineOpClass
  *		Define a new index operator class.
  */
-Oid
+ObjectAddress
 DefineOpClass(CreateOpClassStmt *stmt)
 {
 	char	   *opcname;		/* name of opclass we're creating */
@@ -445,11 +445,14 @@ DefineOpClass(CreateOpClassStmt *stmt)
 		}
 		else
 		{
+			ObjectAddress	tmpAddr;
+
 			/*
 			 * Create it ... again no need for more permissions ...
 			 */
-			opfamilyoid = CreateOpFamily(stmt->amname, opcname,
-										 namespaceoid, amoid);
+			tmpAddr = CreateOpFamily(stmt->amname, opcname,
+									 namespaceoid, amoid);
+			opfamilyoid = tmpAddr.objectId;
 		}
 	}
 
@@ -719,7 +722,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 
 	heap_close(rel, RowExclusiveLock);
 
-	return opclassoid;
+	return myself;
 }
 
 
@@ -727,7 +730,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
  * DefineOpFamily
  *		Define a new index operator family.
  */
-Oid
+ObjectAddress
 DefineOpFamily(CreateOpFamilyStmt *stmt)
 {
 	char	   *opfname;		/* name of opfamily we're creating */
@@ -772,7 +775,7 @@ DefineOpFamily(CreateOpFamilyStmt *stmt)
  * other commands called ALTER OPERATOR FAMILY exist, but go through
  * different code paths.
  */
-Oid
+void
 AlterOpFamily(AlterOpFamilyStmt *stmt)
 {
 	Oid			amoid,			/* our AM's oid */
@@ -826,8 +829,6 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
 		AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid,
 						 maxOpNumber, maxProcNumber,
 						 stmt->items);
-
-	return opfamilyoid;
 }
 
 /*
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index 2996019..1efaacf 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -59,7 +59,7 @@
  *
  * 'parameters' is a list of DefElem
  */
-Oid
+ObjectAddress
 DefineOperator(List *names, List *parameters)
 {
 	char	   *oprName;
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 68bad97..e862997 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -460,7 +460,7 @@ RemovePolicyById(Oid policy_id)
  *
  * stmt - the CreatePolicyStmt that describes the policy to create.
  */
-Oid
+ObjectAddress
 CreatePolicy(CreatePolicyStmt *stmt)
 {
 	Relation		pg_policy_rel;
@@ -626,7 +626,7 @@ CreatePolicy(CreatePolicyStmt *stmt)
 	relation_close(target_table, NoLock);
 	heap_close(pg_policy_rel, RowExclusiveLock);
 
-	return policy_id;
+	return myself;
 }
 
 /*
@@ -635,7 +635,7 @@ CreatePolicy(CreatePolicyStmt *stmt)
  *
  * stmt - the AlterPolicyStmt that describes the policy and how to alter it.
  */
-Oid
+ObjectAddress
 AlterPolicy(AlterPolicyStmt *stmt)
 {
 	Relation		pg_policy_rel;
@@ -830,7 +830,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
 	relation_close(target_table, NoLock);
 	heap_close(pg_policy_rel, RowExclusiveLock);
 
-	return policy_id;
+	return myself;
 }
 
 /*
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 0722461..1b163c5 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -51,7 +51,7 @@ typedef struct
 	char	   *tmpllibrary;	/* path of shared library */
 } PLTemplate;
 
-static Oid create_proc_lang(const char *languageName, bool replace,
+static ObjectAddress create_proc_lang(const char *languageName, bool replace,
 				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
 				 Oid valOid, bool trusted);
 static PLTemplate *find_language_template(const char *languageName);
@@ -60,10 +60,11 @@ static PLTemplate *find_language_template(const char *languageName);
  * CREATE PROCEDURAL LANGUAGE
  * ---------------------------------------------------------------------
  */
-Oid
+ObjectAddress
 CreateProceduralLanguage(CreatePLangStmt *stmt)
 {
 	PLTemplate *pltemplate;
+	ObjectAddress tmpAddr;
 	Oid			handlerOid,
 				inlineOid,
 				valOid;
@@ -118,7 +119,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 		}
 		else
 		{
-			handlerOid = ProcedureCreate(pltemplate->tmplhandler,
+			tmpAddr = ProcedureCreate(pltemplate->tmplhandler,
 										 PG_CATALOG_NAMESPACE,
 										 false, /* replace */
 										 false, /* returnsSet */
@@ -142,6 +143,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 										 PointerGetDatum(NULL),
 										 1,
 										 0);
+			handlerOid = tmpAddr.objectId;
 		}
 
 		/*
@@ -155,7 +157,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 			inlineOid = LookupFuncName(funcname, 1, funcargtypes, true);
 			if (!OidIsValid(inlineOid))
 			{
-				inlineOid = ProcedureCreate(pltemplate->tmplinline,
+				tmpAddr = ProcedureCreate(pltemplate->tmplinline,
 											PG_CATALOG_NAMESPACE,
 											false,		/* replace */
 											false,		/* returnsSet */
@@ -179,6 +181,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 											PointerGetDatum(NULL),
 											1,
 											0);
+				inlineOid = tmpAddr.objectId;
 			}
 		}
 		else
@@ -195,7 +198,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 			valOid = LookupFuncName(funcname, 1, funcargtypes, true);
 			if (!OidIsValid(valOid))
 			{
-				valOid = ProcedureCreate(pltemplate->tmplvalidator,
+				tmpAddr = ProcedureCreate(pltemplate->tmplvalidator,
 										 PG_CATALOG_NAMESPACE,
 										 false, /* replace */
 										 false, /* returnsSet */
@@ -219,6 +222,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 										 PointerGetDatum(NULL),
 										 1,
 										 0);
+				valOid = tmpAddr.objectId;
 			}
 		}
 		else
@@ -309,7 +313,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 /*
  * Guts of language creation.
  */
-static Oid
+static ObjectAddress
 create_proc_lang(const char *languageName, bool replace,
 				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
 				 Oid valOid, bool trusted)
@@ -433,7 +437,7 @@ create_proc_lang(const char *languageName, bool replace,
 
 	heap_close(rel, RowExclusiveLock);
 
-	return myself.objectId;
+	return myself;
 }
 
 /*
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 77b5e21..722142e 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -275,12 +275,13 @@ AlterSchemaOwner_oid(Oid oid, Oid newOwnerId)
 /*
  * Change schema owner
  */
-Oid
+ObjectAddress
 AlterSchemaOwner(const char *name, Oid newOwnerId)
 {
 	Oid			nspOid;
 	HeapTuple	tup;
 	Relation	rel;
+	ObjectAddress address;
 
 	rel = heap_open(NamespaceRelationId, RowExclusiveLock);
 
@@ -294,11 +295,13 @@ AlterSchemaOwner(const char *name, Oid newOwnerId)
 
 	AlterSchemaOwner_internal(tup, rel, newOwnerId);
 
+	ObjectAddressSet(address, NamespaceRelationId, nspOid);
+
 	ReleaseSysCache(tup);
 
 	heap_close(rel, RowExclusiveLock);
 
-	return nspOid;
+	return address;
 }
 
 static void
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 6e15bc8..1ef98ce 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -37,8 +37,10 @@ static List *label_provider_list = NIL;
  * ExecSecLabelStmt --
  *
  * Apply a security label to a database object.
+ *
+ * Returns the ObjectAddress of the object to which the policy was applied.
  */
-Oid
+ObjectAddress
 ExecSecLabelStmt(SecLabelStmt *stmt)
 {
 	LabelProvider *provider = NULL;
@@ -133,7 +135,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 	if (relation != NULL)
 		relation_close(relation, NoLock);
 
-	return address.objectId;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0070c4f..6d316d6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -104,13 +104,14 @@ static void process_owned_by(Relation seqrel, List *owned_by);
  * DefineSequence
  *				Creates a new sequence relation
  */
-Oid
+ObjectAddress
 DefineSequence(CreateSeqStmt *seq)
 {
 	FormData_pg_sequence new;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
 	Oid			seqoid;
+	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	tuple;
 	TupleDesc	tupDesc;
@@ -139,7 +140,7 @@ DefineSequence(CreateSeqStmt *seq)
 					(errcode(ERRCODE_DUPLICATE_TABLE),
 					 errmsg("relation \"%s\" already exists, skipping",
 							seq->sequence->relname)));
-			return InvalidOid;
+			return InvalidObjectAddress;
 		}
 	}
 
@@ -233,7 +234,8 @@ DefineSequence(CreateSeqStmt *seq)
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
 
-	seqoid = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId);
+	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
 	rel = heap_open(seqoid, AccessExclusiveLock);
@@ -249,7 +251,7 @@ DefineSequence(CreateSeqStmt *seq)
 
 	heap_close(rel, NoLock);
 
-	return seqoid;
+	return address;
 }
 
 /*
@@ -401,7 +403,7 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
  *
  * Modify the definition of a sequence relation
  */
-Oid
+ObjectAddress
 AlterSequence(AlterSeqStmt *stmt)
 {
 	Oid			relid;
@@ -412,6 +414,7 @@ AlterSequence(AlterSeqStmt *stmt)
 	Form_pg_sequence seq;
 	FormData_pg_sequence new;
 	List	   *owned_by;
+	ObjectAddress address;
 
 	/* Open and lock sequence. */
 	relid = RangeVarGetRelid(stmt->sequence, AccessShareLock, stmt->missing_ok);
@@ -420,7 +423,7 @@ AlterSequence(AlterSeqStmt *stmt)
 		ereport(NOTICE,
 				(errmsg("relation \"%s\" does not exist, skipping",
 						stmt->sequence->relname)));
-		return InvalidOid;
+		return InvalidObjectAddress;
 	}
 
 	init_sequence(relid, &elm, &seqrel);
@@ -484,9 +487,11 @@ AlterSequence(AlterSeqStmt *stmt)
 
 	InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
 
+	ObjectAddressSet(address, RelationRelationId, relid);
+
 	relation_close(seqrel, NoLock);
 
-	return relid;
+	return address;
 }
 
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8b40ddc..733b987 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -435,17 +435,19 @@ static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
  * The other arguments are used to extend the behavior for other cases:
  * relkind: relkind to assign to the new relation
  * ownerId: if not InvalidOid, use this as the new relation's owner.
+ * typaddress: if not null, it's set to the pg_type entry's address.
  *
  * Note that permissions checks are done against current user regardless of
  * ownerId.  A nonzero ownerId is used when someone is creating a relation
  * "on behalf of" someone else, so we still want to see that the current user
  * has permissions to do it.
  *
- * If successful, returns the OID of the new relation.
+ * If successful, returns the address of the new relation.
  * ----------------------------------------------------------------
  */
-Oid
-DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
+ObjectAddress
+DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
+			   ObjectAddress *typaddress)
 {
 	char		relname[NAMEDATALEN];
 	Oid			namespaceId;
@@ -465,6 +467,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 	Oid			ofTypeId;
+	ObjectAddress address;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 										  reloptions,
 										  true,
 										  allowSystemTableMods,
-										  false);
+										  false,
+										  typaddress);
 
 	/* Store inheritance information for new rel. */
 	StoreCatalogInheritance(relationId, inheritOids);
@@ -690,13 +694,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 		AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
 								  true, true, false);
 
+	address.classId = RelationRelationId;
+	address.objectId = relationId;
+	address.objectSubId = 0;
+
 	/*
 	 * Clean up.  We keep lock on new relation (although it shouldn't be
 	 * visible to anyone else anyway, until commit).
 	 */
 	relation_close(rel, NoLock);
 
-	return relationId;
+	return address;
 }
 
 /*
@@ -5765,7 +5773,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	bool		check_rights;
 	bool		skip_build;
 	bool		quiet;
-	Oid			new_index;
+	ObjectAddress address;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(!stmt->concurrent);
@@ -5780,13 +5788,13 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	/* suppress notices when rebuilding existing index */
 	quiet = is_rebuild;
 
-	new_index = DefineIndex(RelationGetRelid(rel),
-							stmt,
-							InvalidOid, /* no predefined OID */
-							true,		/* is_alter_table */
-							check_rights,
-							skip_build,
-							quiet);
+	address = DefineIndex(RelationGetRelid(rel),
+						  stmt,
+						  InvalidOid, /* no predefined OID */
+						  true,		/* is_alter_table */
+						  check_rights,
+						  skip_build,
+						  quiet);
 
 	/*
 	 * If TryReuseIndex() stashed a relfilenode for us, we used it for the new
@@ -5796,13 +5804,13 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	 */
 	if (OidIsValid(stmt->oldNode))
 	{
-		Relation	irel = index_open(new_index, NoLock);
+		Relation	irel = index_open(address.objectId, NoLock);
 
 		RelationPreserveStorage(irel->rd_node, true);
 		index_close(irel, NoLock);
 	}
 
-	return new_index;
+	return address.objectId;
 }
 
 /*
@@ -11039,7 +11047,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 /*
  * Execute ALTER TABLE SET SCHEMA
  */
-Oid
+ObjectAddress
 AlterTableNamespace(AlterObjectSchemaStmt *stmt)
 {
 	Relation	rel;
@@ -11048,6 +11056,7 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt)
 	Oid			nspOid;
 	RangeVar   *newrv;
 	ObjectAddresses *objsMoved;
+	ObjectAddress myself;
 
 	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
 									 stmt->missing_ok, false,
@@ -11059,7 +11068,7 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt)
 		ereport(NOTICE,
 				(errmsg("relation \"%s\" does not exist, skipping",
 						stmt->relation->relname)));
-		return InvalidOid;
+		return InvalidObjectAddress;
 	}
 
 	rel = relation_open(relid, NoLock);
@@ -11092,10 +11101,12 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt)
 	AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
 	free_object_addresses(objsMoved);
 
+	ObjectAddressSet(myself, RelationRelationId, relid);
+
 	/* close rel, but keep lock until commit */
 	relation_close(rel, NoLock);
 
-	return relid;
+	return myself;
 }
 
 /*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 041ae8d..a45b0f8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -100,7 +100,7 @@ static void AfterTriggerEnlargeQueryState(void);
 
 
 /*
- * Create a trigger.  Returns the OID of the created trigger.
+ * Create a trigger.  Returns the address of the created trigger.
  *
  * queryString is the source text of the CREATE TRIGGER command.
  * This must be supplied if a whenClause is specified, else it can be NULL.
@@ -129,10 +129,11 @@ static void AfterTriggerEnlargeQueryState(void);
  * relation, as well as ACL_EXECUTE on the trigger function.  For internal
  * triggers the caller must apply any required permission checks.
  *
- * Note: can return InvalidOid if we decided to not create a trigger at all,
- * but a foreign-key constraint.  This is a kluge for backwards compatibility.
+ * Note: can return InvalidObjectAddress if we decided to not create a trigger
+ * at all, but a foreign-key constraint.  This is a kluge for backwards
+ * compatibility.
  */
-Oid
+ObjectAddress
 CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 			  Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid,
 			  bool isInternal)
@@ -459,7 +460,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 		ConvertTriggerToFK(stmt, funcoid);
 
-		return InvalidOid;
+		return InvalidObjectAddress;
 	}
 
 	/*
@@ -799,7 +800,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	/* Keep lock on target rel until end of xact */
 	heap_close(rel, NoLock);
 
-	return trigoid;
+	return myself;
 }
 
 
@@ -1366,7 +1367,6 @@ renametrig(RenameStmt *stmt)
 	return address;
 }
 
-
 /*
  * EnableDisableTrigger()
  *
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index c6f8994..45bafd3 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -120,8 +120,10 @@ get_ts_parser_func(DefElem *defel, int attnum)
 
 /*
  * make pg_depend entries for a new pg_ts_parser entry
+ *
+ * Return value is the address of said new entry.
  */
-static void
+static ObjectAddress
 makeParserDependencies(HeapTuple tuple)
 {
 	Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
@@ -162,12 +164,14 @@ makeParserDependencies(HeapTuple tuple)
 		referenced.objectId = prs->prsheadline;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
+
+	return myself;
 }
 
 /*
  * CREATE TEXT SEARCH PARSER
  */
-Oid
+ObjectAddress
 DefineTSParser(List *names, List *parameters)
 {
 	char	   *prsname;
@@ -179,6 +183,7 @@ DefineTSParser(List *names, List *parameters)
 	NameData	pname;
 	Oid			prsOid;
 	Oid			namespaceoid;
+	ObjectAddress address;
 
 	if (!superuser())
 		ereport(ERROR,
@@ -269,7 +274,7 @@ DefineTSParser(List *names, List *parameters)
 
 	CatalogUpdateIndexes(prsRel, tup);
 
-	makeParserDependencies(tup);
+	address = makeParserDependencies(tup);
 
 	/* Post creation hook for new text search parser */
 	InvokeObjectPostCreateHook(TSParserRelationId, prsOid, 0);
@@ -278,7 +283,7 @@ DefineTSParser(List *names, List *parameters)
 
 	heap_close(prsRel, RowExclusiveLock);
 
-	return prsOid;
+	return address;
 }
 
 /*
@@ -308,8 +313,10 @@ RemoveTSParserById(Oid prsId)
 
 /*
  * make pg_depend entries for a new pg_ts_dict entry
+ *
+ * Return value is address of the new entry
  */
-static void
+static ObjectAddress
 makeDictionaryDependencies(HeapTuple tuple)
 {
 	Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
@@ -337,6 +344,8 @@ makeDictionaryDependencies(HeapTuple tuple)
 	referenced.objectId = dict->dicttemplate;
 	referenced.objectSubId = 0;
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	return myself;
 }
 
 /*
@@ -397,7 +406,7 @@ verify_dictoptions(Oid tmplId, List *dictoptions)
 /*
  * CREATE TEXT SEARCH DICTIONARY
  */
-Oid
+ObjectAddress
 DefineTSDictionary(List *names, List *parameters)
 {
 	ListCell   *pl;
@@ -412,6 +421,7 @@ DefineTSDictionary(List *names, List *parameters)
 	Oid			namespaceoid;
 	AclResult	aclresult;
 	char	   *dictname;
+	ObjectAddress address;
 
 	/* Convert list of names to a name and namespace */
 	namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
@@ -475,7 +485,7 @@ DefineTSDictionary(List *names, List *parameters)
 
 	CatalogUpdateIndexes(dictRel, tup);
 
-	makeDictionaryDependencies(tup);
+	address = makeDictionaryDependencies(tup);
 
 	/* Post creation hook for new text search dictionary */
 	InvokeObjectPostCreateHook(TSDictionaryRelationId, dictOid, 0);
@@ -484,7 +494,7 @@ DefineTSDictionary(List *names, List *parameters)
 
 	heap_close(dictRel, RowExclusiveLock);
 
-	return dictOid;
+	return address;
 }
 
 /*
@@ -514,7 +524,7 @@ RemoveTSDictionaryById(Oid dictId)
 /*
  * ALTER TEXT SEARCH DICTIONARY
  */
-Oid
+ObjectAddress
 AlterTSDictionary(AlterTSDictionaryStmt *stmt)
 {
 	HeapTuple	tup,
@@ -528,6 +538,7 @@ AlterTSDictionary(AlterTSDictionaryStmt *stmt)
 	Datum		repl_val[Natts_pg_ts_dict];
 	bool		repl_null[Natts_pg_ts_dict];
 	bool		repl_repl[Natts_pg_ts_dict];
+	ObjectAddress address;
 
 	dictId = get_ts_dict_oid(stmt->dictname, false);
 
@@ -614,6 +625,8 @@ AlterTSDictionary(AlterTSDictionaryStmt *stmt)
 
 	InvokeObjectPostAlterHook(TSDictionaryRelationId, dictId, 0);
 
+	ObjectAddressSet(address, TSDictionaryRelationId, dictId);
+
 	/*
 	 * NOTE: because we only support altering the options, not the template,
 	 * there is no need to update dependencies.  This might have to change if
@@ -625,7 +638,7 @@ AlterTSDictionary(AlterTSDictionaryStmt *stmt)
 
 	heap_close(rel, RowExclusiveLock);
 
-	return dictId;
+	return address;
 }
 
 /* ---------------------- TS Template commands -----------------------*/
@@ -678,7 +691,7 @@ get_ts_template_func(DefElem *defel, int attnum)
 /*
  * make pg_depend entries for a new pg_ts_template entry
  */
-static void
+static ObjectAddress
 makeTSTemplateDependencies(HeapTuple tuple)
 {
 	Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
@@ -710,12 +723,14 @@ makeTSTemplateDependencies(HeapTuple tuple)
 		referenced.objectId = tmpl->tmplinit;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
+
+	return myself;
 }
 
 /*
  * CREATE TEXT SEARCH TEMPLATE
  */
-Oid
+ObjectAddress
 DefineTSTemplate(List *names, List *parameters)
 {
 	ListCell   *pl;
@@ -728,6 +743,7 @@ DefineTSTemplate(List *names, List *parameters)
 	Oid			tmplOid;
 	Oid			namespaceoid;
 	char	   *tmplname;
+	ObjectAddress address;
 
 	if (!superuser())
 		ereport(ERROR,
@@ -793,7 +809,7 @@ DefineTSTemplate(List *names, List *parameters)
 
 	CatalogUpdateIndexes(tmplRel, tup);
 
-	makeTSTemplateDependencies(tup);
+	address = makeTSTemplateDependencies(tup);
 
 	/* Post creation hook for new text search template */
 	InvokeObjectPostCreateHook(TSTemplateRelationId, tmplOid, 0);
@@ -802,7 +818,7 @@ DefineTSTemplate(List *names, List *parameters)
 
 	heap_close(tmplRel, RowExclusiveLock);
 
-	return tmplOid;
+	return address;
 }
 
 /*
@@ -860,7 +876,7 @@ GetTSConfigTuple(List *names)
  * Pass opened pg_ts_config_map relation if there might be any config map
  * entries for the config.
  */
-static void
+static ObjectAddress
 makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
 							  Relation mapRel)
 {
@@ -940,12 +956,14 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 
 	free_object_addresses(addrs);
+
+	return myself;
 }
 
 /*
  * CREATE TEXT SEARCH CONFIGURATION
  */
-Oid
+ObjectAddress
 DefineTSConfiguration(List *names, List *parameters)
 {
 	Relation	cfgRel;
@@ -961,6 +979,7 @@ DefineTSConfiguration(List *names, List *parameters)
 	Oid			prsOid = InvalidOid;
 	Oid			cfgOid;
 	ListCell   *pl;
+	ObjectAddress address;
 
 	/* Convert list of names to a name and namespace */
 	namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
@@ -1088,7 +1107,7 @@ DefineTSConfiguration(List *names, List *parameters)
 		systable_endscan(scan);
 	}
 
-	makeConfigurationDependencies(tup, false, mapRel);
+	address = makeConfigurationDependencies(tup, false, mapRel);
 
 	/* Post creation hook for new text search configuration */
 	InvokeObjectPostCreateHook(TSConfigRelationId, cfgOid, 0);
@@ -1099,7 +1118,7 @@ DefineTSConfiguration(List *names, List *parameters)
 		heap_close(mapRel, RowExclusiveLock);
 	heap_close(cfgRel, RowExclusiveLock);
 
-	return cfgOid;
+	return address;
 }
 
 /*
@@ -1153,12 +1172,13 @@ RemoveTSConfigurationById(Oid cfgId)
 /*
  * ALTER TEXT SEARCH CONFIGURATION - main entry point
  */
-Oid
+ObjectAddress
 AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
 {
 	HeapTuple	tup;
 	Oid			cfgId;
 	Relation	relMap;
+	ObjectAddress address;
 
 	/* Find the configuration */
 	tup = GetTSConfigTuple(stmt->cfgname);
@@ -1189,11 +1209,13 @@ AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
 	InvokeObjectPostAlterHook(TSConfigMapRelationId,
 							  HeapTupleGetOid(tup), 0);
 
+	ObjectAddressSet(address, TSConfigMapRelationId, cfgId);
+
 	heap_close(relMap, RowExclusiveLock);
 
 	ReleaseSysCache(tup);
 
-	return cfgId;
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index b02abfe..e246719 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -115,7 +115,7 @@ static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  * DefineType
  *		Registers a new base type.
  */
-Oid
+ObjectAddress
 DefineType(List *names, List *parameters)
 {
 	char	   *typeName;
@@ -167,6 +167,7 @@ DefineType(List *names, List *parameters)
 	Oid			typoid;
 	Oid			resulttype;
 	ListCell   *pl;
+	ObjectAddress address;
 
 	/*
 	 * As of Postgres 8.4, we require superuser privilege to create a base
@@ -220,7 +221,7 @@ DefineType(List *names, List *parameters)
 	 */
 	if (!OidIsValid(typoid))
 	{
-		typoid = TypeShellMake(typeName, typeNamespace, GetUserId());
+		address = TypeShellMake(typeName, typeNamespace, GetUserId());
 		/* Make new shell type visible for modification below */
 		CommandCounterIncrement();
 
@@ -229,7 +230,7 @@ DefineType(List *names, List *parameters)
 		 * creating the shell type was all we're supposed to do.
 		 */
 		if (parameters == NIL)
-			return InvalidOid;
+			return address;
 	}
 	else
 	{
@@ -602,7 +603,7 @@ DefineType(List *names, List *parameters)
 	 * types) in ArrayType and in composite types in DatumTupleFields.  This
 	 * oid must be preserved by binary upgrades.
 	 */
-	typoid =
+	address =
 		TypeCreate(InvalidOid,	/* no predetermined type OID */
 				   typeName,	/* type name */
 				   typeNamespace,		/* namespace */
@@ -677,7 +678,7 @@ DefineType(List *names, List *parameters)
 
 	pfree(array_type);
 
-	return typoid;
+	return address;
 }
 
 /*
@@ -723,7 +724,7 @@ RemoveTypeById(Oid typeOid)
  * DefineDomain
  *		Registers a new domain.
  */
-Oid
+ObjectAddress
 DefineDomain(CreateDomainStmt *stmt)
 {
 	char	   *domainName;
@@ -753,12 +754,12 @@ DefineDomain(CreateDomainStmt *stmt)
 	List	   *schema = stmt->constraints;
 	ListCell   *listptr;
 	Oid			basetypeoid;
-	Oid			domainoid;
 	Oid			old_type_oid;
 	Oid			domaincoll;
 	Form_pg_type baseType;
 	int32		basetypeMod;
 	Oid			baseColl;
+	ObjectAddress address;
 
 	/* Convert list of names to a name and namespace */
 	domainNamespace = QualifiedNameGetCreationNamespace(stmt->domainname,
@@ -1028,7 +1029,7 @@ DefineDomain(CreateDomainStmt *stmt)
 	/*
 	 * Have TypeCreate do all the real work.
 	 */
-	domainoid =
+	address =
 		TypeCreate(InvalidOid,	/* no predetermined type OID */
 				   domainName,	/* type name */
 				   domainNamespace,		/* namespace */
@@ -1073,7 +1074,7 @@ DefineDomain(CreateDomainStmt *stmt)
 		switch (constr->contype)
 		{
 			case CONSTR_CHECK:
-				domainAddConstraint(domainoid, domainNamespace,
+				domainAddConstraint(address.objectId, domainNamespace,
 									basetypeoid, basetypeMod,
 									constr, domainName);
 				break;
@@ -1093,7 +1094,7 @@ DefineDomain(CreateDomainStmt *stmt)
 	 */
 	ReleaseSysCache(typeTup);
 
-	return domainoid;
+	return address;
 }
 
 
@@ -1101,16 +1102,16 @@ DefineDomain(CreateDomainStmt *stmt)
  * DefineEnum
  *		Registers a new enum.
  */
-Oid
+ObjectAddress
 DefineEnum(CreateEnumStmt *stmt)
 {
 	char	   *enumName;
 	char	   *enumArrayName;
 	Oid			enumNamespace;
-	Oid			enumTypeOid;
 	AclResult	aclresult;
 	Oid			old_type_oid;
 	Oid			enumArrayOid;
+	ObjectAddress enumTypeAddr;
 
 	/* Convert list of names to a name and namespace */
 	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1140,7 +1141,7 @@ DefineEnum(CreateEnumStmt *stmt)
 	enumArrayOid = AssignTypeArrayOid();
 
 	/* Create the pg_type entry */
-	enumTypeOid =
+	enumTypeAddr =
 		TypeCreate(InvalidOid,	/* no predetermined type OID */
 				   enumName,	/* type name */
 				   enumNamespace,		/* namespace */
@@ -1174,7 +1175,7 @@ DefineEnum(CreateEnumStmt *stmt)
 				   InvalidOid); /* type's collation */
 
 	/* Enter the enum's values into pg_enum */
-	EnumValuesCreate(enumTypeOid, stmt->vals);
+	EnumValuesCreate(enumTypeAddr.objectId, stmt->vals);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1199,7 +1200,7 @@ DefineEnum(CreateEnumStmt *stmt)
 			   InvalidOid,		/* typmodin procedure - none */
 			   InvalidOid,		/* typmodout procedure - none */
 			   F_ARRAY_TYPANALYZE,		/* analyze procedure */
-			   enumTypeOid,		/* element type ID */
+			   enumTypeAddr.objectId,	/* element type ID */
 			   true,			/* yes this is an array type */
 			   InvalidOid,		/* no further array type */
 			   InvalidOid,		/* base type ID */
@@ -1215,19 +1216,20 @@ DefineEnum(CreateEnumStmt *stmt)
 
 	pfree(enumArrayName);
 
-	return enumTypeOid;
+	return enumTypeAddr;
 }
 
 /*
  * AlterEnum
  *		Adds a new label to an existing enum.
  */
-Oid
+ObjectAddress
 AlterEnum(AlterEnumStmt *stmt, bool isTopLevel)
 {
 	Oid			enum_type_oid;
 	TypeName   *typename;
 	HeapTuple	tup;
+	ObjectAddress address;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(stmt->typeName);
@@ -1266,9 +1268,11 @@ AlterEnum(AlterEnumStmt *stmt, bool isTopLevel)
 
 	InvokeObjectPostAlterHook(TypeRelationId, enum_type_oid, 0);
 
+	ObjectAddressSet(address, TypeRelationId, enum_type_oid);
+
 	ReleaseSysCache(tup);
 
-	return enum_type_oid;
+	return address;
 }
 
 
@@ -1300,7 +1304,7 @@ checkEnumOwner(HeapTuple tup)
  * DefineRange
  *		Registers a new range type.
  */
-Oid
+ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
 {
 	char	   *typeName;
@@ -1323,6 +1327,7 @@ DefineRange(CreateRangeStmt *stmt)
 	char		alignment;
 	AclResult	aclresult;
 	ListCell   *lc;
+	ObjectAddress address;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1361,7 +1366,8 @@ DefineRange(CreateRangeStmt *stmt)
 	 */
 	if (!OidIsValid(typoid))
 	{
-		typoid = TypeShellMake(typeName, typeNamespace, GetUserId());
+		address = TypeShellMake(typeName, typeNamespace, GetUserId());
+		typoid = address.objectId;
 		/* Make new shell type visible for modification below */
 		CommandCounterIncrement();
 	}
@@ -1474,7 +1480,7 @@ DefineRange(CreateRangeStmt *stmt)
 	rangeArrayOid = AssignTypeArrayOid();
 
 	/* Create the pg_type entry */
-	typoid =
+	address =
 		TypeCreate(InvalidOid,	/* no predetermined type OID */
 				   typeName,	/* type name */
 				   typeNamespace,		/* namespace */
@@ -1506,6 +1512,7 @@ DefineRange(CreateRangeStmt *stmt)
 				   0,			/* Array dimensions of typbasetype */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation (ranges never have one) */
+	typoid = address.objectId;
 
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
@@ -1553,7 +1560,7 @@ DefineRange(CreateRangeStmt *stmt)
 	/* And create the constructor functions for this range type */
 	makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype);
 
-	return typoid;
+	return address;
 }
 
 /*
@@ -1589,12 +1596,11 @@ makeRangeConstructors(const char *name, Oid namespace,
 	for (i = 0; i < lengthof(prosrc); i++)
 	{
 		oidvector  *constructorArgTypesVector;
-		Oid			procOid;
 
 		constructorArgTypesVector = buildoidvector(constructorArgTypes,
 												   pronargs[i]);
 
-		procOid = ProcedureCreate(name, /* name: same as range type */
+		myself = ProcedureCreate(name, /* name: same as range type */
 								  namespace,	/* namespace */
 								  false,		/* replace */
 								  false,		/* returns set */
@@ -1624,10 +1630,6 @@ makeRangeConstructors(const char *name, Oid namespace,
 		 * that they go away silently when the type is dropped.  Note that
 		 * pg_dump depends on this choice to avoid dumping the constructors.
 		 */
-		myself.classId = ProcedureRelationId;
-		myself.objectId = procOid;
-		myself.objectSubId = 0;
-
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
 	}
 }
@@ -2066,17 +2068,16 @@ AssignTypeArrayOid(void)
  * If the relation already exists, then 'DefineRelation' will abort
  * the xact...
  *
- * DefineCompositeType returns relid for use when creating
- * an implicit composite type during function creation
+ * Return type is the new type's object address.
  *-------------------------------------------------------------------
  */
-Oid
+ObjectAddress
 DefineCompositeType(RangeVar *typevar, List *coldeflist)
 {
 	CreateStmt *createStmt = makeNode(CreateStmt);
 	Oid			old_type_oid;
 	Oid			typeNamespace;
-	Oid			relid;
+	ObjectAddress address;
 
 	/*
 	 * now set the parameters for keys/inheritance etc. All of these are
@@ -2115,17 +2116,19 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
 	/*
 	 * Finally create the relation.  This also creates the type.
 	 */
-	relid = DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid);
-	Assert(relid != InvalidOid);
-	return relid;
+	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+
+	return address;
 }
 
 /*
  * AlterDomainDefault
  *
  * Routine implementing ALTER DOMAIN SET/DROP DEFAULT statements.
+ *
+ * Returns ObjectAddress of the modified domain.
  */
-Oid
+ObjectAddress
 AlterDomainDefault(List *names, Node *defaultRaw)
 {
 	TypeName   *typename;
@@ -2140,6 +2143,7 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 	bool		new_record_repl[Natts_pg_type];
 	HeapTuple	newtuple;
 	Form_pg_type typTup;
+	ObjectAddress address;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2249,19 +2253,25 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 
 	InvokeObjectPostAlterHook(TypeRelationId, domainoid, 0);
 
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;
+
 	/* Clean up */
 	heap_close(rel, NoLock);
 	heap_freetuple(newtuple);
 
-	return domainoid;
+	return address;
 }
 
 /*
  * AlterDomainNotNull
  *
  * Routine implementing ALTER DOMAIN SET/DROP NOT NULL statements.
+ *
+ * Returns ObjectAddress of the modified domain.
  */
-Oid
+ObjectAddress
 AlterDomainNotNull(List *names, bool notNull)
 {
 	TypeName   *typename;
@@ -2269,6 +2279,7 @@ AlterDomainNotNull(List *names, bool notNull)
 	Relation	typrel;
 	HeapTuple	tup;
 	Form_pg_type typTup;
+	ObjectAddress address = InvalidObjectAddress;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2289,7 +2300,7 @@ AlterDomainNotNull(List *names, bool notNull)
 	if (typTup->typnotnull == notNull)
 	{
 		heap_close(typrel, RowExclusiveLock);
-		return InvalidOid;
+		return address;
 	}
 
 	/* Adding a NOT NULL constraint requires checking existing columns */
@@ -2363,11 +2374,15 @@ AlterDomainNotNull(List *names, bool notNull)
 
 	InvokeObjectPostAlterHook(TypeRelationId, domainoid, 0);
 
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;
+
 	/* Clean up */
 	heap_freetuple(tup);
 	heap_close(typrel, RowExclusiveLock);
 
-	return domainoid;
+	return address;
 }
 
 /*
@@ -2375,7 +2390,7 @@ AlterDomainNotNull(List *names, bool notNull)
  *
  * Implements the ALTER DOMAIN DROP CONSTRAINT statement
  */
-Oid
+ObjectAddress
 AlterDomainDropConstraint(List *names, const char *constrName,
 						  DropBehavior behavior, bool missing_ok)
 {
@@ -2388,6 +2403,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
 	ScanKeyData key[1];
 	HeapTuple	contup;
 	bool		found = false;
+	ObjectAddress address = InvalidObjectAddress;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2434,6 +2450,11 @@ AlterDomainDropConstraint(List *names, const char *constrName,
 			found = true;
 		}
 	}
+
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;
+
 	/* Clean up after the scan */
 	systable_endscan(conscan);
 	heap_close(conrel, RowExclusiveLock);
@@ -2453,7 +2474,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
 							constrName, TypeNameToString(typename))));
 	}
 
-	return domainoid;
+	return address;
 }
 
 /*
@@ -2461,7 +2482,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
  *
  * Implements the ALTER DOMAIN .. ADD CONSTRAINT statement.
  */
-Oid
+ObjectAddress
 AlterDomainAddConstraint(List *names, Node *newConstraint)
 {
 	TypeName   *typename;
@@ -2471,6 +2492,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
 	Form_pg_type typTup;
 	Constraint *constr;
 	char	   *ccbin;
+	ObjectAddress address;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2555,10 +2577,14 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
 	if (!constr->skip_validation)
 		validateDomainConstraint(domainoid, ccbin);
 
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;
+
 	/* Clean up */
 	heap_close(typrel, RowExclusiveLock);
 
-	return domainoid;
+	return address;
 }
 
 /*
@@ -2566,7 +2592,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
  *
  * Implements the ALTER DOMAIN .. VALIDATE CONSTRAINT statement.
  */
-Oid
+ObjectAddress
 AlterDomainValidateConstraint(List *names, char *constrName)
 {
 	TypeName   *typename;
@@ -2584,6 +2610,7 @@ AlterDomainValidateConstraint(List *names, char *constrName)
 	HeapTuple	tuple;
 	HeapTuple	copyTuple;
 	ScanKeyData key;
+	ObjectAddress address;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2654,6 +2681,10 @@ AlterDomainValidateConstraint(List *names, char *constrName)
 	InvokeObjectPostAlterHook(ConstraintRelationId,
 							  HeapTupleGetOid(copyTuple), 0);
 
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;
+
 	heap_freetuple(copyTuple);
 
 	systable_endscan(scan);
@@ -2663,7 +2694,7 @@ AlterDomainValidateConstraint(List *names, char *constrName)
 
 	ReleaseSysCache(tup);
 
-	return domainoid;
+	return address;
 }
 
 static void
@@ -3283,7 +3314,7 @@ RenameType(RenameStmt *stmt)
 /*
  * Change the owner of a type.
  */
-Oid
+ObjectAddress
 AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype)
 {
 	TypeName   *typename;
@@ -3293,6 +3324,7 @@ AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype)
 	HeapTuple	newtup;
 	Form_pg_type typTup;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	rel = heap_open(TypeRelationId, RowExclusiveLock);
 
@@ -3422,10 +3454,12 @@ AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype)
 		}
 	}
 
+	ObjectAddressSet(address, TypeRelationId, typeOid);
+
 	/* Clean up */
 	heap_close(rel, RowExclusiveLock);
 
-	return typeOid;
+	return address;
 }
 
 /*
@@ -3505,13 +3539,14 @@ AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
 /*
  * Execute ALTER TYPE SET SCHEMA
  */
-Oid
+ObjectAddress
 AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype)
 {
 	TypeName   *typename;
 	Oid			typeOid;
 	Oid			nspOid;
 	ObjectAddresses *objsMoved;
+	ObjectAddress myself;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -3531,7 +3566,9 @@ AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype)
 	AlterTypeNamespace_oid(typeOid, nspOid, objsMoved);
 	free_object_addresses(objsMoved);
 
-	return typeOid;
+	ObjectAddressSet(myself, TypeRelationId, typeOid);
+
+	return myself;
 }
 
 Oid
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 7358723..a6d1705 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -65,7 +65,7 @@ validateWithCheckOption(char *value)
  * work harder.
  *---------------------------------------------------------------------
  */
-static Oid
+static ObjectAddress
 DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 					  List *options)
 {
@@ -143,6 +143,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		TupleDesc	descriptor;
 		List	   *atcmds = NIL;
 		AlterTableCmd *atcmd;
+		ObjectAddress address;
 
 		/* Relation is already locked, but we must build a relcache entry. */
 		rel = relation_open(viewOid, NoLock);
@@ -208,16 +209,20 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		/* OK, let's do it. */
 		AlterTableInternal(viewOid, atcmds, true);
 
+		address.classId = RelationRelationId;
+		address.objectId = viewOid;
+		address.objectSubId = 0;
+
 		/*
 		 * Seems okay, so return the OID of the pre-existing view.
 		 */
 		relation_close(rel, NoLock);	/* keep the lock! */
 
-		return viewOid;
+		return address;
 	}
 	else
 	{
-		Oid			relid;
+		ObjectAddress	address;
 
 		/*
 		 * now set the parameters for keys/inheritance etc. All of these are
@@ -237,9 +242,9 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		 * existing view, so we don't need more code to complain if "replace"
 		 * is false).
 		 */
-		relid = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid);
-		Assert(relid != InvalidOid);
-		return relid;
+		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+		Assert(address.objectId != InvalidOid);
+		return address;
 	}
 }
 
@@ -388,14 +393,14 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
  * DefineView
  *		Execute a CREATE VIEW command.
  */
-Oid
+ObjectAddress
 DefineView(ViewStmt *stmt, const char *queryString)
 {
 	Query	   *viewParse;
-	Oid			viewOid;
 	RangeVar   *view;
 	ListCell   *cell;
 	bool		check_option;
+	ObjectAddress address;
 
 	/*
 	 * Run parse analysis to convert the raw parse tree to a Query.  Note this
@@ -533,7 +538,7 @@ DefineView(ViewStmt *stmt, const char *queryString)
 	 * NOTE: if it already exists and replace is false, the xact will be
 	 * aborted.
 	 */
-	viewOid = DefineVirtualRelation(view, viewParse->targetList,
+	address = DefineVirtualRelation(view, viewParse->targetList,
 									stmt->replace, stmt->options);
 
 	/*
@@ -543,9 +548,9 @@ DefineView(ViewStmt *stmt, const char *queryString)
 	 */
 	CommandCounterIncrement();
 
-	StoreViewQuery(viewOid, viewParse, stmt->replace);
+	StoreViewQuery(address.objectId, viewParse, stmt->replace);
 
-	return viewOid;
+	return address;
 }
 
 /*
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 75b6d80..f540432 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -191,7 +191,7 @@ InsertRule(char *rulname,
  * DefineRule
  *		Execute a CREATE RULE command.
  */
-Oid
+ObjectAddress
 DefineRule(RuleStmt *stmt, const char *queryString)
 {
 	List	   *actions;
@@ -225,7 +225,7 @@ DefineRule(RuleStmt *stmt, const char *queryString)
  * This is essentially the same as DefineRule() except that the rule's
  * action and qual have already been passed through parse analysis.
  */
-Oid
+ObjectAddress
 DefineQueryRewrite(char *rulename,
 				   Oid event_relid,
 				   Node *event_qual,
@@ -239,6 +239,7 @@ DefineQueryRewrite(char *rulename,
 	Query	   *query;
 	bool		RelisBecomingView = false;
 	Oid			ruleId = InvalidOid;
+	ObjectAddress address;
 
 	/*
 	 * If we are installing an ON SELECT rule, we had better grab
@@ -604,10 +605,12 @@ DefineQueryRewrite(char *rulename,
 		heap_close(relationRelation, RowExclusiveLock);
 	}
 
+	ObjectAddressSet(address, RewriteRelationId, ruleId);
+
 	/* Close rel, but keep lock till commit... */
 	heap_close(event_relation, NoLock);
 
-	return ruleId;
+	return address;
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6d26986..7627d43 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -886,6 +886,7 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	ObjectAddress address;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -911,7 +912,6 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					List	   *stmts;
 					ListCell   *l;
-					Oid			relOid;
 
 					/* Run parse analysis ... */
 					stmts = transformCreateStmt((CreateStmt *) parsetree,
@@ -928,9 +928,9 @@ ProcessUtilitySlow(Node *parsetree,
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 
 							/* Create the table itself */
-							relOid = DefineRelation((CreateStmt *) stmt,
-													RELKIND_RELATION,
-													InvalidOid);
+							address = DefineRelation((CreateStmt *) stmt,
+													 RELKIND_RELATION,
+													 InvalidOid, NULL);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -952,16 +952,17 @@ ProcessUtilitySlow(Node *parsetree,
 												   toast_options,
 												   true);
 
-							NewRelationCreateToastTable(relOid, toast_options);
+							NewRelationCreateToastTable(address.objectId,
+														toast_options);
 						}
 						else if (IsA(stmt, CreateForeignTableStmt))
 						{
 							/* Create the table itself */
-							relOid = DefineRelation((CreateStmt *) stmt,
-													RELKIND_FOREIGN_TABLE,
-													InvalidOid);
+							address = DefineRelation((CreateStmt *) stmt,
+													 RELKIND_FOREIGN_TABLE,
+													 InvalidOid, NULL);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
-											   relOid);
+											   address.objectId);
 						}
 						else
 						{
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index fe1ddd9..9a8fa1e 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -14,8 +14,9 @@
 #ifndef HEAP_H
 #define HEAP_H
 
-#include "parser/parse_node.h"
 #include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "parser/parse_node.h"
 
 
 typedef struct RawColumnDefault
@@ -69,7 +70,8 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 Datum reloptions,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
-						 bool is_internal);
+						 bool is_internal,
+						 ObjectAddress *typaddress);
 
 extern void heap_create_init_fork(Relation rel);
 
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 5e1196d..3e28e2f 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -20,6 +20,7 @@
 #define PG_AGGREGATE_H
 
 #include "catalog/genbki.h"
+#include "catalog/objectaddress.h"
 #include "nodes/pg_list.h"
 
 /* ----------------------------------------------------------------
@@ -308,7 +309,7 @@ DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						-		-
 /*
  * prototypes for functions in pg_aggregate.c
  */
-extern Oid AggregateCreate(const char *aggName,
+extern ObjectAddress AggregateCreate(const char *aggName,
 				Oid aggNamespace,
 				char aggKind,
 				int numArgs,
diff --git a/src/include/catalog/pg_conversion_fn.h b/src/include/catalog/pg_conversion_fn.h
index 6185d21..7818272 100644
--- a/src/include/catalog/pg_conversion_fn.h
+++ b/src/include/catalog/pg_conversion_fn.h
@@ -14,7 +14,10 @@
 #ifndef PG_CONVERSION_FN_H
 #define PG_CONVERSION_FN_H
 
-extern Oid ConversionCreate(const char *conname, Oid connamespace,
+
+#include "catalog/objectaddress.h"
+
+extern ObjectAddress ConversionCreate(const char *conname, Oid connamespace,
 				 Oid conowner,
 				 int32 conforencoding, int32 contoencoding,
 				 Oid conproc, bool def);
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index af991d3..e22eb27 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -23,6 +23,7 @@
 #define PG_OPERATOR_H
 
 #include "catalog/genbki.h"
+#include "catalog/objectaddress.h"
 #include "nodes/pg_list.h"
 
 /* ----------------
@@ -1812,7 +1813,7 @@ DESCR("is contained by");
 /*
  * function prototypes
  */
-extern Oid OperatorCreate(const char *operatorName,
+extern ObjectAddress OperatorCreate(const char *operatorName,
 			   Oid operatorNamespace,
 			   Oid leftTypeId,
 			   Oid rightTypeId,
diff --git a/src/include/catalog/pg_proc_fn.h b/src/include/catalog/pg_proc_fn.h
index 791d9d6..e217188 100644
--- a/src/include/catalog/pg_proc_fn.h
+++ b/src/include/catalog/pg_proc_fn.h
@@ -14,9 +14,10 @@
 #ifndef PG_PROC_FN_H
 #define PG_PROC_FN_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/pg_list.h"
 
-extern Oid ProcedureCreate(const char *procedureName,
+extern ObjectAddress ProcedureCreate(const char *procedureName,
 				Oid procNamespace,
 				bool replace,
 				bool returnsSet,
diff --git a/src/include/catalog/pg_type_fn.h b/src/include/catalog/pg_type_fn.h
index 28c43a1..02015fc 100644
--- a/src/include/catalog/pg_type_fn.h
+++ b/src/include/catalog/pg_type_fn.h
@@ -14,14 +14,15 @@
 #ifndef PG_TYPE_FN_H
 #define PG_TYPE_FN_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/nodes.h"
 
 
-extern Oid TypeShellMake(const char *typeName,
+extern ObjectAddress TypeShellMake(const char *typeName,
 			  Oid typeNamespace,
 			  Oid ownerId);
 
-extern Oid TypeCreate(Oid newTypeOid,
+extern ObjectAddress TypeCreate(Oid newTypeOid,
 		   const char *typeName,
 		   Oid typeNamespace,
 		   Oid relationOid,
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index 5df28d3..46d22fd 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -21,11 +21,11 @@
 
 extern ObjectAddress ExecRenameStmt(RenameStmt *stmt);
 
-extern Oid	ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
+extern ObjectAddress ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
 extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 						 ObjectAddresses *objsMoved);
 
-extern Oid	ExecAlterOwnerStmt(AlterOwnerStmt *stmt);
+extern ObjectAddress ExecAlterOwnerStmt(AlterOwnerStmt *stmt);
 extern void AlterObjectOwner_internal(Relation catalog, Oid objectId,
 						  Oid new_ownerId);
 
diff --git a/src/include/commands/collationcmds.h b/src/include/commands/collationcmds.h
index dfc741b..fa99ed6 100644
--- a/src/include/commands/collationcmds.h
+++ b/src/include/commands/collationcmds.h
@@ -15,9 +15,10 @@
 #ifndef COLLATIONCMDS_H
 #define COLLATIONCMDS_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
-extern Oid	DefineCollation(List *names, List *parameters);
+extern ObjectAddress DefineCollation(List *names, List *parameters);
 extern void IsThereCollationInNamespace(const char *collname, Oid nspOid);
 
 #endif   /* COLLATIONCMDS_H */
diff --git a/src/include/commands/conversioncmds.h b/src/include/commands/conversioncmds.h
index dd8f6dc..1f32269 100644
--- a/src/include/commands/conversioncmds.h
+++ b/src/include/commands/conversioncmds.h
@@ -15,8 +15,9 @@
 #ifndef CONVERSIONCMDS_H
 #define CONVERSIONCMDS_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
-extern Oid	CreateConversionCommand(CreateConversionStmt *parsetree);
+extern ObjectAddress CreateConversionCommand(CreateConversionStmt *parsetree);
 
 #endif   /* CONVERSIONCMDS_H */
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index 495298f..8325d73 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -14,12 +14,13 @@
 #ifndef CREATEAS_H
 #define CREATEAS_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "tcop/dest.h"
 
 
-extern Oid	ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
+extern ObjectAddress ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 				  ParamListInfo params, char *completionTag);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 42bc5fa..9fec334 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -44,7 +44,7 @@ extern void dropdb(const char *dbname, bool missing_ok);
 extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
 extern Oid	AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel);
 extern Oid	AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
-extern Oid	AlterDatabaseOwner(const char *dbname, Oid newOwnerId);
+extern ObjectAddress AlterDatabaseOwner(const char *dbname, Oid newOwnerId);
 
 extern Oid	get_database_oid(const char *dbname, bool missingok);
 extern char *get_database_name(Oid dbid);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index cf586fe..785ff50 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -14,6 +14,7 @@
 #ifndef DEFREM_H
 #define DEFREM_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 #include "utils/array.h"
 
@@ -21,7 +22,7 @@
 extern void RemoveObjects(DropStmt *stmt);
 
 /* commands/indexcmds.c */
-extern Oid DefineIndex(Oid relationId,
+extern ObjectAddress DefineIndex(Oid relationId,
 			IndexStmt *stmt,
 			Oid indexRelationId,
 			bool is_alter_table,
@@ -42,12 +43,12 @@ extern bool CheckIndexCompatible(Oid oldId,
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
 
 /* commands/functioncmds.c */
-extern Oid	CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
+extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
 extern void RemoveFunctionById(Oid funcOid);
 extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
 extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
-extern Oid	AlterFunction(AlterFunctionStmt *stmt);
-extern Oid	CreateCast(CreateCastStmt *stmt);
+extern ObjectAddress AlterFunction(AlterFunctionStmt *stmt);
+extern ObjectAddress CreateCast(CreateCastStmt *stmt);
 extern void DropCastById(Oid castOid);
 extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
 						   oidvector *proargtypes, Oid nspOid);
@@ -66,17 +67,17 @@ extern void interpret_function_parameter_list(List *parameters,
 								  Oid *requiredResultType);
 
 /* commands/operatorcmds.c */
-extern Oid	DefineOperator(List *names, List *parameters);
+extern ObjectAddress DefineOperator(List *names, List *parameters);
 extern void RemoveOperatorById(Oid operOid);
 
 /* commands/aggregatecmds.c */
-extern Oid DefineAggregate(List *name, List *args, bool oldstyle,
+extern ObjectAddress DefineAggregate(List *name, List *args, bool oldstyle,
 				List *parameters, const char *queryString);
 
 /* commands/opclasscmds.c */
-extern Oid	DefineOpClass(CreateOpClassStmt *stmt);
-extern Oid	DefineOpFamily(CreateOpFamilyStmt *stmt);
-extern Oid	AlterOpFamily(AlterOpFamilyStmt *stmt);
+extern ObjectAddress DefineOpClass(CreateOpClassStmt *stmt);
+extern ObjectAddress DefineOpFamily(CreateOpFamilyStmt *stmt);
+extern void AlterOpFamily(AlterOpFamilyStmt *stmt);
 extern void RemoveOpClassById(Oid opclassOid);
 extern void RemoveOpFamilyById(Oid opfamilyOid);
 extern void RemoveAmOpEntryById(Oid entryOid);
@@ -90,36 +91,36 @@ extern Oid	get_opclass_oid(Oid amID, List *opclassname, bool missing_ok);
 extern Oid	get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok);
 
 /* commands/tsearchcmds.c */
-extern Oid	DefineTSParser(List *names, List *parameters);
+extern ObjectAddress DefineTSParser(List *names, List *parameters);
 extern void RemoveTSParserById(Oid prsId);
 
-extern Oid	DefineTSDictionary(List *names, List *parameters);
+extern ObjectAddress DefineTSDictionary(List *names, List *parameters);
 extern void RemoveTSDictionaryById(Oid dictId);
-extern Oid	AlterTSDictionary(AlterTSDictionaryStmt *stmt);
+extern ObjectAddress AlterTSDictionary(AlterTSDictionaryStmt *stmt);
 
-extern Oid	DefineTSTemplate(List *names, List *parameters);
+extern ObjectAddress DefineTSTemplate(List *names, List *parameters);
 extern void RemoveTSTemplateById(Oid tmplId);
 
-extern Oid	DefineTSConfiguration(List *names, List *parameters);
+extern ObjectAddress DefineTSConfiguration(List *names, List *parameters);
 extern void RemoveTSConfigurationById(Oid cfgId);
-extern Oid	AlterTSConfiguration(AlterTSConfigurationStmt *stmt);
+extern ObjectAddress AlterTSConfiguration(AlterTSConfigurationStmt *stmt);
 
 extern text *serialize_deflist(List *deflist);
 extern List *deserialize_deflist(Datum txt);
 
 /* commands/foreigncmds.c */
-extern Oid	AlterForeignServerOwner(const char *name, Oid newOwnerId);
+extern ObjectAddress AlterForeignServerOwner(const char *name, Oid newOwnerId);
 extern void AlterForeignServerOwner_oid(Oid, Oid newOwnerId);
-extern Oid	AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId);
+extern ObjectAddress AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId);
 extern void AlterForeignDataWrapperOwner_oid(Oid fwdId, Oid newOwnerId);
-extern Oid	CreateForeignDataWrapper(CreateFdwStmt *stmt);
-extern Oid	AlterForeignDataWrapper(AlterFdwStmt *stmt);
+extern ObjectAddress CreateForeignDataWrapper(CreateFdwStmt *stmt);
+extern ObjectAddress AlterForeignDataWrapper(AlterFdwStmt *stmt);
 extern void RemoveForeignDataWrapperById(Oid fdwId);
-extern Oid	CreateForeignServer(CreateForeignServerStmt *stmt);
-extern Oid	AlterForeignServer(AlterForeignServerStmt *stmt);
+extern ObjectAddress CreateForeignServer(CreateForeignServerStmt *stmt);
+extern ObjectAddress AlterForeignServer(AlterForeignServerStmt *stmt);
 extern void RemoveForeignServerById(Oid srvId);
-extern Oid	CreateUserMapping(CreateUserMappingStmt *stmt);
-extern Oid	AlterUserMapping(AlterUserMappingStmt *stmt);
+extern ObjectAddress CreateUserMapping(CreateUserMappingStmt *stmt);
+extern ObjectAddress AlterUserMapping(AlterUserMappingStmt *stmt);
 extern Oid	RemoveUserMapping(DropUserMappingStmt *stmt);
 extern void RemoveUserMappingById(Oid umId);
 extern void CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid);
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 9ac9fc3..7eb2156 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -43,7 +43,7 @@ extern void RemoveEventTriggerById(Oid ctrigOid);
 extern Oid	get_event_trigger_oid(const char *trigname, bool missing_ok);
 
 extern Oid	AlterEventTrigger(AlterEventTrigStmt *stmt);
-extern Oid	AlterEventTriggerOwner(const char *name, Oid newOwnerId);
+extern ObjectAddress AlterEventTriggerOwner(const char *name, Oid newOwnerId);
 extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
 
 extern bool EventTriggerSupportsObjectType(ObjectType obtype);
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index a349d6a..e004d56 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -14,6 +14,7 @@
 #ifndef EXTENSION_H
 #define EXTENSION_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
 
@@ -27,23 +28,23 @@ extern bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
-extern Oid	CreateExtension(CreateExtensionStmt *stmt);
+extern ObjectAddress CreateExtension(CreateExtensionStmt *stmt);
 
 extern void RemoveExtensionById(Oid extId);
 
-extern Oid InsertExtensionTuple(const char *extName, Oid extOwner,
+extern ObjectAddress InsertExtensionTuple(const char *extName, Oid extOwner,
 					 Oid schemaOid, bool relocatable, const char *extVersion,
 					 Datum extConfig, Datum extCondition,
 					 List *requiredExtensions);
 
-extern Oid	ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
+extern ObjectAddress ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
 
-extern Oid	ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt);
+extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt);
 
 extern Oid	get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
 
-extern Oid	AlterExtensionNamespace(List *names, const char *newschema);
+extern ObjectAddress AlterExtensionNamespace(List *names, const char *newschema);
 
 extern void AlterExtensionOwner_oid(Oid extensionOid, Oid newOwnerId);
 
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index 91e2eb5..37a81aa 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -14,6 +14,7 @@
 #ifndef MATVIEW_H
 #define MATVIEW_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "tcop/dest.h"
@@ -22,7 +23,7 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
-extern Oid ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
+extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				   ParamListInfo params, char *completionTag);
 
 extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
diff --git a/src/include/commands/policy.h b/src/include/commands/policy.h
index 0848a18..ac322e0 100644
--- a/src/include/commands/policy.h
+++ b/src/include/commands/policy.h
@@ -23,8 +23,8 @@ extern void RelationBuildRowSecurity(Relation relation);
 
 extern void RemovePolicyById(Oid policy_id);
 
-extern Oid CreatePolicy(CreatePolicyStmt *stmt);
-extern Oid AlterPolicy(AlterPolicyStmt *stmt);
+extern ObjectAddress CreatePolicy(CreatePolicyStmt *stmt);
+extern ObjectAddress AlterPolicy(AlterPolicyStmt *stmt);
 
 extern Oid get_relation_policy_oid(Oid relid, const char *policy_name,
 						bool missing_ok);
diff --git a/src/include/commands/proclang.h b/src/include/commands/proclang.h
index f43c321..f056978 100644
--- a/src/include/commands/proclang.h
+++ b/src/include/commands/proclang.h
@@ -12,9 +12,10 @@
 #ifndef PROCLANG_H
 #define PROCLANG_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
-extern Oid	CreateProceduralLanguage(CreatePLangStmt *stmt);
+extern ObjectAddress CreateProceduralLanguage(CreatePLangStmt *stmt);
 extern void DropProceduralLanguageById(Oid langOid);
 extern bool PLTemplateExists(const char *languageName);
 extern Oid	get_language_oid(const char *langname, bool missing_ok);
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index 1c0f3ca..55207a4 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -24,7 +24,7 @@ extern Oid CreateSchemaCommand(CreateSchemaStmt *parsetree,
 extern void RemoveSchemaById(Oid schemaOid);
 
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
-extern Oid	AlterSchemaOwner(const char *name, Oid newOwnerId);
+extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaOid, Oid newOwnerId);
 
 #endif   /* SCHEMACMDS_H */
diff --git a/src/include/commands/seclabel.h b/src/include/commands/seclabel.h
index 7e3dde4..661da2b 100644
--- a/src/include/commands/seclabel.h
+++ b/src/include/commands/seclabel.h
@@ -24,7 +24,7 @@ extern void DeleteSharedSecurityLabel(Oid objectId, Oid classId);
 /*
  * Statement and ESP hook support
  */
-extern Oid	ExecSecLabelStmt(SecLabelStmt *stmt);
+extern ObjectAddress ExecSecLabelStmt(SecLabelStmt *stmt);
 
 typedef void (*check_object_relabel_type) (const ObjectAddress *object,
 													   const char *seclabel);
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 1baf43d..44862bb 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -14,6 +14,7 @@
 #define SEQUENCE_H
 
 #include "access/xlogreader.h"
+#include "catalog/objectaddress.h"
 #include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "nodes/parsenodes.h"
@@ -72,8 +73,8 @@ extern Datum lastval(PG_FUNCTION_ARGS);
 
 extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS);
 
-extern Oid	DefineSequence(CreateSeqStmt *stmt);
-extern Oid	AlterSequence(AlterSeqStmt *stmt);
+extern ObjectAddress DefineSequence(CreateSeqStmt *stmt);
+extern ObjectAddress AlterSequence(AlterSeqStmt *stmt);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index f529337..2027a8c 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -22,7 +22,8 @@
 #include "utils/relcache.h"
 
 
-extern Oid	DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId);
+extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
+			   ObjectAddress *typaddress);
 
 extern void RemoveRelations(DropStmt *drop);
 
@@ -38,7 +39,7 @@ extern void AlterTableInternal(Oid relid, List *cmds, bool recurse);
 
 extern Oid	AlterTableMoveAll(AlterTableMoveAllStmt *stmt);
 
-extern Oid	AlterTableNamespace(AlterObjectSchemaStmt *stmt);
+extern ObjectAddress AlterTableNamespace(AlterObjectSchemaStmt *stmt);
 
 extern void AlterTableNamespaceInternal(Relation rel, Oid oldNspOid,
 							Oid nspOid, ObjectAddresses *objsMoved);
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index e09ac29..1a53f6c 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -109,7 +109,7 @@ extern PGDLLIMPORT int SessionReplicationRole;
 #define TRIGGER_FIRES_ON_REPLICA			'R'
 #define TRIGGER_DISABLED					'D'
 
-extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
+extern ObjectAddress CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 			  Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid,
 			  bool isInternal);
 
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index daeabfe..d9bd9d8 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -21,20 +21,20 @@
 
 #define DEFAULT_TYPDELIM		','
 
-extern Oid	DefineType(List *names, List *parameters);
+extern ObjectAddress DefineType(List *names, List *parameters);
 extern void RemoveTypeById(Oid typeOid);
-extern Oid	DefineDomain(CreateDomainStmt *stmt);
-extern Oid	DefineEnum(CreateEnumStmt *stmt);
-extern Oid	DefineRange(CreateRangeStmt *stmt);
-extern Oid	AlterEnum(AlterEnumStmt *stmt, bool isTopLevel);
-extern Oid	DefineCompositeType(RangeVar *typevar, List *coldeflist);
+extern ObjectAddress DefineDomain(CreateDomainStmt *stmt);
+extern ObjectAddress DefineEnum(CreateEnumStmt *stmt);
+extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
+extern ObjectAddress AlterEnum(AlterEnumStmt *stmt, bool isTopLevel);
+extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
 
-extern Oid	AlterDomainDefault(List *names, Node *defaultRaw);
-extern Oid	AlterDomainNotNull(List *names, bool notNull);
-extern Oid	AlterDomainAddConstraint(List *names, Node *constr);
-extern Oid	AlterDomainValidateConstraint(List *names, char *constrName);
-extern Oid AlterDomainDropConstraint(List *names, const char *constrName,
+extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
+extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
+extern ObjectAddress AlterDomainAddConstraint(List *names, Node *constr);
+extern ObjectAddress AlterDomainValidateConstraint(List *names, char *constrName);
+extern ObjectAddress AlterDomainDropConstraint(List *names, const char *constrName,
 						  DropBehavior behavior, bool missing_ok);
 
 extern void checkDomainOwner(HeapTuple tup);
@@ -42,10 +42,11 @@ extern void checkDomainOwner(HeapTuple tup);
 extern List *GetDomainConstraints(Oid typeOid);
 
 extern ObjectAddress RenameType(RenameStmt *stmt);
-extern Oid	AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype);
+extern ObjectAddress AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype);
 extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
 					   bool hasDependEntry);
-extern Oid	AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype);
+extern ObjectAddress AlterTypeNamespace(List *names, const char *newschema,
+				   ObjectType objecttype);
 extern Oid	AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, ObjectAddresses *objsMoved);
 extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
 						   bool isImplicitArray,
diff --git a/src/include/commands/view.h b/src/include/commands/view.h
index 595d266..53db76e 100644
--- a/src/include/commands/view.h
+++ b/src/include/commands/view.h
@@ -14,11 +14,12 @@
 #ifndef VIEW_H
 #define VIEW_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
 extern void validateWithCheckOption(char *value);
 
-extern Oid	DefineView(ViewStmt *stmt, const char *queryString);
+extern ObjectAddress DefineView(ViewStmt *stmt, const char *queryString);
 
 extern void StoreViewQuery(Oid viewOid, Query *viewParse, bool replace);
 
diff --git a/src/include/rewrite/rewriteDefine.h b/src/include/rewrite/rewriteDefine.h
index c112b85..5f0dc31 100644
--- a/src/include/rewrite/rewriteDefine.h
+++ b/src/include/rewrite/rewriteDefine.h
@@ -23,9 +23,9 @@
 #define RULE_FIRES_ON_REPLICA	'R'
 #define RULE_DISABLED			'D'
 
-extern Oid	DefineRule(RuleStmt *stmt, const char *queryString);
+extern ObjectAddress DefineRule(RuleStmt *stmt, const char *queryString);
 
-extern Oid DefineQueryRewrite(char *rulename,
+extern ObjectAddress DefineQueryRewrite(char *rulename,
 				   Oid event_relid,
 				   Node *event_qual,
 				   CmdType event_type,
-- 
2.1.4

0005-deparse-core-have-ALTER-EXTENSION-ADD-DROP-report-Ob.patchtext/x-diff; charset=us-asciiDownload
>From 123218f95605c8f63ad96fc85aa6f96b54658df1 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 18 Feb 2015 13:12:24 -0300
Subject: [PATCH 5/7] deparse/core: have ALTER EXTENSION ADD/DROP report
 ObjAddr of affected object

---
 src/backend/commands/extension.c | 10 +++++++++-
 src/backend/tcop/utility.c       |  3 ++-
 src/include/commands/extension.h |  3 ++-
 3 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 8a47b12..71c5d68 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2887,9 +2887,13 @@ ApplyExtensionUpdates(Oid extensionOid,
 
 /*
  * Execute ALTER EXTENSION ADD/DROP
+ *
+ * Return value is the address of the altered extension; objAddr, if not NULL,
+ * is set to the address of the added/dropped object.
  */
 ObjectAddress
-ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt)
+ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
+							   ObjectAddress *objAddr)
 {
 	ObjectAddress extension;
 	ObjectAddress object;
@@ -2914,6 +2918,10 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt)
 	object = get_object_address(stmt->objtype, stmt->objname, stmt->objargs,
 								&relation, ShareUpdateExclusiveLock, false);
 
+	Assert(object.objectSubId == 0);
+	if (objAddr)
+		*objAddr = object;
+
 	/* Permission check: must own target object, too */
 	check_object_ownership(GetUserId(), stmt->objtype, object,
 						   stmt->objname, stmt->objargs, relation);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 7627d43..33b20b8 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1191,7 +1191,8 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
+				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+											   NULL);
 				break;
 
 			case T_CreateFdwStmt:
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index e004d56..5640cac 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -39,7 +39,8 @@ extern ObjectAddress InsertExtensionTuple(const char *extName, Oid extOwner,
 
 extern ObjectAddress ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
 
-extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt);
+extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
+							   ObjectAddress *objAddress);
 
 extern Oid	get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
-- 
2.1.4

0006-Have-ALTER-SET-SCHEMA-return-the-original-schema-s-O.patchtext/x-diff; charset=us-asciiDownload
>From f651595fa4d999f719d30c19e6b5b7d3a1dc8020 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Feb 2015 13:03:29 -0300
Subject: [PATCH 6/7] Have ALTER/SET SCHEMA return the original schema's
 ObjectAddress

This lets ddl_command_end event triggers know what the original schema
was, since when they are run the catalogs have already changed.

This isn't terribly exciting in itself but it supports future event
trigger changes.
---
 src/backend/commands/alter.c     | 34 +++++++++++++++++++++++++---------
 src/backend/commands/extension.c |  6 +++++-
 src/backend/commands/tablecmds.c |  5 ++++-
 src/backend/commands/typecmds.c  |  9 +++++++--
 src/backend/tcop/utility.c       |  5 +++--
 src/include/commands/alter.h     |  3 ++-
 src/include/commands/extension.h |  3 ++-
 src/include/commands/tablecmds.h |  3 ++-
 src/include/commands/typecmds.h  |  2 +-
 9 files changed, 51 insertions(+), 19 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index e4d6d8c..81c7bed 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -393,26 +393,39 @@ ExecRenameStmt(RenameStmt *stmt)
 /*
  * Executes an ALTER OBJECT / SET SCHEMA statement.  Based on the object
  * type, the function appropriate to that type is executed.
+ *
+ * Return value is that of the altered object.  oldSchemaAddr, if not NULL, is
+ * set to the object address of the original schema.
  */
 ObjectAddress
-ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
+ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
+						  ObjectAddress *oldSchemaAddr)
 {
+	ObjectAddress	address;
+	Oid			oldNspOid;
+
 	switch (stmt->objectType)
 	{
 		case OBJECT_EXTENSION:
-			return AlterExtensionNamespace(stmt->object, stmt->newschema);
+			address = AlterExtensionNamespace(stmt->object, stmt->newschema,
+											  oldSchemaAddr ? &oldNspOid : NULL);
+			break;
 
 		case OBJECT_FOREIGN_TABLE:
 		case OBJECT_SEQUENCE:
 		case OBJECT_TABLE:
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
-			return AlterTableNamespace(stmt);
+			address = AlterTableNamespace(stmt,
+										  oldSchemaAddr ? &oldNspOid : NULL);
+			break;
 
 		case OBJECT_DOMAIN:
 		case OBJECT_TYPE:
-			return AlterTypeNamespace(stmt->object, stmt->newschema,
-									  stmt->objectType);
+			address = AlterTypeNamespace(stmt->object, stmt->newschema,
+										 stmt->objectType,
+										 oldSchemaAddr ? &oldNspOid : NULL);
+			break;
 
 			/* generic code path */
 		case OBJECT_AGGREGATE:
@@ -444,11 +457,9 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
 				catalog = heap_open(classId, RowExclusiveLock);
 				nspOid = LookupCreationNamespace(stmt->newschema);
 
-				AlterObjectNamespace_internal(catalog, address.objectId,
-											  nspOid);
+				oldNspOid = AlterObjectNamespace_internal(catalog, address.objectId,
+														  nspOid);
 				heap_close(catalog, RowExclusiveLock);
-
-				return address;
 			}
 			break;
 
@@ -457,6 +468,11 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
 				 (int) stmt->objectType);
 			return InvalidObjectAddress;	/* keep compiler happy */
 	}
+
+	if (oldSchemaAddr)
+		ObjectAddressSet(*oldSchemaAddr, NamespaceRelationId, oldNspOid);
+
+	return address;
 }
 
 /*
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 71c5d68..7ed9321 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2402,7 +2402,7 @@ extension_config_remove(Oid extensionoid, Oid tableoid)
  * Execute ALTER EXTENSION SET SCHEMA
  */
 ObjectAddress
-AlterExtensionNamespace(List *names, const char *newschema)
+AlterExtensionNamespace(List *names, const char *newschema, Oid *oldschema)
 {
 	char	   *extensionName;
 	Oid			extensionOid;
@@ -2560,6 +2560,10 @@ AlterExtensionNamespace(List *names, const char *newschema)
 							   get_namespace_name(oldNspOid))));
 	}
 
+	/* report old schema, if caller wants it */
+	if (oldschema)
+		*oldschema = oldNspOid;
+
 	systable_endscan(depScan);
 
 	relation_close(depRel, AccessShareLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 733b987..92099a2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11048,7 +11048,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
  * Execute ALTER TABLE SET SCHEMA
  */
 ObjectAddress
-AlterTableNamespace(AlterObjectSchemaStmt *stmt)
+AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
 {
 	Relation	rel;
 	Oid			relid;
@@ -11103,6 +11103,9 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt)
 
 	ObjectAddressSet(myself, RelationRelationId, relid);
 
+	if (oldschema)
+		*oldschema = oldNspOid;
+
 	/* close rel, but keep lock until commit */
 	relation_close(rel, NoLock);
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index e246719..4df7e64 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3540,11 +3540,13 @@ AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
  * Execute ALTER TYPE SET SCHEMA
  */
 ObjectAddress
-AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype)
+AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype,
+				   Oid *oldschema)
 {
 	TypeName   *typename;
 	Oid			typeOid;
 	Oid			nspOid;
+	Oid			oldNspOid;
 	ObjectAddresses *objsMoved;
 	ObjectAddress myself;
 
@@ -3563,9 +3565,12 @@ AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype)
 	nspOid = LookupCreationNamespace(newschema);
 
 	objsMoved = new_object_addresses();
-	AlterTypeNamespace_oid(typeOid, nspOid, objsMoved);
+	oldNspOid = AlterTypeNamespace_oid(typeOid, nspOid, objsMoved);
 	free_object_addresses(objsMoved);
 
+	if (oldschema)
+		*oldschema = oldNspOid;
+
 	ObjectAddressSet(myself, TypeRelationId, typeOid);
 
 	return myself;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 33b20b8..2f34c03 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -818,7 +818,7 @@ standard_ProcessUtility(Node *parsetree,
 									   context, params,
 									   dest, completionTag);
 				else
-					ExecAlterObjectSchemaStmt(stmt);
+					ExecAlterObjectSchemaStmt(stmt, NULL);
 			}
 			break;
 
@@ -1336,7 +1336,8 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
+				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
+										  NULL);
 				break;
 
 			case T_AlterOwnerStmt:
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index 46d22fd..be68890 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -21,7 +21,8 @@
 
 extern ObjectAddress ExecRenameStmt(RenameStmt *stmt);
 
-extern ObjectAddress ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
+extern ObjectAddress ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
+						  ObjectAddress *oldSchemaAddr);
 extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 						 ObjectAddresses *objsMoved);
 
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 5640cac..40ecea2 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -45,7 +45,8 @@ extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *
 extern Oid	get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
 
-extern ObjectAddress AlterExtensionNamespace(List *names, const char *newschema);
+extern ObjectAddress AlterExtensionNamespace(List *names, const char *newschema,
+						Oid *oldschema);
 
 extern void AlterExtensionOwner_oid(Oid extensionOid, Oid newOwnerId);
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 2027a8c..f269c63 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -39,7 +39,8 @@ extern void AlterTableInternal(Oid relid, List *cmds, bool recurse);
 
 extern Oid	AlterTableMoveAll(AlterTableMoveAllStmt *stmt);
 
-extern ObjectAddress AlterTableNamespace(AlterObjectSchemaStmt *stmt);
+extern ObjectAddress AlterTableNamespace(AlterObjectSchemaStmt *stmt,
+					Oid *oldschema);
 
 extern void AlterTableNamespaceInternal(Relation rel, Oid oldNspOid,
 							Oid nspOid, ObjectAddresses *objsMoved);
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index d9bd9d8..2cca88d 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -46,7 +46,7 @@ extern ObjectAddress AlterTypeOwner(List *names, Oid newOwnerId, ObjectType obje
 extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
 					   bool hasDependEntry);
 extern ObjectAddress AlterTypeNamespace(List *names, const char *newschema,
-				   ObjectType objecttype);
+				   ObjectType objecttype, Oid *oldschema);
 extern Oid	AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, ObjectAddresses *objsMoved);
 extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
 						   bool isImplicitArray,
-- 
2.1.4

0007-deparse-core-have-ALTER-DOMAIN-return-ObjAddr-of-aff.patchtext/x-diff; charset=us-asciiDownload
>From 65de6c3277d16f1615ba738743ada76380485980 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Feb 2015 13:37:50 -0300
Subject: [PATCH 7/7] deparse/core: have ALTER DOMAIN return ObjAddr of
 affected constraint

---
 src/backend/commands/typecmds.c | 71 ++++++++++++++++++++++-------------------
 src/backend/tcop/utility.c      |  3 +-
 src/include/commands/typecmds.h |  3 +-
 3 files changed, 42 insertions(+), 35 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 4df7e64..6b7874b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -108,7 +108,7 @@ static void checkEnumOwner(HeapTuple tup);
 static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
 					Oid baseTypeOid,
 					int typMod, Constraint *constr,
-					char *domainName);
+					char *domainName, ObjectAddress *constrAddr);
 
 
 /*
@@ -1076,7 +1076,7 @@ DefineDomain(CreateDomainStmt *stmt)
 			case CONSTR_CHECK:
 				domainAddConstraint(address.objectId, domainNamespace,
 									basetypeoid, basetypeMod,
-									constr, domainName);
+									constr, domainName, NULL);
 				break;
 
 				/* Other constraint types were fully processed above */
@@ -2483,7 +2483,8 @@ AlterDomainDropConstraint(List *names, const char *constrName,
  * Implements the ALTER DOMAIN .. ADD CONSTRAINT statement.
  */
 ObjectAddress
-AlterDomainAddConstraint(List *names, Node *newConstraint)
+AlterDomainAddConstraint(List *names, Node *newConstraint,
+						 ObjectAddress *constrAddr)
 {
 	TypeName   *typename;
 	Oid			domainoid;
@@ -2568,7 +2569,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
 
 	ccbin = domainAddConstraint(domainoid, typTup->typnamespace,
 								typTup->typbasetype, typTup->typtypmod,
-								constr, NameStr(typTup->typname));
+								constr, NameStr(typTup->typname), constrAddr);
 
 	/*
 	 * If requested to validate the constraint, test all values stored in the
@@ -2991,13 +2992,14 @@ checkDomainOwner(HeapTuple tup)
 static char *
 domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 					int typMod, Constraint *constr,
-					char *domainName)
+					char *domainName, ObjectAddress *constrAddr)
 {
 	Node	   *expr;
 	char	   *ccsrc;
 	char	   *ccbin;
 	ParseState *pstate;
 	CoerceToDomainValue *domVal;
+	Oid			ccoid;
 
 	/*
 	 * Assign or validate constraint name
@@ -3076,34 +3078,37 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	/*
 	 * Store the constraint in pg_constraint
 	 */
-	CreateConstraintEntry(constr->conname,		/* Constraint Name */
-						  domainNamespace,		/* namespace */
-						  CONSTRAINT_CHECK,		/* Constraint Type */
-						  false,	/* Is Deferrable */
-						  false,	/* Is Deferred */
-						  !constr->skip_validation,		/* Is Validated */
-						  InvalidOid,	/* not a relation constraint */
-						  NULL,
-						  0,
-						  domainOid,	/* domain constraint */
-						  InvalidOid,	/* no associated index */
-						  InvalidOid,	/* Foreign key fields */
-						  NULL,
-						  NULL,
-						  NULL,
-						  NULL,
-						  0,
-						  ' ',
-						  ' ',
-						  ' ',
-						  NULL, /* not an exclusion constraint */
-						  expr, /* Tree form of check constraint */
-						  ccbin,	/* Binary form of check constraint */
-						  ccsrc,	/* Source form of check constraint */
-						  true, /* is local */
-						  0,	/* inhcount */
-						  false,	/* connoinherit */
-						  false);		/* is_internal */
+	ccoid =
+		CreateConstraintEntry(constr->conname,		/* Constraint Name */
+							  domainNamespace,		/* namespace */
+							  CONSTRAINT_CHECK,		/* Constraint Type */
+							  false,	/* Is Deferrable */
+							  false,	/* Is Deferred */
+							  !constr->skip_validation,		/* Is Validated */
+							  InvalidOid,	/* not a relation constraint */
+							  NULL,
+							  0,
+							  domainOid,	/* domain constraint */
+							  InvalidOid,	/* no associated index */
+							  InvalidOid,	/* Foreign key fields */
+							  NULL,
+							  NULL,
+							  NULL,
+							  NULL,
+							  0,
+							  ' ',
+							  ' ',
+							  ' ',
+							  NULL, /* not an exclusion constraint */
+							  expr, /* Tree form of check constraint */
+							  ccbin,	/* Binary form of check constraint */
+							  ccsrc,	/* Source form of check constraint */
+							  true, /* is local */
+							  0,	/* inhcount */
+							  false,	/* connoinherit */
+							  false);		/* is_internal */
+	if (constrAddr)
+		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
 
 	/*
 	 * Return the compiled constraint expression so the calling routine can
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 2f34c03..daf5326 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1068,7 +1068,8 @@ ProcessUtilitySlow(Node *parsetree,
 							break;
 						case 'C':		/* ADD CONSTRAINT */
 							AlterDomainAddConstraint(stmt->typeName,
-													 stmt->def);
+													 stmt->def,
+													 NULL);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
 							AlterDomainDropConstraint(stmt->typeName,
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index 2cca88d..22aacfd 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -32,7 +32,8 @@ extern Oid	AssignTypeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
-extern ObjectAddress AlterDomainAddConstraint(List *names, Node *constr);
+extern ObjectAddress AlterDomainAddConstraint(List *names, Node *constr,
+						 ObjectAddress *constrAddr);
 extern ObjectAddress AlterDomainValidateConstraint(List *names, char *constrName);
 extern ObjectAddress AlterDomainDropConstraint(List *names, const char *constrName,
 						  DropBehavior behavior, bool missing_ok);
-- 
2.1.4

#29Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#28)
Re: deparsing utility commands

Alvaro,

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Thanks. I have adjusted the code to use ObjectAddress in all functions
called by ProcessUtilitySlow; the patch is a bit tedious but seems
pretty reasonable to me. As I said, there is quite a lot of churn.

Thanks for doing this!

Also attached are some patches to make some commands return info about
a secondary object regarding the execution, which can be used to know
more about what's being executed:

1) ALTER DOMAIN ADD CONSTRAINT returns (in addition to the address of
the doamin) the address of the new constraint.

2) ALTER OBJECT SET SCHEMA returns (in addition to the address of the
object) the address of the old schema.

3) ALTER EXTENSION ADD/DROP OBJECT returns (in addition to the address
of the extension) the address of the added/dropped object.

Also attached is the part of these patches that have ALTER TABLE return
the AttrNumber and OID of the affected object.

I think this is all mostly uninteresting, but thought I'd throw it out
for public review before pushing, just in case. I have fixed many
issues in the rest of the branch meanwhile, and it's looking much better
IMO, but much work remains there and will leave them for later.

Glad to hear that things are progressing well!

Subject: [PATCH 1/7] Have RENAME routines return ObjectAddress rather than OID

[...]

/*
*		renameatt		- changes the name of a attribute in a relation
+ *
+ * The returned ObjectAddress is that of the pg_class address of the column.
*/
-Oid
+ObjectAddress
renameatt(RenameStmt *stmt)

This comment didn't really come across sensibly to me. I'd suggest
instead:

The ObjectAddress returned is that of the column which was renamed.

Or something along those lines instead?

The rest of this patch looked fine to me.

Subject: [PATCH 2/7] deparse/core: have COMMENT return an ObjectAddress, not OID

[...]

Looked fine to me.

Subject: [PATCH 3/7] deparse/core: have ALTER TABLE return table OID and other data

[...]

@@ -1844,7 +1844,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)
{
@@ -1949,6 +1949,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
*/
InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
RelationGetRelid(rel), attnum, is_internal);
+
+ return attrdefOid;
}

Why isn't this returning an ObjectAddress too? It even builds one up
for you in defobject. Also, the comment should be updated to reflect
that it's now returning something.

/*
@@ -1956,8 +1958,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)
@@ -1967,6 +1971,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
List	   *varList;
int			keycount;
int16	   *attNos;
+	Oid			constrOid;
/*
* Flatten expression to string form for storage.
@@ -2018,7 +2023,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 */
@@ -2049,11 +2054,15 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,

pfree(ccbin);
pfree(ccsrc);
+
+ return constrOid;
}

Ditto here? CreateConstraintEntry would have to be updated to return
the ObjectAddress that it builds up, but there aren't all that many
callers of it..

/*
* 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
@@ -2065,7 +2074,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 */

I don't see any cases of 'list_length() == 0' in our code base. The
convention is (overwhelmingly, it seems): if (cooked_constraints == NIL)

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index f85ed93..5773298 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1111,7 +1111,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
@@ -1128,7 +1129,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,
@@ -1316,6 +1317,8 @@ index_constraint_create(Relation heapRelation,
heap_freetuple(indexTuple);
heap_close(pg_index, RowExclusiveLock);
}
+
+	return conOid;
}

Ditto here, it could return 'referenced' instead (which doesn't seem
like a very good name for that variable, if you ask me.. that's kind of
a side-note, but 'referenced' to me makes it sound like it's the *other
relation*, but the ObjectAddress constructed is clearly that of a
constraint and not a table).

@@ -3410,78 +3412,95 @@ static void
ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode)
{

Looking through this, I think I realize why you might have decided to
use the simpler "just the Oid" in many cases.. However, what that means
is that through this function, you have to know the subtype to know what
the Oid actually represents or if colno is supposed to be set or not.
Using a single ObjectAddress for all of these would mean that you know
what class the object is and that informs if you need a sub-id or not or
if the Oid should be set or not (obviously, lots of the below switch
paths end up setting a colno without newoid being set..).

I certainly understand that we don't want to transform Oid ->
ObjectAddress across the entire code base, but at these higher level
functions which are working with lots of different object classes, some
of which could be just class+Oid and some of which are class+Oid+sub-id,
I'm thinking we'd be better off using ObjectAddress.

Apologies for generating lots of work. :(

case AT_ReplicaIdentity:
-			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def,
+								  lockmode);

This is just a whitespace change..? I don't think I disagree with it,
but would prefer to not have just-whitespace changes in the patch if we
can avoid it, makes me think I'm missing something. :)

-static void
+static AttrNumber
ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
{
HeapTuple	tuple;
@@ -5137,18 +5161,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? */

If we need that information to be returned, I think I'd rather do it in
another way rather than returning InvalidAttrNumber (or
InvalidObjectAddress) as a special value that means "did nothing".

Subject: [PATCH 4/7] deparse/core: return ObjAddress rather than Oid

[...]

@@ -1004,6 +1004,10 @@ AddNewRelationType(const char *typeName,
*	use_user_acl: TRUE if should look for user-defined default permissions;
*		if FALSE, relacl is always set NULL
*	allow_system_table_mods: TRUE to allow creation in system namespaces
+ *	is_internal: is this a system-generated catalog?

Shouldn't adding the comment for is_internal be done independently?
It's clearly missing in master and fixing that hasn't got anything to do
with the other changes happening here.

This patch did make me think that there's quite a few places which could
use ObjectAddressSet() that aren't (even after this patch). I don't
think we need to stress over that, but it might be nice to mark that
down as a TODO for someone to go through all the places where we
construct an ObjectAddress and update them to use the new macro.

@@ -613,10 +614,14 @@ DefineIndex(Oid relationId,
stmt->concurrent, !check_rights,
stmt->if_not_exists);

+	address.classId = RelationRelationId;
+	address.objectId = indexRelationId;
+	address.objectSubId = 0;

Like this bit of code (wrt using the macro)?

@@ -772,7 +775,7 @@ DefineOpFamily(CreateOpFamilyStmt *stmt)
* other commands called ALTER OPERATOR FAMILY exist, but go through
* different code paths.
*/
-Oid
+void
AlterOpFamily(AlterOpFamilyStmt *stmt)
{
Oid amoid, /* our AM's oid */
@@ -826,8 +829,6 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid,
maxOpNumber, maxProcNumber,
stmt->items);
-
- return opfamilyoid;
}

This feels a bit funny to me..? Why not construct and return an
ObjectAddress like the other Alter functions?

@@ -690,13 +694,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);

+	address.classId = RelationRelationId;
+	address.objectId = relationId;
+	address.objectSubId = 0;

macro?

@@ -1366,7 +1367,6 @@ renametrig(RenameStmt *stmt)
return address;
}

-
/*
* EnableDisableTrigger()
*

whitespace change

@@ -2249,19 +2253,25 @@ AlterDomainDefault(List *names, Node *defaultRaw)

InvokeObjectPostAlterHook(TypeRelationId, domainoid, 0);

+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;

macro?

@@ -2363,11 +2374,15 @@ AlterDomainNotNull(List *names, bool notNull)

InvokeObjectPostAlterHook(TypeRelationId, domainoid, 0);

+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;

macro?

@@ -2434,6 +2450,11 @@ AlterDomainDropConstraint(List *names, const char *constrName,
found = true;
}
}
+
+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;

macro?

@@ -2471,6 +2492,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
Form_pg_type typTup;
Constraint *constr;
char *ccbin;
+ ObjectAddress address;

/* Make a TypeName so we can use standard type lookup machinery */
typename = makeTypeNameFromNameList(names);
@@ -2555,10 +2577,14 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
if (!constr->skip_validation)
validateDomainConstraint(domainoid, ccbin);

+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;

macro?

@@ -2654,6 +2681,10 @@ AlterDomainValidateConstraint(List *names, char *constrName)
InvokeObjectPostAlterHook(ConstraintRelationId,
HeapTupleGetOid(copyTuple), 0);

+	address.classId = TypeRelationId;
+	address.objectId = domainoid;
+	address.objectSubId = 0;

macro?

@@ -208,16 +209,20 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
/* OK, let's do it. */
AlterTableInternal(viewOid, atcmds, true);

+		address.classId = RelationRelationId;
+		address.objectId = viewOid;
+		address.objectSubId = 0;

macro?

Rest of this patched looked good to me.

Subject: [PATCH 5/7] deparse/core: have ALTER EXTENSION ADD/DROP report ObjAddr of affected object

[...]

/*
* Execute ALTER EXTENSION ADD/DROP
+ *
+ * Return value is the address of the altered extension; objAddr, if not NULL,
+ * is set to the address of the added/dropped object.
*/

I would make those two sentences (or a bulletted list, perhaps). The
first sentence fragment and the second have really got nothing to do
with each other and can stand alone, so using a semi-colon seems a bit
off. Other places you've made it very clear with "Output Argument" or
similar, that'd work too.

Otherwise, this looked fine to me.

Subject: [PATCH 6/7] Have ALTER/SET SCHEMA return the original schema's ObjectAddress

[...]

@@ -393,26 +393,39 @@ ExecRenameStmt(RenameStmt *stmt)
/*
* Executes an ALTER OBJECT / SET SCHEMA statement.  Based on the object
* type, the function appropriate to that type is executed.
+ *
+ * Return value is that of the altered object.  oldSchemaAddr, if not NULL, is
+ * set to the object address of the original schema.
*/

This is better, but would probably be even better if it was on a seperate
line or a bulletted list or something.

--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2402,7 +2402,7 @@ extension_config_remove(Oid extensionoid, Oid tableoid)
* Execute ALTER EXTENSION SET SCHEMA
*/
ObjectAddress
-AlterExtensionNamespace(List *names, const char *newschema)
+AlterExtensionNamespace(List *names, const char *newschema, Oid *oldschema)
{
char	   *extensionName;
Oid			extensionOid;

Output parameter is not an ObjectAddress for this one? The other case
where a schema was an output parameter, it was.. Feels a little odd.

Also, the function comment needs updating to reflect the new output
parameter.

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 733b987..92099a2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11048,7 +11048,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* Execute ALTER TABLE SET SCHEMA
*/
ObjectAddress
-AlterTableNamespace(AlterObjectSchemaStmt *stmt)
+AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
{
Relation	rel;
Oid			relid;

Ditto here.

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index e246719..4df7e64 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3540,11 +3540,13 @@ AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId,
* Execute ALTER TYPE SET SCHEMA
*/
ObjectAddress
-AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype)
+AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype,
+				   Oid *oldschema)

And here.

The rest of this patch looked fine to me.

Subject: [PATCH 7/7] deparse/core: have ALTER DOMAIN return ObjAddr of affected constraint

[...]

@@ -2483,7 +2483,8 @@ AlterDomainDropConstraint(List *names, const char *constrName,
* Implements the ALTER DOMAIN .. ADD CONSTRAINT statement.
*/
ObjectAddress
-AlterDomainAddConstraint(List *names, Node *newConstraint)
+AlterDomainAddConstraint(List *names, Node *newConstraint,
+						 ObjectAddress *constrAddr)
{
TypeName   *typename;
Oid			domainoid;

Function header comment needs updating.

@@ -2991,13 +2992,14 @@ checkDomainOwner(HeapTuple tup)
static char *
domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
int typMod, Constraint *constr,
-					char *domainName)
+					char *domainName, ObjectAddress *constrAddr)

Function header comment needs updating.

Rest of this patch looked fine to me.

Thanks for all of this! Obviously, that was just a (well, not so) quick
review; I didn't try to compile or run it or anything.

Not sure if it's most helpful for me to continue to provide reviews or
if it'd be useful for me to help with actually making some of these
changes- please let me know your thoughts and I'll do my best to help.

Thanks again!

Stephen

#30Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#1)
Re: deparsing utility commands

Hi,

On 2015-02-15 01:48:15 -0300, Alvaro Herrera wrote:

This is a repost of the patch to add CREATE command deparsing support to
event triggers. It now supports not only CREATE but also ALTER and
other commands such as GRANT/REVOKE, COMMENT ON and SECURITY LABEL.
This patch series is broken up in a rather large number of patches, but
my intention would be to commit separately only the first 6 patches; the
remaining parts are split up only so that it's easy to see how deparsing
each patch is implemented, and would be committed as a single changeset
(but before that I need a bunch of extra fixes and additions to other
parts.)

I find the split of the patches not really helpful - it makes
fixing/cleaning up things unnecessarily complicated. I'd like most of
the individual command commits. Obviously the preparatory stuff that'll
be independely committed should stay separate. I also like that the
infrastructure patch is split of; same with the more wildly different
patches like support for GRANT.

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

#31Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#29)
Re: deparsing utility commands

Stephen,

Thanks for the review. I have pushed these patches, squashed in one
commit, with the exception of the one that changed ALTER TABLE. You had
enough comments about that one that I decided to put it aside for now,
and get the other ones done. I will post a new series shortly.

I fixed (most of?) the issues you pointed out; some additional comments:

Stephen Frost wrote:

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

@@ -1004,6 +1004,10 @@ AddNewRelationType(const char *typeName,
*	use_user_acl: TRUE if should look for user-defined default permissions;
*		if FALSE, relacl is always set NULL
*	allow_system_table_mods: TRUE to allow creation in system namespaces
+ *	is_internal: is this a system-generated catalog?

Shouldn't adding the comment for is_internal be done independently?
It's clearly missing in master and fixing that hasn't got anything to do
with the other changes happening here.

That was pushed separately and backpatched to 9.3. Turns out it was my
own very old mistake.

This patch did make me think that there's quite a few places which could
use ObjectAddressSet() that aren't (even after this patch). I don't
think we need to stress over that, but it might be nice to mark that
down as a TODO for someone to go through all the places where we
construct an ObjectAddress and update them to use the new macro.

Agreed. I fixed the ones in these patches, but of course there's a lot
of them in other places.

@@ -772,7 +775,7 @@ DefineOpFamily(CreateOpFamilyStmt *stmt)
* other commands called ALTER OPERATOR FAMILY exist, but go through
* different code paths.
*/
-Oid
+void
AlterOpFamily(AlterOpFamilyStmt *stmt)
{
Oid amoid, /* our AM's oid */
@@ -826,8 +829,6 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid,
maxOpNumber, maxProcNumber,
stmt->items);
-
- return opfamilyoid;
}

This feels a bit funny to me..? Why not construct and return an
ObjectAddress like the other Alter functions?

The reason for not changing AlterOpFamily is that it needs completely
separate support for event triggers -- a simple ObjectAddress is not
enough. If you had a look at the support for GrantStmt in the rest of
the series, you have an idea of what supporting this one needs: we need
to store additional information somewhere in the event trigger queue.
Most likely, this function will end up returning an ObjectAddress as
well, but I want to write the deparse code first to make sure the change
will make sense.

In the committed patch, I ended up not changing this from Oid to void;
that was just pointless churn.

--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2402,7 +2402,7 @@ extension_config_remove(Oid extensionoid, Oid tableoid)
* Execute ALTER EXTENSION SET SCHEMA
*/
ObjectAddress
-AlterExtensionNamespace(List *names, const char *newschema)
+AlterExtensionNamespace(List *names, const char *newschema, Oid *oldschema)
{
char	   *extensionName;
Oid			extensionOid;

Output parameter is not an ObjectAddress for this one? The other case
where a schema was an output parameter, it was.. Feels a little odd.

What's going on here is that this function is not called directly by
ProcessUtilitySlow, but rather through ExecAlterObjectSchemaStmt(); it
seemed simpler to keep the OID return here, and only build the
ObjectAddress when needed. I did it that way because there's a path
through AlterExtensionNamespace itself that goes back through
AlterObjectNamespace_oid that requires the OID, and the address would
just be clutter most of the time. It seemed simpler. Not that much of
an issue anyhow, since this only affects four functions or so; it's
easily changed if necessary.

Also, the function comment needs updating to reflect the new output
parameter.

A large number of these functions were not documenting their APIs at
all, and most of them seemed self-explanatory. I updated the ones for
which it seemed worthwhile.

Not sure if it's most helpful for me to continue to provide reviews or
if it'd be useful for me to help with actually making some of these
changes- please let me know your thoughts and I'll do my best to help.

Given the number of patches in the series, I think it's easiest for both
you and for me to give comments. I rebase this stuff pretty frequently
for submission.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#32Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#31)
Re: deparsing utility commands

Alvaro,

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Thanks for the review. I have pushed these patches, squashed in one
commit, with the exception of the one that changed ALTER TABLE. You had
enough comments about that one that I decided to put it aside for now,
and get the other ones done. I will post a new series shortly.

I look forward to seeing the new series posted soon.

I fixed (most of?) the issues you pointed out; some additional comments:

These all looked good to me.

Thanks!

Stephen

#33Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andres Freund (#30)
29 attachment(s)
Re: deparsing utility commands

Andres Freund wrote:

Hi,

On 2015-02-15 01:48:15 -0300, Alvaro Herrera wrote:

This is a repost of the patch to add CREATE command deparsing support to
event triggers. It now supports not only CREATE but also ALTER and
other commands such as GRANT/REVOKE, COMMENT ON and SECURITY LABEL.
This patch series is broken up in a rather large number of patches, but
my intention would be to commit separately only the first 6 patches; the
remaining parts are split up only so that it's easy to see how deparsing
each patch is implemented, and would be committed as a single changeset
(but before that I need a bunch of extra fixes and additions to other
parts.)

I find the split of the patches not really helpful - it makes
fixing/cleaning up things unnecessarily complicated. I'd like most of
the individual command commits. Obviously the preparatory stuff that'll
be independely committed should stay separate. I also like that the
infrastructure patch is split of; same with the more wildly different
patches like support for GRANT.

Here's a new series. I have reduced the number of patches, per your
request. (Of course, a number of those preparatory commits have gotten
pushed.)

One thing that Stephen commented on was the ALTER TABLE preparatory
patch. He said why not return ObjectAddress from the subcommand
routines instead of just Oid/attnum? The reason is that to interpret
the address correctly you will still need a lot more context; the OID
and attnum are only part of the truth anyway. I think there are a small
number of cases where we could meaningfully return an ObjectAddress and
have the whole thing be useful, but I'm not sure it's worth the bother.

In patch 0004, I removed most of the Stash() calls in ProcessUtility,
instead adding one at the bottom that takes care of most of the simple
cases. That means that a developer adding support for something new in
ProcessUtilitySlow without realizing that something must be added to the
command stash will get an error fairly early, which I think is helpful.

Patch 0021 (fixing a bug in SECURITY LABEL support) I'm not really sure
about. I don't like modifying a parse node during execution -- seems a
bad habit. It seems better to me to return the chosen security label as
an ObjectAddress in ExecSecLabelStmt, as pass that as "secondaryOid" to
the deparse_utility.c routine.

In patch 0023 (CREATE/ALTER POLICY support) I ran into trouble. I
represent the role in the json like this:
tmp = new_objtree_VA("TO %{role:, }I", 0);
which means that role names are quoted as identifiers. This means it
doesn't work as soon as we get a command referencing PUBLIC (which many
in the regression test do, because when the TO clause is omitted, PUBLIC
is the default). So this ends up as "PUBLIC" and everything goes
downhill. I'm not sure what to do about this, except to invent a new
%{} code.

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

Attachments:

0002-deparse-core-have-ALTER-TABLE-return-table-OID-and-o.patchtext/x-diff; charset=us-asciiDownload
>From 915ef30d4db031b05a7cb8acdc86049d554cb467 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 02/29] deparse/core: have ALTER TABLE return table OID and
 other data

Currently, most if not all CREATE commands return the OID of the created
object.  This is useful for event triggers to try to figure out exactly
what happened.  This patch extends this idea a bit further to cover
ALTER TABLE as well: auxilliary info such as a second OID or a column
number are also returned for each subcommand of the ALTER TABLE.  This
OID or attnum can later be used to determine, for instance, which
constraint was added by ALTER TABLE ADD CONSTRAINT, or which parent got
dropped from the inherit-from parents list by NO INHERIT.  This makes it
possible to decode with precision exactly what happened during execution
of any ALTER TABLE command.

There is no user-visible change here; this patch makes the info
available for later.
---
 src/backend/catalog/heap.c          |  40 +++--
 src/backend/catalog/index.c         |   7 +-
 src/backend/catalog/pg_constraint.c |   2 +
 src/backend/commands/tablecmds.c    | 307 ++++++++++++++++++++++++------------
 src/include/catalog/heap.h          |   3 +-
 src/include/catalog/index.h         |   2 +-
 6 files changed, 244 insertions(+), 117 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5ce4395..9675d21 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -97,7 +97,7 @@ static ObjectAddress 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,
@@ -1853,7 +1853,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)
 {
@@ -1958,6 +1958,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	 */
 	InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
 								  RelationGetRelid(rel), attnum, is_internal);
+
+	return attrdefOid;
 }
 
 /*
@@ -1965,8 +1967,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)
@@ -1976,6 +1980,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
+	Oid			constrOid;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2027,7 +2032,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 */
@@ -2058,11 +2063,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
@@ -2074,7 +2083,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 */
 
 	/*
@@ -2091,12 +2100,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:
@@ -2184,6 +2195,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,
@@ -2204,10 +2216,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;
@@ -2227,6 +2240,7 @@ AddRelationNewConstraints(Relation rel,
 	{
 		Constraint *cdef = (Constraint *) lfirst(cell);
 		char	   *ccname;
+		Oid			constrOid;
 
 		if (cdef->contype != CONSTR_CHECK)
 			continue;
@@ -2336,13 +2350,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 f85ed93..5773298 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1111,7 +1111,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
@@ -1128,7 +1129,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,
@@ -1316,6 +1317,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 77fe918..3c756f8 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 623e6bf..fc2c8a9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -282,9 +282,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);
@@ -326,26 +326,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,
 				bool is_view, 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);
@@ -353,20 +353,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,
@@ -377,9 +377,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);
@@ -392,7 +392,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,
+static Oid ATExecClusterOn(Relation rel, const char *indexName,
 				LOCKMODE lockmode);
 static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
 static bool ATPrepChangePersistence(Relation rel, bool toLogged);
@@ -407,10 +407,10 @@ static void ATExecEnableDisableTrigger(Relation rel, char *trigname,
 static void ATExecEnableDisableRule(Relation rel, char *rulename,
 						char fires_when, LOCKMODE lockmode);
 static void ATPrepAddInherit(Relation child_rel);
-static void ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
-static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
+static Oid ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
+static Oid ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
-static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
+static Oid ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
 static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
 static void ATExecGenericOptions(Relation rel, List *options);
@@ -623,6 +623,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;
@@ -1741,6 +1742,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;
@@ -3416,78 +3418,95 @@ static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
+	AttrNumber	colno = InvalidAttrNumber;
+	Oid			newoid = InvalidOid;
+
+	/* temporary measure to silence unused-variable compiler warnings */
+	(void) colno;
+	(void) newoid;
+
 	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,
@@ -3500,10 +3519,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),
@@ -3511,7 +3532,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);
@@ -3600,19 +3621,20 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 
 		case AT_AddInherit:
-			ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
+			newoid = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
 			break;
 		case AT_DropInherit:
-			ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
+			newoid = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
 			break;
 		case AT_AddOf:
-			ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
+			newoid = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
 			break;
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
 		case AT_ReplicaIdentity:
-			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def,
+								  lockmode);
 			break;
 		case AT_EnableRowSecurity:
 			ATExecEnableRowSecurity(rel);
@@ -4613,7 +4635,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)
@@ -4698,7 +4720,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					  colDef->colname, RelationGetRelationName(rel))));
 
 			heap_close(attrdesc, RowExclusiveLock);
-			return;
+			return InvalidAttrNumber;
 		}
 	}
 
@@ -4944,6 +4966,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 		heap_close(childrel, NoLock);
 	}
+
+	return newattnum;
 }
 
 /*
@@ -5058,7 +5082,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;
@@ -5143,18 +5167,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)
 {
@@ -5198,18 +5226,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)
 {
@@ -5260,6 +5292,8 @@ ATExecColumnDefault(Relation rel, const char *colName,
 		AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
 								  false, true, false);
 	}
+
+	return attnum;
 }
 
 /*
@@ -5289,13 +5323,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);
@@ -5330,7 +5365,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\"",
@@ -5349,9 +5385,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)
 {
@@ -5359,6 +5397,7 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	HeapTuple	tuple,
 				newtuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 	Datum		datum,
 				newOptions;
 	bool		isnull;
@@ -5377,7 +5416,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\"",
@@ -5416,12 +5456,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;
@@ -5429,6 +5471,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);
@@ -5461,7 +5504,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\"",
@@ -5491,6 +5535,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return attnum;
 }
 
 
@@ -5715,8 +5761,10 @@ 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 void
+static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
@@ -5759,12 +5807,14 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 		RelationPreserveStorage(irel->rd_node, true);
 		index_close(irel, NoLock);
 	}
+
+	return address.objectId;
 }
 
 /*
  * ALTER TABLE ADD CONSTRAINT USING INDEX
  */
-static void
+static Oid
 ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 						 IndexStmt *stmt, LOCKMODE lockmode)
 {
@@ -5774,6 +5824,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	IndexInfo  *indexInfo;
 	char	   *constraintName;
 	char		constraintType;
+	Oid			conOid;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(OidIsValid(index_oid));
@@ -5818,30 +5869,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));
 
 	/*
@@ -5852,9 +5907,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:
@@ -5885,17 +5941,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.
  *
@@ -5914,7 +5975,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)
@@ -5923,6 +5984,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)
@@ -5965,6 +6027,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 */
@@ -5980,7 +6049,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.
@@ -5988,7 +6057,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
@@ -6026,16 +6095,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)
 {
@@ -6434,6 +6506,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Close pk table, but keep lock until we've committed.
 	 */
 	heap_close(pkrel, NoLock);
+
+	return constrOid;
 }
 
 /*
@@ -6446,7 +6520,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)
 {
@@ -6457,6 +6531,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;
@@ -6516,6 +6591,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);
 
@@ -6563,6 +6640,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 /*
@@ -6573,7 +6652,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)
 {
@@ -6583,6 +6662,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);
 
@@ -6624,9 +6704,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;
 
 			/*
@@ -6641,7 +6722,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 conid);
+										 constrOid);
 			heap_close(refrel, NoLock);
 
 			/*
@@ -6722,6 +6803,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 
@@ -7808,7 +7891,7 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
 	}
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
@@ -8202,9 +8285,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 	/* Cleanup */
 	heap_freetuple(heapTup);
+
+	return attnum;
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnGenericOptions(Relation rel,
 								const char *colName,
 								List *options,
@@ -8223,9 +8308,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);
@@ -8252,7 +8338,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)));
@@ -8301,6 +8388,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	heap_close(attrel, RowExclusiveLock);
 
 	heap_freetuple(newtuple);
+
+	return attnum;
 }
 
 /*
@@ -8942,7 +9031,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;
@@ -8960,6 +9049,8 @@ ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 
 	/* And do the work */
 	mark_index_clustered(rel, indexOid, false);
+
+	return indexOid;
 }
 
 /*
@@ -9660,11 +9751,12 @@ ATPrepAddInherit(Relation child_rel)
 				 errmsg("cannot change inheritance of typed table")));
 }
 
-static void
+static Oid
 ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 {
 	Relation	parent_rel,
 				catalogRelation;
+	Oid			parent_oid;
 	SysScanDesc scan;
 	ScanKeyData key;
 	HeapTuple	inheritsTuple;
@@ -9784,11 +9876,15 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 							 inhseqno + 1,
 							 catalogRelation);
 
+	parent_oid = RelationGetRelid(parent_rel);
+
 	/* Now we're done with pg_inherits */
 	heap_close(catalogRelation, RowExclusiveLock);
 
 	/* keep our lock on the parent relation until commit */
 	heap_close(parent_rel, NoLock);
+
+	return parent_oid;
 }
 
 /*
@@ -10057,10 +10153,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
  * coninhcount and conislocal for inherited constraints are adjusted in
  * exactly the same way.
  */
-static void
+static Oid
 ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 {
 	Relation	parent_rel;
+	Oid			parent_oid;
 	Relation	catalogRelation;
 	SysScanDesc scan;
 	ScanKeyData key[3];
@@ -10229,6 +10326,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 		}
 	}
 
+	parent_oid = RelationGetRelid(parent_rel);
+
 	systable_endscan(scan);
 	heap_close(catalogRelation, RowExclusiveLock);
 
@@ -10247,6 +10346,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 
 	/* keep our lock on the parent relation until commit */
 	heap_close(parent_rel, NoLock);
+
+	return parent_oid;
 }
 
 /*
@@ -10304,8 +10405,10 @@ drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid)
  * TABLE OF.  All attname, atttypid, atttypmod and attcollation must match.  The
  * subject table must not have inheritance parents.  These restrictions ensure
  * that you cannot create a configuration impossible with CREATE TABLE OF alone.
+ *
+ * The OID of the type is returned.
  */
-static void
+static Oid
 ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 {
 	Oid			relid = RelationGetRelid(rel);
@@ -10434,6 +10537,8 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	heap_close(relationRelation, RowExclusiveLock);
 
 	ReleaseSysCache(typetuple);
+
+	return typeid;
 }
 
 /*
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 573b2de..9a8fa1e 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -28,6 +28,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 */
@@ -101,7 +102,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 e7cc7a0..e22ef8f 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -63,7 +63,7 @@ extern Oid index_create(Relation heapRelation,
 			 bool is_internal,
 			 bool if_not_exists);
 
-extern void index_constraint_create(Relation heapRelation,
+extern Oid index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
 						const char *constraintName,
-- 
2.1.4

0003-deparse-infrastructure-needed-for-command-deparsing.patchtext/x-diff; charset=us-asciiDownload
>From c3da225a65717ae46238e2e1698c8d9f00affc3d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 24 Sep 2014 15:53:04 -0300
Subject: [PATCH 03/29] deparse: infrastructure needed for command deparsing

This patch introduces unused infrastructure for command deparsing.
There are three main parts:

1. A list (stash) of executed commands in Node format, stored in the
event trigger state.  At ddl_command_end, the stored items can be
extracted.  For now this only support "basic" commands (in particular
not ALTER TABLE or GRANT).  It's useful enough to cover all CREATE
command as well as many simple ALTER forms.

2. Support code to enable writing routines that convert the Node format
into a JSON blob.  This JSON representation allows extracting and
modifying individual parts of the command.

3. Code to convert the JSON blobs back into plain strings.

No actual routines to convert Node into JSON is provided by this patch;
that is left to later patches.  This split is only presented as is for
ease of review.
---
 src/backend/commands/event_trigger.c | 267 +++++++++-
 src/backend/tcop/Makefile            |   2 +-
 src/backend/tcop/deparse_utility.c   | 992 +++++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           |   2 +
 src/backend/utils/adt/Makefile       |   2 +-
 src/backend/utils/adt/ddl_json.c     | 676 ++++++++++++++++++++++++
 src/backend/utils/adt/format_type.c  | 139 ++++-
 src/include/catalog/pg_proc.h        |   5 +-
 src/include/commands/event_trigger.h |   5 +
 src/include/commands/extension.h     |   2 +-
 src/include/nodes/parsenodes.h       |   1 +
 src/include/tcop/deparse_utility.h   |  59 +++
 src/include/utils/builtins.h         |   7 +
 13 files changed, 2144 insertions(+), 15 deletions(-)
 create mode 100644 src/backend/tcop/deparse_utility.c
 create mode 100644 src/backend/utils/adt/ddl_json.c
 create mode 100644 src/include/tcop/deparse_utility.h

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 4bcc327..2cd7b3d 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -25,16 +25,20 @@
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/trigger.h"
 #include "funcapi.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -52,7 +56,9 @@ typedef struct EventTriggerQueryState
 	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite event */
 	int			table_rewrite_reason;	/* AT_REWRITE reason */
 
+	bool		commandCollectionInhibited;
 	MemoryContext cxt;
+	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -71,6 +77,7 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
 	{"AGGREGATE", true},
 	{"CAST", true},
@@ -1066,6 +1073,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CAST:
 		case OBJECT_COLUMN:
 		case OBJECT_COLLATION:
+		case OBJECT_COMPOSITE:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFACL:
 		case OBJECT_DEFAULT:
@@ -1201,14 +1209,6 @@ EventTriggerBeginCompleteQuery(void)
 	EventTriggerQueryState *state;
 	MemoryContext cxt;
 
-	/*
-	 * Currently, sql_drop and table_rewrite events are the only reason to
-	 * have event trigger state at all; so if there are none, don't install
-	 * one.
-	 */
-	if (!trackDroppedObjectsNeeded())
-		return false;
-
 	cxt = AllocSetContextCreate(TopMemoryContext,
 								"event trigger state",
 								ALLOCSET_DEFAULT_MINSIZE,
@@ -1219,7 +1219,9 @@ EventTriggerBeginCompleteQuery(void)
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
-
+	state->commandCollectionInhibited = currentEventTriggerState ?
+		currentEventTriggerState->commandCollectionInhibited : false;
+	state->stash = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
 
@@ -1544,3 +1546,250 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
 }
+
+/*-------------------------------------------------------------------------
+ * Support for DDL command deparsing
+ *
+ * The routines below enable an event trigger function to obtain a list of
+ * DDL commands as they are executed, in a "normalized" format.  There are
+ * four main pieces to this feature:
+ *
+ * 1) Within ProcessUtilitySlow, or some sub-routine thereof, each DDL
+ * command must add an internal representation to the "commands stash",
+ * using the routines below.
+ *
+ * 2) Some time after that, the ddl_command_end event trigger occurs, and the
+ * command stash is made available to the event trigger function by way of
+ * a set-returning function called pg_event_trigger_get_creation_commands().
+ * XXX since this feature also captures ALTER and other commands, this
+ * function should probably be renamed.
+ *
+ * 3) deparse_utility.c knows how to interpret those internal formats and
+ * convert them into a JSON representation.  That representation has been
+ * designed to allow the event trigger function to inspect or modify the
+ * tree.
+ *
+ * 4) ddl_json.c knows how to convert the JSON representation back into
+ * a string corresponding to a valid command.  (This step is optional; the
+ * JSON itself could be stored directly into a table, for example for audit
+ * purposes.)
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * EventTriggerStashCommand
+ * 		Save data about a simple DDL command that was just executed
+ *
+ * address identifies the object being operated on.  secondaryObject is an
+ * object address that was related in some way to the executed command; its
+ * meaning is command-specific.
+ *
+ * For instance, for an ALTER obj SET SCHEMA command, objtype is the type of
+ * object being moved, objectId is its OID, and secondaryOid is the OID of the
+ * old schema.  (The destination schema OID can be obtained by catalog lookup
+ * of the object.)
+ */
+void
+EventTriggerStashCommand(ObjectAddress address, ObjectAddress *secondaryObject,
+						 Node *parsetree)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_Simple;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.simple.address = address;
+	stashed->d.simple.secondaryObject =
+		secondaryObject ? *secondaryObject : InvalidObjectAddress;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+void
+EventTriggerInhibitCommandCollection(void)
+{
+	currentEventTriggerState->commandCollectionInhibited = true;
+}
+
+void
+EventTriggerUndoInhibitCommandCollection(void)
+{
+	currentEventTriggerState->commandCollectionInhibited = false;
+}
+
+Datum
+pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_get_creation_commands()")));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->stash)
+	{
+		StashedCommand *cmd = lfirst(lc);
+		char	   *command;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid; in those cases, return an empty
+		 * command instead of trying to soldier on.
+		 *
+		 * One might think that a viable alternative would be to look up the
+		 * Oid of the existing object and run the deparse with that.  But since
+		 * the parse tree might be different from the one that created the
+		 * object in the first place, we might not end up in a consistent state
+		 * anyway.
+		 */
+		if (cmd->type == SCT_Simple &&
+			!OidIsValid(cmd->d.simple.address.objectId))
+			continue;
+
+		command = deparse_utility_command(cmd);
+
+		/*
+		 * Some parse trees return NULL when deparse is attempted; we don't
+		 * emit anything for them.
+		 */
+		if (command != NULL)
+		{
+			Datum		values[9];
+			bool		nulls[9];
+			ObjectAddress addr;
+			int			i = 0;
+
+			MemSet(nulls, 0, sizeof(nulls));
+
+			if (cmd->type == SCT_Simple)
+			{
+				const char *tag;
+				char	   *identity;
+				char	   *type;
+				char	   *schema = NULL;
+
+				if (cmd->type == SCT_Simple)
+					addr = cmd->d.simple.address;
+
+				tag = CreateCommandTag(cmd->parsetree);
+
+				type = getObjectTypeDescription(&addr);
+				identity = getObjectIdentity(&addr);
+
+				/*
+				 * Obtain schema name, if any ("pg_temp" if a temp object).  If
+				 * the object class is not in the supported list here, we
+				 * assume it's a schema-less object type, and thus "schema"
+				 * remains set to NULL.
+				 */
+				if (is_objectclass_supported(addr.classId))
+				{
+					AttrNumber	nspAttnum;
+
+					nspAttnum = get_object_attnum_namespace(addr.classId);
+					if (nspAttnum != InvalidAttrNumber)
+					{
+						Relation	catalog;
+						HeapTuple	objtup;
+						Oid			schema_oid;
+						bool		isnull;
+
+						catalog = heap_open(addr.classId, AccessShareLock);
+						objtup = get_catalog_object_by_oid(catalog,
+														   addr.objectId);
+						if (!HeapTupleIsValid(objtup))
+							elog(ERROR, "cache lookup failed for object %u/%u",
+								 addr.classId, addr.objectId);
+						schema_oid = heap_getattr(objtup, nspAttnum,
+												  RelationGetDescr(catalog), &isnull);
+						if (isnull)
+							elog(ERROR, "invalid null namespace in object %u/%u/%d",
+								 addr.classId, addr.objectId, addr.objectSubId);
+						if (isAnyTempNamespace(schema_oid))
+							schema = pstrdup("pg_temp");
+						else
+							schema = get_namespace_name(schema_oid);
+
+						heap_close(catalog, AccessShareLock);
+					}
+				}
+
+				/* classid */
+				values[i++] = ObjectIdGetDatum(addr.classId);
+				/* objid */
+				values[i++] = ObjectIdGetDatum(addr.objectId);
+				/* objsubid */
+				values[i++] = Int32GetDatum(addr.objectSubId);
+				/* command tag */
+				values[i++] = CStringGetTextDatum(tag);
+				/* object_type */
+				values[i++] = CStringGetTextDatum(type);
+				/* schema */
+				if (schema == NULL)
+					nulls[i++] = true;
+				else
+					values[i++] = CStringGetTextDatum(schema);
+				/* identity */
+				values[i++] = CStringGetTextDatum(identity);
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+			}
+
+			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+		}
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
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..17be1fc
--- /dev/null
+++ b/src/backend/tcop/deparse_utility.c
@@ -0,0 +1,992 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/sysattr.h"
+#include "catalog/heap.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_cast.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_opclass.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_policy.h"
+#include "catalog/pg_proc.h"
+#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"
+#include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "foreign/foreign.h"
+#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"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/jsonb.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Before they are turned into JSONB representation, each command is
+ * represented as an object tree, using the structs below.
+ */
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeInteger,
+	ObjTypeFloat,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjTree
+{
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+
+	union
+	{
+		bool		boolean;
+		char	   *string;
+		int64		integer;
+		float8		flt;
+		ObjTree	   *object;
+		List	   *array;
+	} value;
+	slist_node	node;
+} ObjElem;
+
+static ObjElem *new_null_object(void);
+static ObjElem *new_bool_object(bool value);
+static ObjElem *new_string_object(char *value);
+static ObjElem *new_object_object(ObjTree *value);
+static ObjElem *new_array_object(List *array);
+static ObjElem *new_integer_object(int64 value);
+static ObjElem *new_float_object(float8 value);
+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);
+#ifdef NOT_USED
+static void append_integer_object(ObjTree *tree, char *name, int64 value);
+#endif
+static void append_float_object(ObjTree *tree, char *name, float8 value);
+static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
+static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+
+/*
+ * Allocate a new object tree to store parameter values.
+ */
+static ObjTree *
+new_objtree(void)
+{
+	ObjTree    *params;
+
+	params = palloc(sizeof(ObjTree));
+	params->numParams = 0;
+	slist_init(&params->params);
+
+	return params;
+}
+
+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one, a
+ * name (string), type (from the ObjType enum) and value must be supplied.  The
+ * value must match the type given; for instance, ObjTypeInteger requires an
+ * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of
+ * ObjElem), ObjTypeObject requires an ObjTree, and so on.  Each element type *
+ * must match the conversion specifier given in the format string, as described
+ * in pg_event_trigger_expand_command, q.v.
+ *
+ * Note we don't have the luxury of sprintf-like compiler warnings for
+ * malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree();
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		char	   *name;
+		ObjType		type;
+		ObjElem	   *elem;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/*
+		 * 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:
+				elem = new_bool_object(va_arg(args, int));
+				break;
+			case ObjTypeString:
+				elem = new_string_object(va_arg(args, char *));
+				break;
+			case ObjTypeObject:
+				elem = new_object_object(va_arg(args, ObjTree *));
+				break;
+			case ObjTypeArray:
+				elem = new_array_object(va_arg(args, List *));
+				break;
+			case ObjTypeInteger:
+				elem = new_integer_object(va_arg(args, int64));
+				break;
+			case ObjTypeFloat:
+				elem = new_float_object(va_arg(args, double));
+				break;
+			case ObjTypeNull:
+				/* Null params don't have a value (obviously) */
+				elem = new_null_object();
+				break;
+			default:
+				elog(ERROR, "invalid ObjTree element type %d", type);
+		}
+
+		elem->name = name;
+		append_premade_object(tree, elem);
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/* Allocate a new parameter with a NULL value */
+static ObjElem *
+new_null_object(void)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = NULL;
+	param->objtype = ObjTypeNull;
+
+	return param;
+}
+
+/* Append a NULL object to a tree */
+static void
+append_null_object(ObjTree *tree, char *name)
+{
+	ObjElem    *param;
+
+	param = new_null_object();
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new boolean parameter */
+static ObjElem *
+new_bool_object(bool value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeBool;
+	param->value.boolean = value;
+
+	return param;
+}
+
+/* Append a boolean parameter to a tree */
+static void
+append_bool_object(ObjTree *tree, char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = new_bool_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new string object */
+static ObjElem *
+new_string_object(char *value)
+{
+	ObjElem    *param;
+
+	Assert(value);
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeString;
+	param->value.string = value;
+
+	return param;
+}
+
+/*
+ * Append a string parameter to a tree.
+ */
+static void
+append_string_object(ObjTree *tree, char *name, char *value)
+{
+	ObjElem	   *param;
+
+	Assert(name);
+	param = new_string_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+static ObjElem *
+new_integer_object(int64 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeInteger;
+	param->value.integer = value;
+
+	return param;
+}
+
+static ObjElem *
+new_float_object(float8 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeFloat;
+	param->value.flt = value;
+
+	return param;
+}
+
+#ifdef NOT_USED
+/*
+ * Append an int64 parameter to a tree.
+ */
+static void
+append_integer_object(ObjTree *tree, char *name, int64 value)
+{
+	ObjElem	   *param;
+
+	param = new_integer_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+#endif
+
+/*
+ * Append a float8 parameter to a tree.
+ */
+static void
+append_float_object(ObjTree *tree, char *name, float8 value)
+{
+	ObjElem	   *param;
+
+	param = new_float_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new object parameter */
+static ObjElem *
+new_object_object(ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeObject;
+	param->value.object = value;
+
+	return param;
+}
+
+/* Append an object parameter to a tree */
+static void
+append_object_object(ObjTree *tree, char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	Assert(name);
+	param = new_object_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new array parameter */
+static ObjElem *
+new_array_object(List *array)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeArray;
+	param->value.array = array;
+
+	return param;
+}
+
+/* Append an array parameter to a tree */
+static void
+append_array_object(ObjTree *tree, char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = new_array_object(array);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Append a preallocated parameter to a tree */
+static inline void
+append_premade_object(ObjTree *tree, ObjElem *elem)
+{
+	slist_push_head(&tree->params, &elem->node);
+	tree->numParams++;
+}
+
+/*
+ * Helper for objtree_to_jsonb: process an individual element from an object or
+ * an array into the output parse state.
+ */
+static void
+objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object,
+						 JsonbIteratorToken elem_token)
+{
+	ListCell   *cell;
+	JsonbValue	val;
+
+	switch (object->objtype)
+	{
+		case ObjTypeNull:
+			val.type = jbvNull;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeString:
+			val.type = jbvString;
+			val.val.string.len = strlen(object->value.string);
+			val.val.string.val = object->value.string;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeInteger:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeFloat:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(float8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeBool:
+			val.type = jbvBool;
+			val.val.boolean = object->value.boolean;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeObject:
+			/* recursively add the object into the existing parse state */
+			objtree_to_jsonb_rec(object->value.object, state);
+			break;
+
+		case ObjTypeArray:
+			pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+			foreach(cell, object->value.array)
+			{
+				ObjElem   *elem = lfirst(cell);
+
+				objtree_to_jsonb_element(state, elem, WJB_ELEM);
+			}
+			pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+			break;
+
+		default:
+			elog(ERROR, "unrecognized object type %d", object->objtype);
+			break;
+	}
+}
+
+/*
+ * Recursive helper for objtree_to_jsonb
+ */
+static JsonbValue *
+objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state)
+{
+	slist_iter	iter;
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	slist_foreach(iter, &tree->params)
+	{
+		ObjElem    *object = slist_container(ObjElem, node, iter.cur);
+		JsonbValue	key;
+
+		/* Push the key first */
+		key.type = jbvString;
+		key.val.string.len = strlen(object->name);
+		key.val.string.val = object->name;
+		pushJsonbValue(&state, WJB_KEY, &key);
+
+		/* Then process the value according to its type */
+		objtree_to_jsonb_element(state, object, WJB_VALUE);
+	}
+
+	return pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Create a JSONB representation from an ObjTree.
+ */
+static Jsonb *
+objtree_to_jsonb(ObjTree *tree)
+{
+	JsonbValue *value;
+
+	value = objtree_to_jsonb_rec(tree, NULL);
+	return JsonbValueToJsonb(value);
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{
+	ObjTree    *typeParam;
+	Oid			typnspid;
+	char	   *typnsp;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		typarray;
+
+	format_type_detailed(typeId, typmod,
+						 &typnspid, &typename, &typmodstr, &typarray);
+
+	if (!OidIsValid(typnspid))
+		typnsp = pstrdup("");
+	else if (isAnyTempNamespace(typnspid))
+		typnsp = pstrdup("pg_temp");
+	else
+		typnsp = get_namespace_name(typnspid);
+
+	/* We don't use new_objtree_VA here because types don't have a "fmt" */
+	typeParam = new_objtree();
+	append_string_object(typeParam, "schemaname", typnsp);
+	append_string_object(typeParam, "typename", typename);
+	append_string_object(typeParam, "typmod", typmodstr);
+	append_bool_object(typeParam, "typarray", typarray);
+
+	return typeParam;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements
+ *
+ * Elements "schemaname" and "objname" are set.  If the namespace OID
+ * corresponds to a temp schema, that's set to "pg_temp".
+ *
+ * The difference between those two element types is whether the objname will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static ObjTree *
+new_objtree_for_qualname(Oid nspid, char *name)
+{
+	ObjTree    *qualified;
+	char	   *namespace;
+
+	/*
+	 * We don't use new_objtree_VA here because these names don't have a "fmt"
+	 */
+	qualified = new_objtree();
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+	append_string_object(qualified, "schemaname", namespace);
+	append_string_object(qualified, "objname", pstrdup(name));
+
+	return qualified;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements, with the object specified
+ * by classId/objId
+ *
+ * Elements "schemaname" and "objname" are set.  If the object is a temporary
+ * object, the schema name is set to "pg_temp".
+ */
+static ObjTree *
+new_objtree_for_qualname_id(Oid classId, Oid objectId)
+{
+	ObjTree    *qualified;
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		objnsp;
+	Datum		objname;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	bool		isnull;
+
+	catalog = heap_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL namespace");
+	objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL name");
+
+	qualified = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+										 NameStr(*DatumGetName(objname)));
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+static ObjTree *
+new_objtree_for_role(Oid roleoid)
+{
+	ObjTree    *tmp;
+
+	if (roleoid == ACL_ID_PUBLIC)
+		tmp = new_objtree_VA("PUBLIC", 0);
+	else
+	{
+		HeapTuple	roltup;
+		char	   *rolname;
+
+		roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleoid));
+		if (!HeapTupleIsValid(roltup))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("role with OID %u does not exist", roleoid)));
+
+		tmp = new_objtree_VA("%{name}I", 0);
+		rolname = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname);
+		append_string_object(tmp, "name", pstrdup(rolname));
+		ReleaseSysCache(roltup);
+	}
+
+	return tmp;
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function contains a large switch that mirrors that in
+ * ProcessUtilitySlow.  All cases covered there should also be covered here.
+ */
+static ObjTree *
+deparse_simple_command(StashedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+	ObjTree	   *command;
+
+	Assert(cmd->type == SCT_Simple);
+
+	parsetree = cmd->parsetree;
+	objectId = cmd->d.simple.address.objectId;
+
+	/* This switch needs to handle everything that ProcessUtilitySlow does */
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateForeignTableStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTableStmt:
+		case T_AlterTableMoveAllStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterDomainStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+			/* other local objects */
+		case T_DefineStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_IndexStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateExtensionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterExtensionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterExtensionContentsStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropUserMappingStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_ImportForeignSchemaStmt:
+			/* generated commands are stashed individually */
+			elog(ERROR, "unexpected command %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterEnumStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_ViewStmt:		/* CREATE VIEW */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateFunctionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterFunctionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_RuleStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateSeqStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterSeqStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateTableAsStmt:
+			/* XXX handle at least the CREATE MATVIEW case? */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_RefreshMatViewStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateTrigStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePLangStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateDomainStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateConversionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateCastStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpClassStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpFamilyStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterOpFamilyStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSDictionaryStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSConfigurationStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_RenameStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterObjectSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterOwnerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CommentStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_GrantStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropOwnedStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_AlterDefaultPrivilegesStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePolicyStmt:	/* CREATE POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterPolicyStmt:		/* ALTER POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_SecLabelStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		default:
+			command = NULL;
+			elog(LOG, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return command;
+}
+
+/*
+ * Given a utility command parsetree and the OID of the corresponding object,
+ * return a JSON representation of the command.
+ *
+ * The command is expanded fully, so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+char *
+deparse_utility_command(StashedCommand *cmd)
+{
+	OverrideSearchPath *overridePath;
+	MemoryContext	oldcxt;
+	MemoryContext	tmpcxt;
+	ObjTree		   *tree;
+	char		   *command;
+	StringInfoData  str;
+
+	/*
+	 * Allocate everything done by the deparsing routines into a temp context,
+	 * to avoid having to sprinkle them with memory handling code; but allocate
+	 * the output StringInfo before switching.
+	 */
+	initStringInfo(&str);
+	tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "deparse ctx",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * in order to obtain deparsed versions of expressions.  In such results,
+	 * we want all object names to be qualified, so that results are "portable"
+	 * to environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = true;
+	PushOverrideSearchPath(overridePath);
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			tree = deparse_simple_command(cmd);
+			break;
+		default:
+			elog(ERROR, "unexpected deparse node type %d", cmd->type);
+	}
+
+	PopOverrideSearchPath();
+
+	if (tree)
+	{
+		Jsonb *jsonb;
+
+		jsonb = objtree_to_jsonb(tree);
+		command = JsonbToCString(&str, &jsonb->root, 128);
+	}
+	else
+		command = NULL;
+
+	/*
+	 * Clean up.  Note that since we created the StringInfo in the caller's
+	 * context, the output string is not deleted here.
+	 */
+	MemoryContextSwitchTo(oldcxt);
+	MemoryContextDelete(tmpcxt);
+
+	return command;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 065475d..f6cf161 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -875,6 +875,8 @@ standard_ProcessUtility(Node *parsetree,
  * The "Slow" variant of ProcessUtility should only receive statements
  * supported by the event triggers facility.  Therefore, we always
  * perform the trigger support calls if the context allows it.
+ *
+ * See deparse_utility_command, which must be kept in sync with this.
  */
 static void
 ProcessUtilitySlow(Node *parsetree,
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 20e5ff1..110c6ee 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -18,7 +18,7 @@ endif
 # keep this list arranged alphabetically or it gets to be a mess
 OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
 	array_userfuncs.o arrayutils.o ascii.o bool.o \
-	cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \
+	cash.o char.o date.o datetime.o datum.o dbsize.o ddl_json.o domains.o \
 	encode.o enum.o float.o format_type.o formatting.o genfile.o \
 	geo_ops.o geo_selfuncs.o inet_cidr_ntop.o inet_net_pton.o int.o \
 	int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
diff --git a/src/backend/utils/adt/ddl_json.c b/src/backend/utils/adt/ddl_json.c
new file mode 100644
index 0000000..9f6f42e
--- /dev/null
+++ b/src/backend/utils/adt/ddl_json.c
@@ -0,0 +1,676 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_json.c
+ *	  JSON code related to DDL command deparsing
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/ddl_json.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "lib/stringinfo.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+
+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	SpecNumber,
+	SpecStringLiteral,
+	SpecIdentifier
+} convSpecifier;
+
+typedef enum
+{
+	tv_absent,
+	tv_true,
+	tv_false
+} trivalue;
+
+static void expand_one_jsonb_element(StringInfo out, char *param,
+						 JsonbValue *jsonval, convSpecifier specifier,
+						 const char *fmt);
+static void expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep,
+				   convSpecifier specifier, const char *fmt);
+static void fmtstr_error_callback(void *arg);
+
+static trivalue
+find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	bool		result;
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+		return tv_absent;
+	if (value->type != jbvBool)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not of type boolean",
+						keyname)));
+	result = value->val.boolean ? tv_true : tv_false;
+	pfree(value);
+
+	return result;
+}
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvString, an error is raised.  If it doesn't
+ * exist, an error is raised if missing_ok; otherwise return NULL.
+ *
+ * If it exists and is a string, a freshly palloc'ed copy is returned.
+ *
+ * If *length is not NULL, it is set to the length of the string.
+ */
+static char *
+find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname,
+							  bool missing_ok, int *length)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	char	   *str;
+
+	/* XXX verify that this is an object, not an array */
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+	{
+		if (missing_ok)
+			return NULL;
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing element \"%s\" in json object", keyname)));
+	}
+
+	str = pnstrdup(value->val.string.val, value->val.string.len);
+	if (length)
+		*length = value->val.string.len;
+	pfree(value);
+	return str;
+}
+
+#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)
+
+/*
+ * Recursive helper for pg_event_trigger_expand_command
+ *
+ * Find the "fmt" element in the given container, and expand it into the
+ * provided StringInfo.
+ */
+static void
+expand_fmt_recursive(JsonbContainer *container, StringInfo out)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	int			len;
+
+	start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len);
+	end_ptr = start_ptr + len;
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{
+		convSpecifier specifier;
+		bool		is_array;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		is_array = false;
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			for (; cp < end_ptr;)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing conversion name in conversion specifier")));
+
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'L':
+				specifier = SpecStringLiteral;
+				break;
+			case 'T':
+				specifier = SpecTypename;
+				break;
+			case 'O':
+				specifier = SpecOperatorname;
+				break;
+			case 'n':
+				specifier = SpecNumber;
+				break;
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid conversion specifier \"%c\"", *cp)));
+		}
+
+		/*
+		 * Obtain the element to be expanded.
+		 */
+		key.type = jbvString;
+		key.val.string.val = param;
+		key.val.string.len = strlen(param);
+
+		value = findJsonbValueFromContainer(container, JB_FOBJECT, &key);
+
+		/* Validate that we got an array if the format string specified one. */
+
+		/* And finally print out the data */
+		if (is_array)
+			expand_jsonb_array(out, param, value, arraysep, specifier, start_ptr);
+		else
+			expand_one_jsonb_element(out, param, value, specifier, start_ptr);
+	}
+}
+
+/*
+ * Expand a json value as an identifier.  The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	Assert(jsonval->type == jbvString);
+
+	str = pnstrdup(jsonval->val.string.val,
+				   jsonval->val.string.len);
+	appendStringInfoString(buf, quote_identifier(str));
+	pfree(str);
+}
+
+/*
+ * Expand a json value as a dot-separated-name.  The value must be of type
+ * object and must contain elements "schemaname" (optional), "objname"
+ * (mandatory), "attrname" (optional).  Double quotes are added to each element
+ * as necessary, and dot separators where needed.
+ *
+ * One day we might need a "catalog" element as well, but no current use case
+ * needs that.
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+		pfree(str);
+	}
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfo(buf, "%s", quote_identifier(str));
+	pfree(str);
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"attrname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, ".%s", quote_identifier(str));
+		pfree(str);
+	}
+}
+
+/*
+ * expand a json value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename;
+	char	   *typmodstr;
+	trivalue	is_array;
+	char	   *array_decor;
+
+	/*
+	 * We omit schema-qualifying the output name if the schema element is
+	 * either the empty string or NULL; the difference between those two cases
+	 * is that in the latter we quote the type name, in the former we don't.
+	 * This allows for types with special typmod needs, such as interval and
+	 * timestamp (see format_type_detailed), while at the same time allowing
+	 * for the schema name to be omitted from type names that require quotes
+	 * but are to be obtained from a user schema.
+	 */
+
+	schema = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										   "schemaname", true, NULL);
+	typename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											 "typename", false, NULL);
+	typmodstr = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											  "typmod", true, NULL);
+	is_array = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+										   "typarray");
+	switch (is_array)
+	{
+		default:
+		case tv_absent:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("missing typarray element")));
+			break;
+		case tv_true:
+			array_decor = "[]";
+			break;
+		case tv_false:
+			array_decor = "";
+			break;
+	}
+
+	if (schema == NULL)
+		appendStringInfo(buf, "%s%s%s",
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else if (schema[0] == '\0')
+		appendStringInfo(buf, "%s%s%s",
+						 typename,
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else
+		appendStringInfo(buf, "%s.%s%s%s",
+						 quote_identifier(schema),
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+}
+
+/*
+ * Expand a json value as an operator name
+ */
+static void
+expand_jsonval_operator(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	/* schema might be NULL or empty */
+	if (str != NULL && str[0] != '\0')
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfoString(buf, str);
+}
+
+/*
+ * Expand a json value as a string.  The value must be of type string or of
+ * type object.  In the latter 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, JsonbValue *jsonval)
+{
+	if (jsonval->type == jbvString)
+	{
+		appendBinaryStringInfo(buf, jsonval->val.string.val,
+							   jsonval->val.string.len);
+	}
+	else if (jsonval->type == jbvBinary)
+	{
+		trivalue	present;
+
+		present = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											  "present");
+		/*
+		 * If "present" is set to false, this element expands to empty;
+		 * otherwise (either true or absent), fall through to expand "fmt".
+		 */
+		if (present == tv_false)
+			return;
+
+		expand_fmt_recursive(jsonval->val.binary.data, buf);
+	}
+}
+
+/*
+ * Expand a json value as a string literal
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval)
+{
+	char   *str;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int         dqnextchar = 0;
+
+	str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+
+	/* easy case: if there are no ' and no \, just use a single quote */
+	if (strchr(str, '\'') == NULL &&
+		strchr(str, '\\') == NULL)
+	{
+		appendStringInfo(buf, "'%s'", str);
+		pfree(str);
+		return;
+	}
+
+	/* Otherwise need to find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(str, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar %= sizeof(dqsuffixes) - 1;
+	}
+	/* add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data);
+	pfree(dqdelim.data);
+	pfree(str);
+}
+
+/*
+ * Expand a json value as an integer quantity
+ */
+static void
+expand_jsonval_number(StringInfo buf, JsonbValue *jsonval)
+{
+	char *strdatum;
+
+	strdatum = DatumGetCString(DirectFunctionCall1(numeric_out,
+												   NumericGetDatum(jsonval->val.numeric)));
+	appendStringInfoString(buf, strdatum);
+}
+
+/*
+ * Expand one json element into the output StringInfo according to the
+ * conversion specifier.  The element type is validated, and an error is raised
+ * if it doesn't match what we expect for the conversion specifier.
+ */
+static void
+expand_one_jsonb_element(StringInfo out, char *param, JsonbValue *jsonval,
+						 convSpecifier specifier, const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (!jsonval)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" not found", param)));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%I element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_identifier(out, jsonval);
+			break;
+
+		case SpecDottedName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%D element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_dottedname(out, jsonval);
+			break;
+
+		case SpecString:
+			if (jsonval->type != jbvString &&
+				jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string or object for %%s element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_string(out, jsonval);
+			break;
+
+		case SpecStringLiteral:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%L element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_strlit(out, jsonval);
+			break;
+
+		case SpecTypename:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%T element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_typename(out, jsonval);
+			break;
+
+		case SpecOperatorname:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%O element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_operator(out, jsonval);
+			break;
+
+		case SpecNumber:
+			if (jsonval->type != jbvNumeric)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON numeric for %%n element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_number(out, jsonval);
+			break;
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*
+ * Iterate on the elements of a JSON array, expanding each one into the output
+ * StringInfo per the given conversion specifier, separated by the given
+ * separator.
+ */
+static void
+expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep, convSpecifier specifier,
+				   const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+	JsonbContainer *container;
+	JsonbIterator  *it;
+	JsonbValue	v;
+	int			type;
+	bool		first = true;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (jsonarr->type != jbvBinary)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	container = jsonarr->val.binary.data;
+	if ((container->header & JB_FARRAY) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	it = JsonbIteratorInit(container);
+	while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+	{
+		switch (type)
+		{
+			case WJB_ELEM:
+				if (!first)
+					appendStringInfoString(out, arraysep);
+				first = false;
+				expand_one_jsonb_element(out, param, &v, specifier, NULL);
+				break;
+		}
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ * n		expand as a simple number (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.
+ *------
+ */
+Datum
+pg_event_trigger_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	Datum		d;
+	Jsonb	   *jsonb;
+	StringInfoData out;
+
+	initStringInfo(&out);
+	d = DirectFunctionCall1(jsonb_in,
+							PointerGetDatum(TextDatumGetCString(json)));
+	jsonb = (Jsonb *) DatumGetPointer(d);
+
+	expand_fmt_recursive(&jsonb->root, &out);
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(out.data));
+}
+
+/*
+ * Error context callback for JSON format string expansion.
+ *
+ * Possible improvement: indicate which element we're expanding, if applicable
+ */
+static void
+fmtstr_error_callback(void *arg)
+{
+	errcontext("while expanding format string \"%s\"", (char *) arg);
+
+}
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index fc816ce..fee71e5 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,132 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
+/*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name in this case.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *typarray)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID ||
+		type_oid == TIMEOID ||
+		type_oid == TIMETZOID)
+	{
+		*typarray = false;
+
+peculiar_typmod:
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPTZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmod, so fall through */
+			case TIMESTAMPOID:
+				*typname = pstrdup("TIMESTAMP");
+				break;
+			case TIMETZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIME WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmode, so fall through */
+			case TIMEOID:
+				*typname = pstrdup("TIME");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		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;
+		*typarray = true;
+
+		/*
+		 * If it's an array of one of the types with special typmod rules,
+		 * have the element type be processed as above, but now with typarray
+		 * set to true.
+		 */
+		if (type_oid == INTERVALOID ||
+			type_oid == TIMESTAMPTZOID ||
+			type_oid == TIMESTAMPOID ||
+			type_oid == TIMETZOID ||
+			type_oid == TIMEOID)
+			goto peculiar_typmod;
+	}
+	else
+		*typarray = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
 
 /*
  * Add typmod decoration to the basic type name
@@ -338,7 +467,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 +479,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index b8a3660..7f04d89 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5088,12 +5088,15 @@ DESCR("peek at binary changes from replication slot");
 
 /* event triggers */
 DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,16,16,25,25,25,25,1009,1009}" "{o,o,o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, original, normal, object_type, schema_name, object_name, object_identity, address_names, address_args}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
-
 DESCR("list objects dropped by the current command");
 DATA(insert OID = 4566 (  pg_event_trigger_table_rewrite_oid	PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 26 "" "{26}" "{o}" "{oid}" _null_ pg_event_trigger_table_rewrite_oid _null_ _null_ _null_ ));
 DESCR("return Oid of the table getting rewritten");
 DATA(insert OID = 4567 (  pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ ));
 DESCR("return reason code for table getting rewritten");
+DATA(insert OID = 3590 (  pg_event_trigger_get_creation_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,114}" "{o,o,o,o,o,o,o,o,o}" "{classid,objid,objsubid,command_tag,object_type,schema,identity,in_extension,command}" _null_ pg_event_trigger_get_creation_commands _null_ _null_ _null_ ));
+DESCR("list JSON-formatted commands executed by the current command");
+DATA(insert OID = 3591 (  pg_event_trigger_expand_command PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 25 "114" _null_ _null_ _null_ _null_ pg_event_trigger_expand_command _null_ _null_ _null_ ));
+DESCR("format JSON command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 7eb2156..2ca88ee 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -60,4 +60,9 @@ extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 							 bool original, bool normal);
 
+extern void EventTriggerStashCommand(ObjectAddress address,
+						 ObjectAddress *secondaryObject, Node *parsetree);
+extern void EventTriggerInhibitCommandCollection(void);
+extern void EventTriggerUndoInhibitCommandCollection(void);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 40ecea2..0423350 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -24,7 +24,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a575353..bde3522 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1229,6 +1229,7 @@ typedef enum ObjectType
 	OBJECT_CAST,
 	OBJECT_COLUMN,
 	OBJECT_COLLATION,
+	OBJECT_COMPOSITE,
 	OBJECT_CONVERSION,
 	OBJECT_DATABASE,
 	OBJECT_DEFAULT,
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..bc6fa10
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+#include "access/attnum.h"
+#include "nodes/nodes.h"
+
+/*
+ * Support for keeping track of a command to deparse.
+ *
+ * When a command is run, we collect some information about it for later
+ * deparsing; deparse_utility_command can later be used to obtain a usable
+ * representation of it.
+ */
+
+typedef enum StashedCommandType
+{
+	SCT_Simple,
+} StashedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct StashedATSubcmd
+{
+	AttrNumber		attnum;	/* affected column number */
+	Oid				oid;	/* affected constraint, default value or index */
+	Node		   *parsetree;
+} StashedATSubcmd;
+
+typedef struct StashedCommand
+{
+	StashedCommandType type;
+	bool		in_extension;
+	Node	   *parsetree;
+
+	union
+	{
+		struct SimpleCommand
+		{
+			ObjectAddress address;
+			ObjectAddress secondaryObject;
+		} simple;
+	} d;
+} StashedCommand;
+
+extern char *deparse_utility_command(StashedCommand *cmd);
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index bc4517d..470a325 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1079,6 +1079,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);
@@ -1210,6 +1213,10 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS);
+
+/* utils/adt/ddl_json.c */
+extern Datum pg_event_trigger_expand_command(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
-- 
2.1.4

0004-deparse-add-EventTriggerStashCommand-calls-to-Proces.patchtext/x-diff; charset=us-asciiDownload
>From d1386afc2a60aee15f4e1f0901374bdd6ca3d147 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:50:37 -0300
Subject: [PATCH 04/29] deparse: add EventTriggerStashCommand() calls to
 ProcessUtilitySlow

These calls add each executed command to the event trigger command
stash.
---
 src/backend/commands/schemacmds.c |  14 +++
 src/backend/tcop/utility.c        | 243 ++++++++++++++++++++++++++------------
 2 files changed, 179 insertions(+), 78 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index c090ed2..237905a 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -25,6 +25,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/schemacmds.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
@@ -52,6 +53,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	Oid			saved_uid;
 	int			save_sec_context;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
 
@@ -142,6 +144,18 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	/* XXX should we clear overridePath->useTemp? */
 	PushOverrideSearchPath(overridePath);
 
+	address.classId = NamespaceRelationId;
+	address.objectId = namespaceId;
+	address.objectSubId = 0;
+
+	/*
+	 * 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(address, NULL, (Node *) stmt);
+
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
 	 * reorganize them into a sequentially executable order with no forward
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f6cf161..624754c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -889,7 +889,9 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	bool		commandStashed = false;
 	ObjectAddress address;
+	ObjectAddress secondaryObject = InvalidObjectAddress;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -908,6 +910,12 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * CreateSchemaCommand calls EventTriggerStashCommand
+				 * internally, for reasons explained there, so turn off
+				 * command collection below.
+				 */
+				commandStashed = true;
 				break;
 
 			case T_CreateStmt:
@@ -934,6 +942,7 @@ ProcessUtilitySlow(Node *parsetree,
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
 													 InvalidOid, NULL);
+							EventTriggerStashCommand(address, NULL, stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -966,10 +975,15 @@ ProcessUtilitySlow(Node *parsetree,
 													 InvalidOid, NULL);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
+							EventTriggerStashCommand(address, NULL, 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,
@@ -982,6 +996,12 @@ ProcessUtilitySlow(Node *parsetree,
 						if (lnext(l) != NULL)
 							CommandCounterIncrement();
 					}
+
+					/*
+					 * The multiple commands generated here are stashed
+					 * individually, so disable collection below.
+					 */
+					commandStashed = true;
 				}
 				break;
 
@@ -1040,6 +1060,9 @@ ProcessUtilitySlow(Node *parsetree,
 						  (errmsg("relation \"%s\" does not exist, skipping",
 								  atstmt->relation->relname)));
 				}
+
+				/* ALTER TABLE stashes commands internally */
+				commandStashed = true;
 				break;
 
 			case T_AlterDomainStmt:
@@ -1058,31 +1081,37 @@ ProcessUtilitySlow(Node *parsetree,
 							 * Recursively alter column default for table and,
 							 * if requested, for descendants
 							 */
-							AlterDomainDefault(stmt->typeName,
-											   stmt->def);
+							address =
+								AlterDomainDefault(stmt->typeName,
+												   stmt->def);
 							break;
 						case 'N':		/* ALTER DOMAIN DROP NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   false);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   false);
 							break;
 						case 'O':		/* ALTER DOMAIN SET NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   true);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   true);
 							break;
 						case 'C':		/* ADD CONSTRAINT */
-							AlterDomainAddConstraint(stmt->typeName,
-													 stmt->def,
-													 NULL);
+							address =
+								AlterDomainAddConstraint(stmt->typeName,
+														 stmt->def,
+														 &secondaryObject);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
-							AlterDomainDropConstraint(stmt->typeName,
-													  stmt->name,
-													  stmt->behavior,
-													  stmt->missing_ok);
+							address =
+								AlterDomainDropConstraint(stmt->typeName,
+														  stmt->name,
+														  stmt->behavior,
+														  stmt->missing_ok);
 							break;
 						case 'V':		/* VALIDATE CONSTRAINT */
-							AlterDomainValidateConstraint(stmt->typeName,
-														  stmt->name);
+							address =
+								AlterDomainValidateConstraint(stmt->typeName,
+															  stmt->name);
 							break;
 						default:		/* oops */
 							elog(ERROR, "unrecognized alter domain type: %d",
@@ -1102,40 +1131,46 @@ ProcessUtilitySlow(Node *parsetree,
 					switch (stmt->kind)
 					{
 						case OBJECT_AGGREGATE:
-							DefineAggregate(stmt->defnames, stmt->args,
-											stmt->oldstyle, stmt->definition,
-											queryString);
+							address =
+								DefineAggregate(stmt->defnames, stmt->args,
+												stmt->oldstyle,
+												stmt->definition, queryString);
 							break;
 						case OBJECT_OPERATOR:
 							Assert(stmt->args == NIL);
-							DefineOperator(stmt->defnames, stmt->definition);
+							address = DefineOperator(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TYPE:
 							Assert(stmt->args == NIL);
-							DefineType(stmt->defnames, stmt->definition);
+							address = DefineType(stmt->defnames,
+												  stmt->definition);
 							break;
 						case OBJECT_TSPARSER:
 							Assert(stmt->args == NIL);
-							DefineTSParser(stmt->defnames, stmt->definition);
+							address = DefineTSParser(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TSDICTIONARY:
 							Assert(stmt->args == NIL);
-							DefineTSDictionary(stmt->defnames,
-											   stmt->definition);
+							address = DefineTSDictionary(stmt->defnames,
+														  stmt->definition);
 							break;
 						case OBJECT_TSTEMPLATE:
 							Assert(stmt->args == NIL);
-							DefineTSTemplate(stmt->defnames,
-											 stmt->definition);
+							address = DefineTSTemplate(stmt->defnames,
+														stmt->definition);
 							break;
 						case OBJECT_TSCONFIGURATION:
 							Assert(stmt->args == NIL);
-							DefineTSConfiguration(stmt->defnames,
-												  stmt->definition);
+							address = DefineTSConfiguration(stmt->defnames,
+															 stmt->definition,
+															 &secondaryObject);
 							break;
 						case OBJECT_COLLATION:
 							Assert(stmt->args == NIL);
-							DefineCollation(stmt->defnames, stmt->definition);
+							address = DefineCollation(stmt->defnames,
+													   stmt->definition);
 							break;
 						default:
 							elog(ERROR, "unrecognized define stmt type: %d",
@@ -1176,204 +1211,249 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
-					DefineIndex(relid,	/* OID of heap relation */
-								stmt,
-								InvalidOid,		/* no predefined OID */
-								false,	/* is_alter_table */
-								true,	/* check_rights */
-								false,	/* skip_build */
-								false); /* quiet */
+					address =
+						DefineIndex(relid,	/* OID of heap relation */
+									stmt,
+									InvalidOid,		/* no predefined OID */
+									false,	/* is_alter_table */
+									true,	/* check_rights */
+									false,	/* skip_build */
+									false); /* quiet */
+					/*
+					 * Add the CREATE INDEX node itself to stash right away; if
+					 * there were any commands stashed in the ALTER TABLE code,
+					 * we need them to appear after this one.
+					 */
+					EventTriggerStashCommand(address, NULL, parsetree);
+					commandStashed = true;
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				address = CreateExtension((CreateExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				address = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
-											   NULL);
+				address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+														  &secondaryObject);
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				address = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
 				break;
 
 			case T_AlterFdwStmt:
-				AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				address = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				address = CreateForeignServer((CreateForeignServerStmt *) parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
-				AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				address = AlterForeignServer((AlterForeignServerStmt *) parsetree);
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				address = CreateUserMapping((CreateUserMappingStmt *) parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
-				AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				address = AlterUserMapping((AlterUserMappingStmt *) parsetree);
 				break;
 
 			case T_DropUserMappingStmt:
 				RemoveUserMapping((DropUserMappingStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandStashed = true;
 				break;
 
 			case T_ImportForeignSchemaStmt:
 				ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+				/* commands are stashed inside ImportForeignSchema */
+				commandStashed = true;
 				break;
 
 			case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					address = DefineCompositeType(stmt->typevar,
+												  stmt->coldeflist);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				address = DefineEnum((CreateEnumStmt *) parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				address = DefineRange((CreateRangeStmt *) parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				address = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				address = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerStashCommand(address, NULL, parsetree);
+				/* stashed internally */
+				commandStashed = true;
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				address = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				address = AlterFunction((AlterFunctionStmt *) parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				address = DefineRule((RuleStmt *) parsetree, queryString);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				address = DefineSequence((CreateSeqStmt *) parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				address = AlterSequence((AlterSeqStmt *) parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
-								   queryString, params, completionTag);
+				/*
+				 * REFRSH CONCURRENTLY executes some DDL commands internally.
+				 * Inhibit DDL command collection here to avoid those commands
+				 * from showing up in the deparsed command queue.  The refresh
+				 * command itself is queued, which is enough.
+				 */
+				EventTriggerInhibitCommandCollection();
+				PG_TRY();
+				{
+					address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+												 queryString, params, completionTag);
+				}
+				PG_CATCH();
+				{
+					EventTriggerUndoInhibitCommandCollection();
+					PG_RE_THROW();
+				}
+				PG_END_TRY();
+				EventTriggerUndoInhibitCommandCollection();
 				break;
 
 			case T_CreateTrigStmt:
-				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
-									 InvalidOid, InvalidOid, InvalidOid,
-									 InvalidOid, false);
+				address = CreateTrigger((CreateTrigStmt *) parsetree,
+										 queryString, InvalidOid, InvalidOid,
+										 InvalidOid, InvalidOid, false);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				address = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				address = DefineDomain((CreateDomainStmt *) parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				address = CreateConversionCommand((CreateConversionStmt *) parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				address = CreateCast((CreateCastStmt *) parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				address = DefineOpClass((CreateOpClassStmt *) parsetree);
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				address = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
 				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				/* commands are stashed in AlterOpFamily */
+				commandStashed = true;
 				break;
 
 			case T_AlterTSDictionaryStmt:
-				AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				address = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
 				break;
 
 			case T_AlterTableMoveAllStmt:
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
+				/* commands are stashed in AlterTableMoveAll */
+				commandStashed = true;
 				break;
 
 			case T_DropStmt:
 				ExecDropStmt((DropStmt *) parsetree, isTopLevel);
+				/* no commands stashed for DROP */
+				commandStashed = true;
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree);
+				address = ExecRenameStmt((RenameStmt *) parsetree);
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
-										  NULL);
+				address =
+					ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
+											  &secondaryObject);
 				break;
 
 			case T_AlterOwnerStmt:
-				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
 			case T_CommentStmt:
-				CommentObject((CommentStmt *) parsetree);
+				address = CommentObject((CommentStmt *) parsetree);
 				break;
 
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
+				/* commands are stashed in ExecGrantStmt_oids */
+				commandStashed = true;
 				break;
 
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandStashed = true;
 				break;
 
 			case T_AlterDefaultPrivilegesStmt:
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
+				/* XXX FIXME WTF? */
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
-				CreatePolicy((CreatePolicyStmt *) parsetree);
+				address = CreatePolicy((CreatePolicyStmt *) parsetree);
 				break;
 
 			case T_AlterPolicyStmt:		/* ALTER POLICY */
-				AlterPolicy((AlterPolicyStmt *) parsetree);
+				address = AlterPolicy((AlterPolicyStmt *) parsetree);
 				break;
 
 			case T_SecLabelStmt:
-				ExecSecLabelStmt((SecLabelStmt *) parsetree);
+				address = ExecSecLabelStmt((SecLabelStmt *) parsetree);
 				break;
 
 			default:
@@ -1382,6 +1462,13 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 		}
 
+		/*
+		 * Remember the object so that ddl_command_end event triggers have
+		 * access to it.
+		 */
+		if (!commandStashed)
+			EventTriggerStashCommand(address, &secondaryObject, parsetree);
+
 		if (isCompleteQuery)
 		{
 			EventTriggerSQLDrop(parsetree);
-- 
2.1.4

0005-deparse-Support-CREATE-SCHEMA-TABLE-SEQUENCE-INDEX-T.patchtext/x-diff; charset=us-asciiDownload
>From fca784698297d01a7a53f00f2a54b8ec46a8835c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:54:00 -0300
Subject: [PATCH 05/29] deparse: Support CREATE
 SCHEMA/TABLE/SEQUENCE/INDEX/TRIGGER/TYPE AS

---
 src/backend/commands/sequence.c    |   34 +
 src/backend/commands/tablecmds.c   |    3 +-
 src/backend/tcop/deparse_utility.c | 1264 +++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  373 +++++++++--
 src/include/commands/sequence.h    |    1 +
 src/include/utils/ruleutils.h      |   14 +-
 6 files changed, 1628 insertions(+), 61 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6d316d6..73cf4da 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1503,6 +1503,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/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fc2c8a9..bd16ef0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8056,7 +8056,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/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 17be1fc..8b1e749 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -674,6 +674,1256 @@ new_objtree_for_role(Oid roleoid)
 }
 
 /*
+ * 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_CreateTrigStmt
+ *		Deparse a CreateTrigStmt (CREATE TRIGGER)
+ *
+ * Given a trigger OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
+{
+	CreateTrigStmt *node = (CreateTrigStmt *) parsetree;
+	Relation	pg_trigger;
+	HeapTuple	trigTup;
+	Form_pg_trigger trigForm;
+	ObjTree	   *trigger;
+	ObjTree	   *tmp;
+	int			tgnargs;
+	List	   *list;
+	List	   *events;
+
+	pg_trigger = heap_open(TriggerRelationId, AccessShareLock);
+
+	trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+	trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+	/*
+	 * Some of the elements only make sense for CONSTRAINT TRIGGERs, but it
+	 * seems simpler to use a single fmt string for both kinds of triggers.
+	 */
+	trigger =
+		new_objtree_VA("CREATE %{constraint}s TRIGGER %{name}I %{time}s %{events: OR }s "
+					   "ON %{relation}D %{from_table}s %{constraint_attrs: }s "
+					   "FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE %{function}s",
+					   2,
+					   "name", ObjTypeString, node->trigname,
+					   "constraint", ObjTypeString,
+					   node->isconstraint ? "CONSTRAINT" : "");
+
+	if (node->timing == TRIGGER_TYPE_BEFORE)
+		append_string_object(trigger, "time", "BEFORE");
+	else if (node->timing == TRIGGER_TYPE_AFTER)
+		append_string_object(trigger, "time", "AFTER");
+	else if (node->timing == TRIGGER_TYPE_INSTEAD)
+		append_string_object(trigger, "time", "INSTEAD OF");
+	else
+		elog(ERROR, "unrecognized trigger timing value %d", node->timing);
+
+	/*
+	 * Decode the events that the trigger fires for.  The output is a list;
+	 * in most cases it will just be a string with the even name, but when
+	 * there's an UPDATE with a list of columns, we return a JSON object.
+	 */
+	events = NIL;
+	if (node->events & TRIGGER_TYPE_INSERT)
+		events = lappend(events, new_string_object("INSERT"));
+	if (node->events & TRIGGER_TYPE_DELETE)
+		events = lappend(events, new_string_object("DELETE"));
+	if (node->events & TRIGGER_TYPE_TRUNCATE)
+		events = lappend(events, new_string_object("TRUNCATE"));
+	if (node->events & TRIGGER_TYPE_UPDATE)
+	{
+		if (node->columns == NIL)
+		{
+			events = lappend(events, new_string_object("UPDATE"));
+		}
+		else
+		{
+			ObjTree	   *update;
+			ListCell   *cell;
+			List	   *cols = NIL;
+
+			/*
+			 * Currently only UPDATE OF can be objects in the output JSON, but
+			 * we add a "kind" element so that user code can distinguish
+			 * possible future new event types.
+			 */
+			update = new_objtree_VA("UPDATE OF %{columns:, }I",
+									1, "kind", ObjTypeString, "update_of");
+
+			foreach(cell, node->columns)
+			{
+				char   *colname = strVal(lfirst(cell));
+
+				cols = lappend(cols,
+							   new_string_object(colname));
+			}
+
+			append_array_object(update, "columns", cols);
+
+			events = lappend(events,
+							 new_object_object(update));
+		}
+	}
+	append_array_object(trigger, "events", events);
+
+	tmp = new_objtree_for_qualname_id(RelationRelationId,
+									  trigForm->tgrelid);
+	append_object_object(trigger, "relation", tmp);
+
+	tmp = new_objtree_VA("FROM %{relation}D", 0);
+	if (trigForm->tgconstrrelid)
+	{
+		ObjTree	   *rel;
+
+		rel = new_objtree_for_qualname_id(RelationRelationId,
+										  trigForm->tgconstrrelid);
+		append_object_object(tmp, "relation", rel);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "from_table", tmp);
+
+	list = NIL;
+	if (node->deferrable)
+		list = lappend(list,
+					   new_string_object("DEFERRABLE"));
+	if (node->initdeferred)
+		list = lappend(list,
+					   new_string_object("INITIALLY DEFERRED"));
+	append_array_object(trigger, "constraint_attrs", list);
+
+	append_string_object(trigger, "for_each",
+						 node->row ? "ROW" : "STATEMENT");
+
+	tmp = new_objtree_VA("WHEN (%{clause}s)", 0);
+	if (node->whenClause)
+	{
+		Node	   *whenClause;
+		Datum		value;
+		bool		isnull;
+
+		value = fastgetattr(trigTup, Anum_pg_trigger_tgqual,
+							RelationGetDescr(pg_trigger), &isnull);
+		if (isnull)
+			elog(ERROR, "bogus NULL tgqual");
+
+		whenClause = stringToNode(TextDatumGetCString(value));
+		append_string_object(tmp, "clause",
+							 pg_get_trigger_whenclause(trigForm,
+													   whenClause,
+													   false));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "when", tmp);
+
+	tmp = new_objtree_VA("%{funcname}D(%{args:, }L)",
+						 1, "funcname", ObjTypeObject,
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 trigForm->tgfoid));
+	list = NIL;
+	tgnargs = trigForm->tgnargs;
+	if (tgnargs > 0)
+	{
+		bytea  *tgargs;
+		char   *argstr;
+		bool	isnull;
+		int		findx;
+		int		lentgargs;
+		char   *p;
+
+		tgargs = DatumGetByteaP(fastgetattr(trigTup,
+											Anum_pg_trigger_tgargs,
+											RelationGetDescr(pg_trigger),
+											&isnull));
+		if (isnull)
+			elog(ERROR, "invalid NULL tgargs");
+		argstr = (char *) VARDATA(tgargs);
+		lentgargs = VARSIZE_ANY_EXHDR(tgargs);
+
+		p = argstr;
+		for (findx = 0; findx < tgnargs; findx++)
+		{
+			size_t	tlen;
+
+			/* verify that the argument encoding is correct */
+			tlen = strlen(p);
+			if (p + tlen >= argstr + lentgargs)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid argument string (%s) for trigger \"%s\"",
+								argstr, NameStr(trigForm->tgname))));
+
+			list = lappend(list, new_string_object(p));
+
+			p += tlen + 1;
+		}
+	}
+
+	append_array_object(tmp, "args", list);		/* might be NIL */
+
+	append_object_object(trigger, "function", tmp);
+
+	heap_close(pg_trigger, AccessShareLock);
+
+	return trigger;
+}
+
+/*
+ * 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(Relation relation, List *dpcontext, bool composite,
+				  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);
+
+	/* Composite types use a slightly simpler format string */
+	if (composite)
+		column = new_objtree_VA("%{name}I %{coltype}T %{collation}s",
+								3,
+								"type", ObjTypeString, "column",
+								"name", ObjTypeString, coldef->colname,
+								"coltype", ObjTypeObject,
+								new_objtree_for_type(typid, typmod));
+	else
+		column = new_objtree_VA("%{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(typid, typmod));
+
+	tmp = new_objtree_VA("COLLATE %{name}D", 0);
+	if (OidIsValid(typcollation))
+	{
+		ObjTree *collname;
+
+		collname = new_objtree_for_qualname_id(CollationRelationId,
+											   typcollation);
+		append_object_object(tmp, "name", collname);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(column, "collation", tmp);
+
+	if (!composite)
+	{
+		/*
+		 * 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.  Therefore,
+		 * we scan the list of constraints attached to this column to determine
+		 * whether we need to emit anything.
+		 * (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("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(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("%{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(Relation relation, List *tableElements, List *dpcontext,
+					 bool typed, bool composite)
+{
+	List	   *elements = NIL;
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					ObjTree	   *tree;
+
+					tree = typed ?
+						deparse_ColumnDef_typed(relation, dpcontext,
+												(ColumnDef *) elt) :
+						deparse_ColumnDef(relation, dpcontext,
+										  composite, (ColumnDef *) elt);
+					if (tree != NULL)
+					{
+						ObjElem    *column;
+
+						column = new_object_object(tree);
+						elements = lappend(elements, column);
+					}
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+
+	return elements;
+}
+
+/*
+ * obtainConstraints
+ *		Subroutine for CREATE TABLE/CREATE DOMAIN deparsing
+ *
+ * Given a table OID or domain OID, obtain its constraints and append them to
+ * the given elements list.  The updated list is returned.
+ *
+ * This works for typed tables, regular tables, and domains.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static List *
+obtainConstraints(List *elements, Oid relationId, Oid domainId)
+{
+	Relation	conRel;
+	ScanKeyData key;
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	ObjTree    *tmp;
+
+	/* only one may be valid */
+	Assert(OidIsValid(relationId) ^ OidIsValid(domainId));
+
+	/*
+	 * scan pg_constraint to fetch all constraints linked to the given
+	 * relation.
+	 */
+	conRel = heap_open(ConstraintRelationId, AccessShareLock);
+	if (OidIsValid(relationId))
+	{
+		ScanKeyInit(&key,
+					Anum_pg_constraint_conrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relationId));
+		scan = systable_beginscan(conRel, ConstraintRelidIndexId,
+								  true, NULL, 1, &key);
+	}
+	else
+	{
+		Assert(OidIsValid(domainId));
+		ScanKeyInit(&key,
+					Anum_pg_constraint_contypid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(domainId));
+		scan = systable_beginscan(conRel, ConstraintTypidIndexId,
+								  true, NULL, 1, &key);
+	}
+
+	/*
+	 * 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:
+				continue;	/* not here */
+			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("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, new_object_object(tmp));
+	}
+
+	systable_endscan(scan);
+	heap_close(conRel, AccessShareLock);
+
+	return elements;
+}
+
+/*
+ * deparse the ON COMMMIT ... clause for CREATE ... TEMPORARY ...
+ */
+static ObjTree *
+deparse_OnCommitClause(OnCommitAction option)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0);
+	switch (option)
+	{
+		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;
+	}
+
+	return tmp;
+}
+
+/*
+ * Deparse DefElems, as used e.g. by ALTER COLUMN ... SET, into a list of SET
+ * (...)  or RESET (...) contents.
+ */
+static ObjTree *
+deparse_DefElem(DefElem *elem, bool is_reset)
+{
+	ObjTree	   *set;
+	ObjTree	   *optname;
+
+	if (elem->defnamespace != NULL)
+		optname = new_objtree_VA("%{schema}I.%{label}I", 1,
+								 "schema", ObjTypeString, elem->defnamespace);
+	else
+		optname = new_objtree_VA("%{label}I", 0);
+
+	append_string_object(optname, "label", elem->defname);
+
+	if (is_reset)
+		set = new_objtree_VA("%{label}s", 0);
+	else
+		set = new_objtree_VA("%{label}s = %{value}L", 1,
+							 "value", ObjTypeString,
+							 elem->arg ? defGetString(elem) :
+							 defGetBoolean(elem) ? "TRUE" : "FALSE");
+
+	append_object_object(set, "label", optname);
+	return set;
+}
+
+/*
+ * deparse_CreateStmt
+ *		Deparse a CreateStmt (CREATE TABLE)
+ *
+ * Given a table OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *list;
+	ListCell   *cell;
+	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 %{if_not_exists}s %{identity}D "
+			"OF %{of_type}T %{table_elements}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) %{inherits}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+
+	createStmt =
+		new_objtree_VA(fmtstr, 1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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(relation->rd_rel->reloftype, -1);
+		append_object_object(createStmt, "of_type", tmp);
+
+		tableelts = deparseTableElements(relation, node->tableElts, dpcontext,
+										 true,		/* typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+		if (tableelts == NIL)
+			tmp = new_objtree_VA("", 1,
+								 "present", ObjTypeBool, false);
+		else
+			tmp = new_objtree_VA("(%{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(relation, node->tableElts, dpcontext,
+										 false,		/* not typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+		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("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(RelationRelationId,
+													 formInh->inhparent);
+				parents = lappend(parents, new_object_object(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("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);
+
+	append_object_object(createStmt, "on_commit",
+						 deparse_OnCommitClause(node->oncommit));
+
+	/*
+	 * WITH clause.  We always emit one, containing at least the OIDS option.
+	 * That way we don't depend on the default value for default_with_oids.
+	 * We can skip emitting other options if there don't appear in the parse
+	 * node.
+	 */
+	tmp = new_objtree_VA("oids=%{value}s", 2,
+						 "option", ObjTypeString, "oids",
+						 "value", ObjTypeString,
+						 relation->rd_rel->relhasoids ? "ON" : "OFF");
+	list = list_make1(new_object_object(tmp));
+
+	foreach(cell, node->options)
+	{
+		DefElem	*opt = (DefElem *) lfirst(cell);
+
+		/* already handled above */
+		if (strcmp(opt->defname, "oids") == 0)
+			continue;
+
+		tmp = deparse_DefElem(opt, false);
+		list = lappend(list, new_object_object(tmp));
+	}
+	append_array_object(createStmt, "with", list);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
+/*
+ * deparse_CompositeTypeStmt
+ *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
+{
+	CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree;
+	ObjTree	   *composite;
+	HeapTuple	typtup;
+	Form_pg_type typform;
+	Relation	typerel;
+	List	   *dpcontext;
+	List	   *tableelts = NIL;
+
+	/* Find the pg_type entry and open the corresponding relation */
+	typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(typtup))
+		elog(ERROR, "cache lookup failed for type %u", objectId);
+	typform = (Form_pg_type) GETSTRUCT(typtup);
+	typerel = relation_open(typform->typrelid, AccessShareLock);
+
+	dpcontext = deparse_context_for(RelationGetRelationName(typerel),
+									RelationGetRelid(typerel));
+
+	composite = new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)",
+							   0);
+	append_object_object(composite, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+
+	tableelts = deparseTableElements(typerel, node->coldeflist, dpcontext,
+									 false,		/* not typed */
+									 true);		/* composite type */
+
+	append_array_object(composite, "columns", tableelts);
+
+	heap_close(typerel, AccessShareLock);
+	ReleaseSysCache(typtup);
+
+	return composite;
+}
+
+static inline ObjElem *
+deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->cache_value);
+	tmp = new_objtree_VA("CACHE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "cache",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Cycle(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA("%{no}s CYCLE",
+						 2,
+						 "clause", ObjTypeString, "cycle",
+						 "no", ObjTypeString,
+						 seqdata->is_cycled ? "" : "NO");
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_IncrementBy(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->increment_by);
+	tmp = new_objtree_VA("INCREMENT BY %{value}s",
+						 2,
+						 "clause", ObjTypeString, "increment_by",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Minvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->min_value);
+	tmp = new_objtree_VA("MINVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "minvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Maxvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->max_value);
+	tmp = new_objtree_VA("MAXVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "maxvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Startwith(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->start_value);
+	tmp = new_objtree_VA("START WITH %{value}s",
+						 2,
+						 "clause", ObjTypeString, "start",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Restart(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->last_value);
+	tmp = new_objtree_VA("RESTART %{value}s",
+						 2,
+						 "clause", ObjTypeString, "restart",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static ObjElem *
+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(RelationRelationId, ownerId);
+		append_string_object(tmp, "attrname", colname);
+		ownedby = new_objtree_VA("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("OWNED BY %{owner}D",
+								 3,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeNull,
+								 "present", ObjTypeBool, false);
+	return new_object_object(ownedby);
+}
+
+/*
+ * deparse_CreateSeqStmt
+ *		deparse a CreateSeqStmt
+ *
+ * Given a sequence OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *createSeq;
+	ObjTree    *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+
+	seqdata = get_sequence_values(objectId);
+
+	createSeq =
+		new_objtree_VA("CREATE %{persistence}s SEQUENCE %{identity}D "
+					   "%{definition: }s",
+					   1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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);
+
+	relation_close(relation, AccessShareLock);
+
+	return createSeq;
+}
+
+/*
+ * deparse_AlterSeqStmt
+ *		deparse an AlterSeqStmt
+ *
+ * Given a sequence OID and a parsetree that modified it, return an ObjTree
+ * representing the alter command.
+ */
+static ObjTree *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *alterSeq;
+	ObjTree	   *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+	ListCell   *cell;
+
+	seqdata = get_sequence_values(objectId);
+
+	alterSeq =
+		new_objtree_VA("ALTER SEQUENCE %{identity}D %{definition: }s", 0);
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(alterSeq, "identity", tmp);
+
+	foreach(cell, ((AlterSeqStmt *) parsetree)->options)
+	{
+		DefElem *elem = (DefElem *) lfirst(cell);
+		ObjElem *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") == 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);
+
+	relation_close(relation, AccessShareLock);
+
+	return alterSeq;
+}
+
+/*
+ * deparse_IndexStmt
+ *		deparse an IndexStmt
+ *
+ * Given an index OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ */
+static ObjTree *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+	IndexStmt  *node = (IndexStmt *) parsetree;
+	ObjTree    *indexStmt;
+	ObjTree    *tmp;
+	Relation	idxrel;
+	Relation	heaprel;
+	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);
+
+	pg_get_indexdef_detailed(objectId,
+							 &index_am, &definition, &reloptions,
+							 &tablespace, &whereClause);
+
+	indexStmt =
+		new_objtree_VA("CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   6,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "if_not_exists", ObjTypeString, node->if_not_exists ? "IF NOT EXISTS" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(heaprel->rd_rel->relnamespace,
+								   RelationGetRelationName(heaprel));
+	append_object_object(indexStmt, "table", tmp);
+
+	/* reloptions */
+	tmp = new_objtree_VA("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("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("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);
+
+	heap_close(idxrel, AccessShareLock);
+	heap_close(heaprel, AccessShareLock);
+
+	return indexStmt;
+}
+
+/*
+ * deparse_CreateSchemaStmt
+ *		deparse a CreateSchemaStmt
+ *
+ * Given a schema OID and the parsetree that created it, return an ObjTree
+ * 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 ObjTree *
+deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
+{
+	CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
+	ObjTree    *createSchema;
+	ObjTree    *auth;
+
+	createSchema =
+		new_objtree_VA("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("AUTHORIZATION %{authorization_role}I", 0);
+	if (node->authrole)
+		append_string_object(auth, "authorization_role",
+							 get_rolespec_name(node->authrole));
+	else
+	{
+		append_null_object(auth, "authorization_role");
+		append_bool_object(auth, "present", false);
+	}
+	append_object_object(createSchema, "authorization", auth);
+
+	return createSchema;
+}
+
+/*
  * Handle deparsing of simple commands.
  *
  * This function contains a large switch that mirrors that in
@@ -695,11 +1945,11 @@ deparse_simple_command(StashedCommand *cmd)
 	switch (nodeTag(parsetree))
 	{
 		case T_CreateSchemaStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateSchemaStmt(objectId, parsetree);
 			break;
 
 		case T_CreateStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateStmt(objectId, parsetree);
 			break;
 
 		case T_CreateForeignTableStmt:
@@ -722,7 +1972,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_IndexStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_IndexStmt(objectId, parsetree);
 			break;
 
 		case T_CreateExtensionStmt:
@@ -772,7 +2022,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CompositeTypeStmt(objectId, parsetree);
 			break;
 
 		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
@@ -804,11 +2054,11 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateSeqStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateSeqStmt(objectId, parsetree);
 			break;
 
 		case T_AlterSeqStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterSeqStmt(objectId, parsetree);
 			break;
 
 		case T_CreateTableAsStmt:
@@ -821,7 +2071,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateTrigStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateTrigStmt(objectId, parsetree);
 			break;
 
 		case T_CreatePLangStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b7a81ec..d9aab2d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -821,59 +821,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char	   *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.ctes = NIL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
@@ -914,6 +867,63 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char		relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+	initStringInfo(&buf);
+
+	relkind = get_rel_relkind(trigrec->tgrelid);
+
+	/* Build minimal OLD and NEW RTEs for the rel */
+	oldrte = makeNode(RangeTblEntry);
+	oldrte->rtekind = RTE_RELATION;
+	oldrte->relid = trigrec->tgrelid;
+	oldrte->relkind = relkind;
+	oldrte->alias = makeAlias("old", NIL);
+	oldrte->eref = oldrte->alias;
+	oldrte->lateral = false;
+	oldrte->inh = false;
+	oldrte->inFromCl = true;
+
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_RELATION;
+	newrte->relid = trigrec->tgrelid;
+	newrte->relkind = relkind;
+	newrte->alias = makeAlias("new", NIL);
+	newrte->eref = newrte->alias;
+	newrte->lateral = false;
+	newrte->inh = false;
+	newrte->inFromCl = true;
+
+	/* Build two-element rtable */
+	memset(&dpns, 0, sizeof(dpns));
+	dpns.rtable = list_make2(oldrte, newrte);
+	dpns.ctes = NIL;
+	set_rtable_names(&dpns, NIL, NULL);
+	set_simple_column_names(&dpns);
+
+	/* Set up context with one-deep namespace stack */
+	context.buf = &buf;
+	context.namespaces = list_make1(&dpns);
+	context.windowClause = NIL;
+	context.windowTList = NIL;
+	context.varprefix = true;
+	context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+	context.wrapColumn = WRAP_COLUMN_DEFAULT;
+	context.indentLevel = PRETTYINDENT_STD;
+
+	get_rule_expr(whenClause, &context, false);
+
+	return buf.data;
+}
+
 /* ----------
  * get_indexdef			- Get the definition of an index
  *
@@ -977,6 +987,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,
@@ -1256,6 +1268,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
@@ -1290,9 +1541,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);
 }
 
 /*
@@ -9388,3 +9639,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/commands/sequence.h b/src/include/commands/sequence.h
index 44862bb..8d8e556 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -72,6 +72,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 ObjectAddress DefineSequence(CreateSeqStmt *stmt);
 extern ObjectAddress AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index fed9c7b..4447af9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,6 +13,7 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
@@ -20,8 +21,16 @@
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+						  Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 
-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);
@@ -32,4 +41,7 @@ extern List *select_rtable_names_for_explain(List *rtable,
 								Bitmapset *rels_used);
 extern char *generate_collation_name(Oid collid);
 
+extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
+						 List *dpcontext);
+
 #endif	/* RULEUTILS_H */
-- 
2.1.4

0006-deparse-Support-CREATE-TYPE-AS-ENUM-RANGE.patchtext/x-diff; charset=us-asciiDownload
>From 4d2cb9eed3394a3b35dcc061e2336e76d8d16284 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:55:43 -0300
Subject: [PATCH 06/29] deparse: Support CREATE TYPE AS ENUM / RANGE

---
 src/backend/tcop/deparse_utility.c | 134 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 132 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 8b1e749..4a7f365 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1523,6 +1523,136 @@ deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
 	return composite;
 }
 
+/*
+ * deparse_CreateEnumStmt
+ *		Deparse a CreateEnumStmt (CREATE TYPE AS ENUM)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
+{
+	CreateEnumStmt *node = (CreateEnumStmt *) parsetree;
+	ObjTree	   *enumtype;
+	List	   *values;
+	ListCell   *cell;
+
+	enumtype = new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)",
+							  0);
+	append_object_object(enumtype, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	values = NIL;
+	foreach(cell, node->vals)
+	{
+		Value   *val = (Value *) lfirst(cell);
+
+		values = lappend(values, new_string_object(strVal(val)));
+	}
+	append_array_object(enumtype, "values", values);
+
+	return enumtype;
+}
+
+static ObjTree *
+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;
+
+	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(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(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(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(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(tmp));
+	}
+
+	append_array_object(range, "definition", definition);
+
+	systable_endscan(scan);
+	heap_close(pg_range, RowExclusiveLock);
+
+	return range;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -2026,11 +2156,11 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateEnumStmt(objectId, parsetree);
 			break;
 
 		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateRangeStmt(objectId, parsetree);
 			break;
 
 		case T_AlterEnumStmt:
-- 
2.1.4

0001-add-secondaryOid-to-DefineTSConfiguration.patchtext/x-diff; charset=us-asciiDownload
>From 47ea914ec1047f44c3e64cd0a10a2186215745ea Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 16 Mar 2015 17:51:03 -0300
Subject: [PATCH 01/29] add secondaryOid to DefineTSConfiguration()

In the spirit of a2e35b53c39: we need an extra output argument to know
about a tsconfig referenced in the COPY clause of CREATE TS CONFIG.
---
 src/backend/commands/tsearchcmds.c | 10 +++++++++-
 src/include/commands/defrem.h      |  3 ++-
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 45bafd3..4c404e7 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -964,7 +964,7 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
  * CREATE TEXT SEARCH CONFIGURATION
  */
 ObjectAddress
-DefineTSConfiguration(List *names, List *parameters)
+DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
 {
 	Relation	cfgRel;
 	Relation	mapRel = NULL;
@@ -1013,6 +1013,14 @@ DefineTSConfiguration(List *names, List *parameters)
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("cannot specify both PARSER and COPY options")));
 
+	/* make copied tsconfig available to callers */
+	if (copied && OidIsValid(sourceOid))
+	{
+		ObjectAddressSet(*copied,
+						 TSConfigRelationId,
+						 sourceOid);
+	}
+
 	/*
 	 * Look up source config if given.
 	 */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index a9c6783..595f93f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -101,7 +101,8 @@ extern ObjectAddress AlterTSDictionary(AlterTSDictionaryStmt *stmt);
 extern ObjectAddress DefineTSTemplate(List *names, List *parameters);
 extern void RemoveTSTemplateById(Oid tmplId);
 
-extern ObjectAddress DefineTSConfiguration(List *names, List *parameters);
+extern ObjectAddress DefineTSConfiguration(List *names, List *parameters,
+					  ObjectAddress *copied);
 extern void RemoveTSConfigurationById(Oid cfgId);
 extern ObjectAddress AlterTSConfiguration(AlterTSConfigurationStmt *stmt);
 
-- 
2.1.4

0007-deparse-Support-EXTENSION-commands.patchtext/x-diff; charset=us-asciiDownload
>From d6c62f463590872ca67e3e00a628f439f07acd56 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 07/29] deparse: Support EXTENSION commands

CREATE EXTENSION
ALTER EXTENSION / UPDATE TO
ALTER EXTENSION ADD/DROP
---
 src/backend/tcop/deparse_utility.c | 148 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 145 insertions(+), 3 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 4a7f365..917a25b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -693,6 +693,147 @@ 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 ObjTree *
+deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
+{
+	CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *extStmt;
+	ObjTree	   *tmp;
+	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(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(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(tmp));
+
+	append_array_object(extStmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return extStmt;
+}
+
+static ObjTree *
+deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
+{
+	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list = NIL;
+	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 %{options: }s", 1,
+						  "identity", ObjTypeString,
+						  NameStr(extForm->extname));
+
+	foreach(cell, node->options)
+	{
+		DefElem *opt = (DefElem *) lfirst(cell);
+
+		if (strcmp(opt->defname, "new_version") == 0)
+		{
+			tmp = new_objtree_VA("TO %{version}L", 2,
+								 "type", ObjTypeString, "version",
+								 "version", ObjTypeString, defGetString(opt));
+			list = lappend(list, new_object_object(tmp));
+		}
+		else
+			elog(ERROR, "unsupported option %s", opt->defname);
+	}
+
+	append_array_object(stmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree,
+								   ObjectAddress objectAddress)
+{
+	AlterExtensionContentsStmt *node = (AlterExtensionContentsStmt *) parsetree;
+	ObjTree	   *stmt;
+	char	   *fmt;
+
+	Assert(node->action == +1 || node->action == -1);
+
+	fmt = psprintf("ALTER EXTENSION %%{extidentity}I %s %s %%{objidentity}s",
+				   node->action == +1 ? "ADD" : "DROP",
+				   stringify_objtype(node->objtype));
+
+	stmt = new_objtree_VA(fmt, 2, "extidentity", ObjTypeString, node->extname,
+						  "objidentity", ObjTypeString,
+						  getObjectIdentity(&objectAddress));
+
+	return stmt;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -2106,15 +2247,16 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateExtensionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionContentsStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterExtensionContentsStmt(objectId, parsetree,
+														 cmd->d.simple.secondaryObject);
 			break;
 
 		case T_CreateFdwStmt:
-- 
2.1.4

0008-deparse-Support-CREATE-RULE.patchtext/x-diff; charset=us-asciiDownload
>From 070fff94c9bba20c2eb142b0fecaaca4226f1f76 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 08/29] deparse: Support CREATE RULE

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 917a25b..d3dfba6 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2154,6 +2154,95 @@ deparse_IndexStmt(Oid objectId, Node *parsetree)
 	return indexStmt;
 }
 
+static ObjTree *
+deparse_RuleStmt(Oid objectId, Node *parsetree)
+{
+	RuleStmt *node = (RuleStmt *) parsetree;
+	ObjTree	   *ruleStmt;
+	ObjTree	   *tmp;
+	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(action));
+	}
+	append_array_object(ruleStmt, "actions", list);
+
+	systable_endscan(scan);
+	heap_close(pg_rewrite, AccessShareLock);
+
+	return ruleStmt;
+}
+
+
+
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -2322,7 +2411,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RuleStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RuleStmt(objectId, parsetree);
 			break;
 
 		case T_CreateSeqStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d9aab2d..169119c 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 4447af9..9989cdd 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -30,6 +30,8 @@ extern void pg_get_indexdef_detailed(Oid indexrelid,
 extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 						  Node *whenClause, bool pretty);
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+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);
-- 
2.1.4

0009-deparse-Support-ALTER-TYPE-ADD-VALUE-enums.patchtext/x-diff; charset=us-asciiDownload
>From 07cff36eb78b2161c209336bc0167ed9b6069585 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 09/29] deparse: Support ALTER TYPE / ADD VALUE (enums)

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index d3dfba6..a586e55 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2283,6 +2283,39 @@ deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
 	return createSchema;
 }
 
+static ObjTree *
+deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
+{
+	AlterEnumStmt *node = (AlterEnumStmt *) parsetree;
+	ObjTree	   *alterEnum;
+	ObjTree	   *tmp;
+
+	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);
+
+	return alterEnum;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -2395,7 +2428,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterEnumStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterEnumStmt(objectId, parsetree);
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-- 
2.1.4

0010-deparse-Support-ALTER-.-RENAME-OWNER-SCHEMA.patchtext/x-diff; charset=us-asciiDownload
>From e6c10991d8582d220953cf2f4d23dee1b3ef47d6 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 10/29] deparse: Support ALTER .. {RENAME/OWNER/SCHEMA}

---
 src/backend/commands/alter.c       |   1 -
 src/backend/tcop/deparse_utility.c | 483 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/regproc.c    | 101 +++++---
 src/include/utils/builtins.h       |   1 +
 4 files changed, 553 insertions(+), 33 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 3ddd7ec..d28758c 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -446,7 +446,6 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 				Relation	relation;
 				Oid			classId;
 				Oid			nspOid;
-				ObjectAddress address;
 
 				address = get_object_address(stmt->objectType,
 											 stmt->object,
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index a586e55..c9a89b5 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -144,6 +144,7 @@ static void append_integer_object(ObjTree *tree, char *name, int64 value);
 static void append_float_object(ObjTree *tree, char *name, float8 value);
 static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
 static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+static const char *stringify_objtype(ObjectType objtype);
 
 /*
  * Allocate a new object tree to store parameter values.
@@ -1794,6 +1795,478 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return range;
 }
 
+/*
+ * Return the given object type as a string.
+ */
+static const char *
+stringify_objtype(ObjectType objtype)
+{
+	switch (objtype)
+	{
+		case OBJECT_AGGREGATE:
+			return "AGGREGATE";
+		case OBJECT_CAST:
+			return "CAST";
+		case OBJECT_COLUMN:
+			return "COLUMN";
+		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 "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_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_USER_MAPPING:
+			return "USER MAPPING";
+		case OBJECT_VIEW:
+			return "VIEW";
+
+		default:
+			elog(ERROR, "unsupported object type %d", objtype);
+	}
+}
+
+static ObjTree *
+deparse_RenameStmt(ObjectAddress address, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree	   *renameStmt;
+	char	   *fmtstr;
+	Relation	relation;
+	Oid			schemaId;
+
+	/*
+	 * 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(address.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_ATTRIBUTE:
+		case OBJECT_COLUMN:
+			relation = relation_open(address.objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			if (node->renameType == OBJECT_ATTRIBUTE)
+			{
+				fmtstr = "ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I TO %{newname}I %{cascade}s";
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_object_object(renameStmt, "cascade",
+									 new_objtree_VA("CASCADE", 1,
+													"present", ObjTypeBool,
+													node->behavior == DROP_CASCADE));
+			}
+			else
+			{
+				fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME COLUMN %%{colname}I TO %%{newname}I",
+								  stringify_objtype(node->relationType));
+				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);
+
+			/* composite types do not support IF EXISTS */
+			if (node->relationType != OBJECT_TYPE)
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+
+			break;
+
+		case OBJECT_SCHEMA:
+			{
+				renameStmt =
+					new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I",
+								   0);
+				append_string_object(renameStmt, "identity", node->subname);
+			}
+			break;
+
+		case OBJECT_FDW:
+		case OBJECT_LANGUAGE:
+		case OBJECT_FOREIGN_SERVER:
+			{
+				fmtstr = psprintf("ALTER %s %%{identity}I RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_string_object(renameStmt, "identity",
+									 strVal(linitial(node->object)));
+			}
+			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:
+			{
+				HeapTuple	objTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				AttrNumber	Anum_namespace;
+				ObjTree	   *ident;
+
+				/* obtain object tuple */
+				catalog = relation_open(address.classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, address.objectId);
+
+				/* obtain namespace */
+				Anum_namespace = get_object_attnum_namespace(address.classId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				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(DatumGetObjectId(objnsp),
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+
+				relation_close(catalog, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_OPCLASS:
+		case OBJECT_OPFAMILY:
+			{
+				HeapTuple	objTup;
+				HeapTuple	amTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				AttrNumber	Anum_namespace;
+				Oid			amoid;
+				ObjTree	   *ident;
+
+				/* obtain object tuple */
+				catalog = relation_open(address.classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, address.objectId);
+
+				/* obtain namespace */
+				Anum_namespace = get_object_attnum_namespace(address.classId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				/* obtain AM tuple */
+				if (node->renameType == OBJECT_OPCLASS)
+					amoid = ((Form_pg_opclass) GETSTRUCT(objTup))->opcmethod;
+				else
+					amoid = ((Form_pg_opfamily) GETSTRUCT(objTup))->opfmethod;
+				amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+				if (!HeapTupleIsValid(amTup))
+					elog(ERROR, "cache lookup failed for access method %u", amoid);
+
+
+				fmtstr = psprintf("ALTER %s %%{identity}D USING %%{amname}I RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+
+				/* add the identity clauses */
+				ident = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+				append_string_object(renameStmt, "amname",
+									 pstrdup(NameStr(((Form_pg_am) GETSTRUCT(amTup))->amname)));
+
+				ReleaseSysCache(amTup);
+				relation_close(catalog, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			{
+				char	*ident;
+				HeapTuple proctup;
+				Form_pg_proc procform;
+
+				proctup = SearchSysCache1(PROCOID,
+										  ObjectIdGetDatum(address.objectId));
+				if (!HeapTupleIsValid(proctup))
+					elog(ERROR, "cache lookup failed for procedure %u",
+						 address.objectId);
+				procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+				/* XXX does this work for ordered-set aggregates? */
+				ident = psprintf("%s%s",
+								 quote_qualified_identifier(get_namespace_name(procform->pronamespace),
+															strVal(llast(node->object))),
+								 format_procedure_args(address.objectId, true));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 1,
+											"identity", ObjTypeString, ident);
+
+				ReleaseSysCache(proctup);
+			}
+			break;
+
+		case OBJECT_TABCONSTRAINT:
+		case OBJECT_DOMCONSTRAINT:
+			{
+				HeapTuple		conTup;
+				Form_pg_constraint	constrForm;
+				ObjTree		   *ident;
+
+				conTup = SearchSysCache1(CONSTROID, address.objectId);
+				constrForm = (Form_pg_constraint) GETSTRUCT(conTup);
+
+				if (node->renameType == OBJECT_TABCONSTRAINT)
+				{
+					fmtstr = "ALTER TABLE %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(RelationRelationId,
+														constrForm->conrelid);
+				}
+				else
+				{
+					fmtstr = "ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(TypeRelationId,
+														constrForm->contypid);
+				}
+				renameStmt = new_objtree_VA(fmtstr, 2,
+											"conname", ObjTypeString, node->subname,
+											"identity", ObjTypeObject, ident);
+				ReleaseSysCache(conTup);
+			}
+			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, address.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, address.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;
+
+		case OBJECT_POLICY:
+			{
+				HeapTuple	polTup;
+				Form_pg_policy polForm;
+				Relation	pg_policy;
+				ScanKeyData	key;
+				SysScanDesc	scan;
+
+				pg_policy = relation_open(PolicyRelationId, AccessShareLock);
+				ScanKeyInit(&key, ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(address.objectId));
+				scan = systable_beginscan(pg_policy, PolicyOidIndexId, true,
+										  NULL, 1, &key);
+				polTup = systable_getnext(scan);
+				if (!HeapTupleIsValid(polTup))
+					elog(ERROR, "cache lookup failed for policy %u", address.objectId);
+				polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+				renameStmt = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I on %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "policyname", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 polForm->polrelid));
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+				systable_endscan(scan);
+				relation_close(pg_policy, AccessShareLock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	append_string_object(renameStmt, "newname", node->newname);
+
+	return renameStmt;
+}
+
+/*
+ * deparse an ALTER ... SET SCHEMA command.
+ */
+static ObjTree *
+deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree,
+							  ObjectAddress oldschema)
+{
+	AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree;
+	ObjTree	   *alterStmt;
+	char	   *fmt;
+	char	   *identity;
+	char	   *newschema;
+	char	   *oldschname;
+	char	   *ident;
+
+	newschema = node->newschema;
+
+	fmt = psprintf("ALTER %s %%{identity}s SET SCHEMA %%{newschema}I",
+				   stringify_objtype(node->objectType));
+	alterStmt = new_objtree_VA(fmt, 0);
+	append_string_object(alterStmt, "newschema", newschema);
+
+	/*
+	 * Since the command has already taken place from the point of view of
+	 * catalogs, getObjectIdentity returns the object name with the already
+	 * changed schema.  The output of our deparsing must return the original
+	 * schema name however, so we chop the schema name off the identity string
+	 * and then prepend the quoted schema name.
+	 *
+	 * XXX This is pretty clunky. Can we do better?
+	 */
+	identity = getObjectIdentity(&address);
+	oldschname = get_namespace_name(oldschema.objectId);
+	if (!oldschname)
+		elog(ERROR, "cache lookup failed for schema with OID %u", oldschema.objectId);
+	ident = psprintf("%s%s",
+					 quote_identifier(oldschname),
+					 identity + strlen(quote_identifier(newschema)));
+	append_string_object(alterStmt, "identity", ident);
+
+	return alterStmt;
+}
+
+static ObjTree *
+deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+	ObjTree	   *ownerStmt;
+	char	   *fmt;
+
+	fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newowner}I",
+				   stringify_objtype(node->objectType));
+	ownerStmt = new_objtree_VA(fmt, 0);
+	append_string_object(ownerStmt, "newowner",
+						 get_rolespec_name(node->newowner));
+
+	append_string_object(ownerStmt, "identity",
+						 getObjectIdentity(&address));
+
+	return ownerStmt;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -2241,8 +2714,6 @@ deparse_RuleStmt(Oid objectId, Node *parsetree)
 	return ruleStmt;
 }
 
-
-
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -2510,15 +2981,17 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RenameStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RenameStmt(cmd->d.simple.address, parsetree);
 			break;
 
 		case T_AlterObjectSchemaStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
+													parsetree,
+													cmd->d.simple.secondaryObject);
 			break;
 
 		case T_AlterOwnerStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree);
 			break;
 
 		case T_CommentStmt:
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 3d1bb32..68447ca 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -42,7 +42,11 @@
 #include "utils/tqual.h"
 
 static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
+static char *format_procedure_internal(Oid procedure_oid, bool force_qualify,
+						  bool args_only);
+static void format_procedure_args_internal(Form_pg_proc procform,
+							   StringInfo buf, bool force_qualify);
+
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -363,13 +367,36 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_internal(procedure_oid, false, false);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_internal(procedure_oid, true, false);
+}
+
+/*
+ * format_procedure_args	- converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+	StringInfoData buf;
+	HeapTuple	proctup;
+	Form_pg_proc procform;
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+	initStringInfo(&buf);
+	format_procedure_args_internal(procform, &buf, force_qualify);
+
+	ReleaseSysCache(proctup);
+
+	return buf.data;
 }
 
 /*
@@ -380,7 +407,7 @@ format_procedure_qualified(Oid procedure_oid)
  * qualified if the function is not in path.
  */
 static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+format_procedure_internal(Oid procedure_oid, bool force_qualify, bool args_only)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -391,8 +418,6 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 	{
 		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
 		char	   *proname = NameStr(procform->proname);
-		int			nargs = procform->pronargs;
-		int			i;
 		char	   *nspname;
 		StringInfoData buf;
 
@@ -400,29 +425,24 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		initStringInfo(&buf);
 
-		/*
-		 * Would this proc be found (given the right args) by regprocedurein?
-		 * If not, or if caller requests it, we need to qualify it.
-		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
-			nspname = NULL;
-		else
-			nspname = get_namespace_name(procform->pronamespace);
-
-		appendStringInfo(&buf, "%s(",
-						 quote_qualified_identifier(nspname, proname));
-		for (i = 0; i < nargs; i++)
+		if (!args_only)
 		{
-			Oid			thisargtype = procform->proargtypes.values[i];
-
-			if (i > 0)
-				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf,
-								   force_qualify ?
-								   format_type_be_qualified(thisargtype) :
-								   format_type_be(thisargtype));
+			/*
+			 * Would this proc be found (given the right args) by
+			 * regprocedurein?  If not, or if caller requests it, we need to
+			 * qualify it.
+			 */
+			if (!force_qualify && FunctionIsVisible(procedure_oid))
+				nspname = NULL;
+			else
+				nspname = get_namespace_name(procform->pronamespace);
+
+			appendStringInfo(&buf, "%s",
+							 quote_qualified_identifier(nspname, proname));
 		}
-		appendStringInfoChar(&buf, ')');
+
+		/* add the attributes */
+		format_procedure_args_internal(procform, &buf, force_qualify);
 
 		result = buf.data;
 
@@ -439,6 +459,33 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 }
 
 /*
+ * Append the parenthised arguments of the given pg_proc row into the output
+ * buffer.  force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+							   bool force_qualify)
+{
+	int			i;
+	int			nargs = procform->pronargs;
+
+	appendStringInfoChar(buf, '(');
+	for (i = 0; i < nargs; i++)
+	{
+		Oid			thisargtype = procform->proargtypes.values[i];
+
+		if (i > 0)
+			appendStringInfoChar(buf, ',');
+		appendStringInfoString(buf,
+							   force_qualify ?
+							   format_type_be_qualified(thisargtype) :
+							   format_type_be(thisargtype));
+	}
+	appendStringInfoChar(buf, ')');
+}
+
+/*
  * Output a objname/objargs representation for the procedure with the
  * given OID.  If it doesn't exist, an error is thrown.
  *
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 470a325..dd539fe 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -644,6 +644,7 @@ extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
 					  List **objargs);
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-- 
2.1.4

0011-deparse-Support-CREATE-ALTER-DOMAIN.patchtext/x-diff; charset=us-asciiDownload
>From d2986b4f1735b8e18c17abc91eb6fe349262d8c9 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 11/29] deparse: Support CREATE/ALTER DOMAIN

---
 src/backend/tcop/deparse_utility.c | 140 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  23 ++++++
 src/include/utils/ruleutils.h      |   2 +
 3 files changed, 163 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index c9a89b5..d5a4d05 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -834,6 +834,88 @@ deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree,
 	return stmt;
 }
 
+static ObjTree *
+deparse_AlterDomainStmt(Oid objectId, Node *parsetree,
+						ObjectAddress constraintAddr)
+{
+	AlterDomainStmt *node = (AlterDomainStmt *) parsetree;
+	HeapTuple	domTup;
+	Form_pg_type domForm;
+	ObjTree	   *alterDom;
+	char	   *fmt;
+
+	/* ALTER DOMAIN DROP CONSTRAINT is handled by the DROP support code */
+	if (node->subtype == 'X')
+		return NULL;
+
+	domTup = SearchSysCache1(TYPEOID, objectId);
+	if (!HeapTupleIsValid(domTup))
+		elog(ERROR, "cache lookup failed for domain with OID %u",
+			 objectId);
+	domForm = (Form_pg_type) GETSTRUCT(domTup);
+
+	switch (node->subtype)
+	{
+		case 'T':
+			/* SET DEFAULT / DROP DEFAULT */
+			if (node->def == NULL)
+				fmt = "ALTER DOMAIN %{identity}D DROP DEFAULT";
+			else
+				fmt = "ALTER DOMAIN %{identity}D SET DEFAULT %{default}s";
+			break;
+		case 'N':
+			/* DROP NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D DROP NOT NULL";
+			break;
+		case 'O':
+			/* SET NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D SET NOT NULL";
+			break;
+		case 'C':
+			/* ADD CONSTRAINT.  Only CHECK constraints are supported by domains */
+			fmt = "ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}I %{definition}s";
+			break;
+		case 'V':
+			/* VALIDATE CONSTRAINT */
+			fmt = "ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}I";
+			break;
+		default:
+			elog(ERROR, "invalid subtype %c", node->subtype);
+	}
+
+	alterDom = new_objtree_VA(fmt, 0);
+	append_object_object(alterDom, "identity",
+						 new_objtree_for_qualname(domForm->typnamespace,
+												  NameStr(domForm->typname)));
+
+	/*
+	 * Process subtype-specific options.  Validating a constraint
+	 * requires its name ...
+	 */
+	if (node->subtype == 'V')
+		append_string_object(alterDom, "constraint_name", node->name);
+
+	/* ... a new constraint has a name and definition ... */
+	if (node->subtype == 'C')
+	{
+		append_string_object(alterDom, "definition",
+							 pg_get_constraintdef_string(constraintAddr.objectId,
+														 false));
+		/* can't rely on node->name here; might not be defined */
+		append_string_object(alterDom, "constraint_name",
+							 get_constraint_name(constraintAddr.objectId));
+	}
+
+	/* ... and setting a default has a definition only. */
+	if (node->subtype == 'T' && node->def != NULL)
+		append_string_object(alterDom, "default", DomainGetDefault(domTup));
+
+	/* done */
+	ReleaseSysCache(domTup);
+
+	return alterDom;
+}
+
 /*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
@@ -1795,6 +1877,59 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return range;
 }
 
+static ObjTree *
+deparse_CreateDomain(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *createDomain;
+	ObjTree	   *tmp;
+	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}T %{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_type(typForm->typbasetype, typForm->typtypmod));
+
+	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);
+
+	return createDomain;
+}
+
 /*
  * Return the given object type as a string.
  */
@@ -2827,7 +2962,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterDomainStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterDomainStmt(objectId, parsetree,
+											  cmd->d.simple.secondaryObject);
 			break;
 
 			/* other local objects */
@@ -2944,7 +3080,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateDomainStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateDomain(objectId, parsetree);
 			break;
 
 		case T_CreateConversionStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 169119c..791673a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9728,3 +9728,26 @@ RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
 
 	return defstr;
 }
+
+/*
+ * Return the default value of a domain.
+ */
+char *
+DomainGetDefault(HeapTuple domTup)
+{
+	Datum	def;
+	Node   *defval;
+	char   *defstr;
+	bool	isnull;
+
+	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+							 &isnull);
+	if (isnull)
+		elog(ERROR, "domain \"%s\" does not have a default value",
+			 NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+	defval = stringToNode(TextDatumGetCString(def));
+	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
+									   false, false, 0, 0);
+
+	return defstr;
+}
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 9989cdd..828c80b 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -46,4 +46,6 @@ extern char *generate_collation_name(Oid collid);
 extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
 						 List *dpcontext);
 
+extern char *DomainGetDefault(HeapTuple domTup);
+
 #endif	/* RULEUTILS_H */
-- 
2.1.4

0012-deparse-Support-CREATE-ALTER-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From 0d67e3cc9de932025582000b6721b5c7186e6cab 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 12/29] deparse: Support CREATE/ALTER FUNCTION

Petr Jelinek, Andres Freund
---
 src/backend/tcop/deparse_utility.c | 467 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  25 ++
 src/include/utils/ruleutils.h      |   1 +
 3 files changed, 491 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index d5a4d05..721e83c 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1930,6 +1930,469 @@ deparse_CreateDomain(Oid objectId, Node *parsetree)
 	return createDomain;
 }
 
+static ObjTree *
+deparse_FunctionSet(VariableSetKind kind, char *name, char *value)
+{
+	ObjTree	   *r;
+
+	if (kind == VAR_RESET_ALL)
+	{
+		r = new_objtree_VA("RESET ALL", 0);
+	}
+	else if (value != NULL)
+	{
+		/*
+		 * Some GUC variable names are 'LIST' type and hence must not be
+		 * quoted. FIXME: shouldn't this and pg_get_functiondef() rather use
+		 * guc.c to check for GUC_LIST?
+		 */
+		if (pg_strcasecmp(name, "DateStyle") == 0
+			|| pg_strcasecmp(name, "search_path") == 0)
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}s", 0);
+		else
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}L", 0);
+
+		append_string_object(r, "set_name", name);
+		append_string_object(r, "set_value", value);
+	}
+	else
+	{
+		r = new_objtree_VA("RESET %{set_name}I", 0);
+		append_string_object(r, "set_name", name);
+	}
+
+	return r;
+}
+
+/*
+ * 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.
+ */
+static ObjTree *
+deparse_CreateFunction(Oid objectId, Node *parsetree)
+{
+	CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree;
+	ObjTree	   *createFunc;
+	ObjTree	   *sign;
+	ObjTree	   *tmp;
+	Datum		tmpdatum;
+	char	   *fmt;
+	char	   *definition;
+	char	   *source;
+	char	   *probin;
+	List	   *params;
+	List	   *defaults;
+	List	   *sets = NIL;
+	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)
+			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(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(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}n", 1,
+										"cost", ObjTypeFloat,
+										procForm->procost));
+
+	tmp = new_objtree_VA("ROWS %{rows}n", 0);
+	if (procForm->prorows == 0)
+		append_bool_object(tmp, "present", false);
+	else
+		append_float_object(tmp, "rows", procForm->prorows);
+	append_object_object(createFunc, "rows", tmp);
+
+	foreach(cell, node->options)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+			sets = lappend(sets, new_object_object(tmp));
+		}
+	}
+	append_array_object(createFunc, "set_options", sets);
+
+	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);
+
+	return createFunc;
+}
+
+/*
+ * deparse_AlterFunctionStmt
+ *		Deparse a AlterFunctionStmt (ALTER FUNCTION)
+ *
+ * Given a function OID and the parsetree that created it, return the JSON
+ * blob representing the alter command.
+ */
+static ObjTree *
+deparse_AlterFunction(Oid objectId, Node *parsetree)
+{
+	AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree;
+	ObjTree	   *alterFunc;
+	ObjTree	   *sign;
+	HeapTuple	procTup;
+	Form_pg_proc procForm;
+	List	   *params;
+	List	   *elems = NIL;
+	ListCell   *cell;
+	int			i;
+
+	/* 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);
+
+	alterFunc = new_objtree_VA("ALTER FUNCTION %{signature}s %{definition: }s", 0);
+
+	sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0);
+
+	params = NIL;
+
+	/*
+	 * ALTER FUNCTION does not change signature so we can use catalog
+	 * to get input type Oids.
+	 */
+	for (i = 0; i < procForm->pronargs; i++)
+	{
+		ObjTree	   *tmp = new_objtree_VA("%{type}T", 0);
+
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(procForm->proargtypes.values[i], -1));
+		params = lappend(params, new_object_object(tmp));
+	}
+
+	append_array_object(sign, "arguments", params);
+	append_object_object(sign, "identity",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 objectId));
+	append_object_object(alterFunc, "signature", sign);
+
+	foreach(cell, node->actions)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "volatility") == 0)
+		{
+			tmp = new_objtree_VA(strVal(defel->arg), 0);
+		}
+		else if (strcmp(defel->defname, "strict") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "RETURNS NULL ON NULL INPUT" :
+								 "CALLED ON NULL INPUT", 0);
+		}
+		else if (strcmp(defel->defname, "security") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "SECURITY DEFINER" : "SECURITY INVOKER", 0);
+		}
+		else if (strcmp(defel->defname, "leakproof") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "LEAKPROOF" : "NOT LEAKPROOF", 0);
+		}
+		else if (strcmp(defel->defname, "cost") == 0)
+		{
+			tmp = new_objtree_VA("COST %{cost}n", 1,
+								 "cost", ObjTypeFloat,
+								 defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "rows") == 0)
+		{
+			tmp = new_objtree_VA("ROWS %{rows}n", 0);
+			if (defGetNumeric(defel) == 0)
+				append_bool_object(tmp, "present", false);
+			else
+				append_float_object(tmp, "rows",
+									defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+		}
+
+		elems = lappend(elems, new_object_object(tmp));
+	}
+
+	append_array_object(alterFunc, "definition", elems);
+
+	ReleaseSysCache(procTup);
+
+	return alterFunc;
+}
+
 /*
  * Return the given object type as a string.
  */
@@ -3043,11 +3506,11 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateFunctionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateFunction(objectId, parsetree);
 			break;
 
 		case T_AlterFunctionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterFunction(objectId, parsetree);
 			break;
 
 		case T_RuleStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 791673a..7fbd4f9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9751,3 +9751,28 @@ DomainGetDefault(HeapTuple domTup)
 
 	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 828c80b..ce6a1b1 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -42,6 +42,7 @@ extern List *set_deparse_context_planstate(List *dpcontext,
 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);
-- 
2.1.4

0013-deparse-Support-ALTER-TABLE.patchtext/x-diff; charset=us-asciiDownload
>From 2fdb8687806c6ee85be781a81f0ff07d8a52b8c1 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 13/29] deparse: Support ALTER TABLE

With help from Andres Freund
---
 src/backend/commands/event_trigger.c | 134 +++++++-
 src/backend/commands/tablecmds.c     |   8 +
 src/backend/tcop/deparse_utility.c   | 621 ++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c           |  23 +-
 src/include/commands/event_trigger.h |   6 +
 src/include/tcop/deparse_utility.h   |   9 +
 6 files changed, 797 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 2cd7b3d..12bfad4 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -58,6 +58,7 @@ typedef struct EventTriggerQueryState
 
 	bool		commandCollectionInhibited;
 	MemoryContext cxt;
+	StashedCommand *curcmd;
 	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
@@ -1219,8 +1220,10 @@ EventTriggerBeginCompleteQuery(void)
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
+
 	state->commandCollectionInhibited = currentEventTriggerState ?
 		currentEventTriggerState->commandCollectionInhibited : false;
+	state->curcmd = NULL;
 	state->stash = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
@@ -1629,6 +1632,130 @@ EventTriggerUndoInhibitCommandCollection(void)
 	currentEventTriggerState->commandCollectionInhibited = false;
 }
 
+/*
+ * EventTriggerAlterTableStart
+ *		Prepare to receive data on an ALTER TABLE 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.
+ *
+ * XXX -- this API isn't considering the possibility of an ALTER TABLE command
+ * being called reentrantly by an event trigger function.  Do we need stackable
+ * commands at this level?  Perhaps at least we should detect the condition and
+ * raise an error.
+ */
+void
+EventTriggerAlterTableStart(Node *parsetree)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_AlterTable;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.alterTable.classId = RelationRelationId;
+	stashed->d.alterTable.objectId = InvalidOid;
+	stashed->d.alterTable.subcmds = NIL;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->curcmd = stashed;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remember the OID of the object being affected by a complex command.
+ *
+ * This is needed because in some cases we don't know the OID until later.
+ */
+void
+EventTriggerAlterTableRelid(Oid objectId)
+{
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	currentEventTriggerState->curcmd->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerAlterTableStashSubcmd
+ * 		Save data about a single part of a complex DDL command
+ *
+ * Several different commands go through this path, but apart from ALTER TABLE
+ * itself, they are all concerned with AlterTableCmd nodes that are generated
+ * internally, so that's all that this code needs to handle at the moment.
+ */
+void
+EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid, AttrNumber attnum,
+							 Oid newoid)
+{
+	MemoryContext	oldcxt;
+	StashedATSubcmd *newsub;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	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);
+}
+
+/*
+ * EventTriggerAlterTableEnd
+ * 		Finish up saving an ALTER TABLE command, and add it to stash
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerAlterTableEnd(void)
+{
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	/* 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)
 {
@@ -1708,7 +1835,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
 			MemSet(nulls, 0, sizeof(nulls));
 
-			if (cmd->type == SCT_Simple)
+			if (cmd->type == SCT_Simple ||
+				cmd->type == SCT_AlterTable)
 			{
 				const char *tag;
 				char	   *identity;
@@ -1717,6 +1845,10 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
 				if (cmd->type == SCT_Simple)
 					addr = cmd->d.simple.address;
+				else if (cmd->type == SCT_AlterTable)
+					ObjectAddressSet(addr,
+									 cmd->d.alterTable.classId,
+									 cmd->d.alterTable.objectId);
 
 				tag = CreateCommandTag(cmd->parsetree);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bd16ef0..d46541b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2773,6 +2773,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerAlterTableRelid(relid);
+
 	ATController(NULL, rel, cmds, recurse, lockmode);
 }
 
@@ -3651,6 +3653,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 	}
 
+	EventTriggerAlterTableStashSubcmd((Node *) cmd, RelationGetRelid(rel),
+									  colno, newoid);
+
 	/*
 	 * Bump the command counter to ensure the next subcommand in the sequence
 	 * can see the changes so far
@@ -9621,7 +9626,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		cmds = lappend(cmds, cmd);
 
+		EventTriggerAlterTableStart((Node *) stmt);
+		/* OID is set by AlterTableInternal */
 		AlterTableInternal(lfirst_oid(l), cmds, false);
+		EventTriggerAlterTableEnd();
 	}
 
 	return new_tablespaceoid;
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 721e83c..96b16dc 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1126,7 +1126,7 @@ deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
  */
 static ObjTree *
 deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
-				  ColumnDef *coldef)
+				  ColumnDef *coldef, bool is_alter)
 {
 	ObjTree    *column;
 	ObjTree    *tmp;
@@ -1194,6 +1194,9 @@ deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
 		 * we scan the list of constraints attached to this column to determine
 		 * whether we need to emit anything.
 		 * (Fortunately, NOT NULL constraints cannot be table constraints.)
+		 *
+		 * In the ALTER TABLE cases, we also add a NOT NULL if the colDef is
+		 * marked is_not_null.
 		 */
 		saw_notnull = false;
 		foreach(cell, coldef->constraints)
@@ -1203,6 +1206,8 @@ deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
 			if (constr->contype == CONSTR_NOTNULL)
 				saw_notnull = true;
 		}
+		if (is_alter && coldef->is_not_null)
+			saw_notnull = true;
 
 		if (saw_notnull)
 			append_string_object(column, "not_null", "NOT NULL");
@@ -1267,6 +1272,7 @@ deparse_ColumnDef_typed(Relation relation, List *dpcontext, ColumnDef *coldef)
 	/*
 	 * 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.
+	 * (This routine is never used for ALTER cases.)
 	 */
 	saw_notnull = false;
 	foreach(cell, coldef->constraints)
@@ -1320,7 +1326,8 @@ deparseTableElements(Relation relation, List *tableElements, List *dpcontext,
 						deparse_ColumnDef_typed(relation, dpcontext,
 												(ColumnDef *) elt) :
 						deparse_ColumnDef(relation, dpcontext,
-										  composite, (ColumnDef *) elt);
+										  composite, (ColumnDef *) elt,
+										  false);
 					if (tree != NULL)
 					{
 						ObjElem    *column;
@@ -1511,6 +1518,70 @@ deparse_DefElem(DefElem *elem, bool is_reset)
 }
 
 /*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ */
+static ObjTree *
+deparse_ColumnSetOptions(AlterTableCmd *subcmd)
+{
+	List	   *sets = NIL;
+	ListCell   *cell;
+	ObjTree    *tmp;
+	bool		is_reset = subcmd->subtype == AT_ResetOptions;
+
+	if (is_reset)
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I RESET (%{options:, }s)", 0);
+	else
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I SET (%{options:, }s)", 0);
+
+	append_string_object(tmp, "column", subcmd->name);
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem	   *elem;
+		ObjTree	   *set;
+
+		elem = (DefElem *) lfirst(cell);
+		set = deparse_DefElem(elem, is_reset);
+		sets = lappend(sets, new_object_object(set));
+	}
+
+	append_array_object(tmp, "options", sets);
+
+	return tmp;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ */
+static ObjTree *
+deparse_RelSetOptions(AlterTableCmd *subcmd)
+{
+	List	   *sets = NIL;
+	ListCell   *cell;
+	ObjTree    *tmp;
+	bool		is_reset = subcmd->subtype == AT_ResetRelOptions;
+
+	if (is_reset)
+		tmp = new_objtree_VA("RESET (%{options:, }s)", 0);
+	else
+		tmp = new_objtree_VA("SET (%{options:, }s)", 0);
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem	   *elem;
+		ObjTree	   *set;
+
+		elem = (DefElem *) lfirst(cell);
+		set = deparse_DefElem(elem, is_reset);
+		sets = lappend(sets, new_object_object(set));
+	}
+
+	append_array_object(tmp, "options", sets);
+
+	return tmp;
+}
+
+/*
  * deparse_CreateStmt
  *		Deparse a CreateStmt (CREATE TABLE)
  *
@@ -3385,6 +3456,549 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 	return alterEnum;
 }
 
+static ObjTree *
+deparse_AlterTableStmt(StashedCommand *cmd)
+{
+	ObjTree	   *alterTableStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	List	   *dpcontext;
+	Relation	rel;
+	List	   *subcmds = NIL;
+	ListCell   *cell;
+	char	   *fmtstr;
+	const char *reltype;
+	bool		istype = false;
+
+	Assert(cmd->type == SCT_AlterTable);
+
+	rel = relation_open(cmd->d.alterTable.objectId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									cmd->d.alterTable.objectId);
+
+	switch (rel->rd_rel->relkind)
+	{
+		case RELKIND_RELATION:
+			reltype = "TABLE";
+			break;
+		case RELKIND_INDEX:
+			reltype = "INDEX";
+			break;
+		case RELKIND_VIEW:
+			reltype = "VIEW";
+			break;
+		case RELKIND_COMPOSITE_TYPE:
+			reltype = "TYPE";
+			istype = true;
+			break;
+		case RELKIND_FOREIGN_TABLE:
+			reltype = "FOREIGN TABLE";
+			break;
+
+		default:
+			elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind);
+			reltype = NULL;;
+	}
+
+	fmtstr = psprintf("ALTER %s %%{identity}D %%{subcmds:, }s", reltype);
+	alterTableStmt = new_objtree_VA(fmtstr, 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, true);
+				fmtstr = psprintf("ADD %s %%{definition}s",
+								  istype ? "ATTRIBUTE" : "COLUMN");
+				tmp = new_objtree_VA(fmtstr, 2,
+									 "type", ObjTypeString, "add column",
+									 "definition", ObjTypeObject, tree);
+				subcmds = lappend(subcmds, new_object_object(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(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(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(tmp));
+				break;
+
+			case AT_SetStatistics:
+				{
+					Assert(IsA(subcmd->def, Integer));
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n",
+										 3, "type", ObjTypeString, "set statistics",
+										 "column", ObjTypeString, subcmd->name,
+										 "statistics", ObjTypeInteger,
+										 intVal((Value *) subcmd->def));
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_SetOptions:
+			case AT_ResetOptions:
+				subcmds = lappend(subcmds, new_object_object(
+									  deparse_ColumnSetOptions(subcmd)));
+				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(tmp));
+				break;
+
+			case AT_DropColumn:
+				/*
+				 * FIXME -- this command is also reported by sql_drop. Should
+				 * we remove it from here?
+				 */
+				fmtstr = psprintf("DROP %s %%{column}I %%{cascade}s",
+								  istype ? "ATTRIBUTE" : "COLUMN");
+				tmp = new_objtree_VA(fmtstr, 2,
+									 "type", ObjTypeString, "drop column",
+									 "column", ObjTypeString, subcmd->name);
+				tmp2 = new_objtree_VA("CASCADE", 1,
+									  "present", ObjTypeBool, subcmd->behavior);
+				append_object_object(tmp, "cascade", tmp2);
+
+				subcmds = lappend(subcmds, new_object_object(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(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(tmp));
+				}
+				break;
+
+			case AT_AlterConstraint:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER CONSTRAINT");
+				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(tmp));
+				break;
+
+			case AT_DropConstraint:
+				/*
+				 * FIXME -- this command is also reported by sql_drop. Should
+				 * we remove it from here?
+				 */
+				tmp = new_objtree_VA("DROP CONSTRAINT %{constraint}I", 2,
+									 "type", ObjTypeString, "drop constraint",
+									 "constraint", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AlterColumnType:
+				{
+					TupleDesc tupdesc = RelationGetDescr(rel);
+					Form_pg_attribute att = tupdesc->attrs[substashed->attnum - 1];
+					ColumnDef	   *def;
+
+					def = (ColumnDef *) subcmd->def;
+					Assert(IsA(def, ColumnDef));
+
+					fmtstr = psprintf("ALTER %s %%{column}I SET DATA TYPE %%{datatype}T %%{collation}s %s",
+									  istype ? "ATTRIBUTE" : "COLUMN",
+									  istype ? "%{cascade}s" : "%{using}s");
+
+					tmp = new_objtree_VA(fmtstr, 2,
+										 "type", ObjTypeString, "alter column type",
+										 "column", ObjTypeString, subcmd->name);
+					/* add the TYPE clause */
+					append_object_object(tmp, "datatype",
+										 new_objtree_for_type(att->atttypid,
+															  att->atttypmod));
+
+					/* add a COLLATE clause, if needed */
+					tmp2 = new_objtree_VA("COLLATE %{name}D", 0);
+					if (OidIsValid(att->attcollation))
+					{
+						ObjTree *collname;
+
+						collname = new_objtree_for_qualname_id(CollationRelationId,
+															   att->attcollation);
+						append_object_object(tmp2, "name", collname);
+					}
+					else
+						append_bool_object(tmp2, "present", false);
+					append_object_object(tmp, "collation", tmp2);
+
+					/* if not a composite type, add the USING clause */
+					if (!istype)
+					{
+						/*
+						 * Error out if the USING clause was used.  We cannot use
+						 * it directly here, because it needs to run through
+						 * transformExpr() before being usable for ruleutils.c, and
+						 * we're not in a position to transform it ourselves.  To
+						 * fix this problem, tablecmds.c needs to be modified to store
+						 * the transformed expression somewhere in the StashedATSubcmd.
+						 */
+						tmp2 = new_objtree_VA("USING %{expression}s", 0);
+						if (def->raw_default)
+							elog(ERROR, "unimplemented deparse of ALTER TABLE TYPE USING");
+						else
+							append_bool_object(tmp2, "present", false);
+						append_object_object(tmp, "using", tmp2);
+					}
+
+					/* if it's a composite type, add the CASCADE clause */
+					if (istype)
+					{
+						tmp2 = new_objtree_VA("CASCADE", 0);
+						if (subcmd->behavior != DROP_CASCADE)
+							append_bool_object(tmp2, "present", false);
+						append_object_object(tmp, "cascade", tmp2);
+					}
+
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS");
+				break;
+
+			case AT_ChangeOwner:
+				tmp = new_objtree_VA("OWNER TO %{owner}I",
+									 2, "type", ObjTypeString, "change owner",
+									 "owner",  ObjTypeString,
+									 get_rolespec_name(subcmd->newowner));
+				subcmds = lappend(subcmds, new_object_object(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(tmp));
+				break;
+
+			case AT_DropCluster:
+				tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+									 "type", ObjTypeString, "set without cluster");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetLogged:
+				tmp = new_objtree_VA("SET LOGGED", 1,
+									 "type", ObjTypeString, "set logged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetUnLogged:
+				tmp = new_objtree_VA("SET UNLOGGED", 1,
+									 "type", ObjTypeString, "set unlogged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOids:
+				tmp = new_objtree_VA("SET WITH OIDS", 1,
+									 "type", ObjTypeString, "set with oids");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOids:
+				tmp = new_objtree_VA("SET WITHOUT OIDS", 1,
+									 "type", ObjTypeString, "set without oids");
+				subcmds = lappend(subcmds, new_object_object(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(tmp));
+				break;
+
+			case AT_SetRelOptions:
+			case AT_ResetRelOptions:
+				subcmds = lappend(subcmds, new_object_object(
+									  deparse_RelSetOptions(subcmd)));
+				break;
+
+			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(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(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(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(tmp));
+				break;
+
+			case AT_EnableTrigAll:
+				tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "enable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigAll:
+				tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "disable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableTrigUser:
+				tmp = new_objtree_VA("ENABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "enable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigUser:
+				tmp = new_objtree_VA("DISABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "disable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableRule:
+				tmp = new_objtree_VA("ENABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableAlwaysRule:
+				tmp = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable always rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableReplicaRule:
+				tmp = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable replica rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRule:
+				tmp = new_objtree_VA("DISABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "disable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddInherit:
+				tmp = new_objtree_VA("ADD INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "add inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 substashed->oid));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropInherit:
+				tmp = new_objtree_VA("NO INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "drop inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 substashed->oid));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOf:
+				tmp = new_objtree_VA("OF %{type_of}T",
+									 2, "type", ObjTypeString, "add of",
+									 "type_of", ObjTypeObject,
+									 new_objtree_for_type(substashed->oid, -1));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOf:
+				tmp = new_objtree_VA("NOT OF",
+									 1, "type", ObjTypeString, "not of");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				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(tmp));
+				break;
+
+			case AT_EnableRowSecurity:
+				tmp = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "enable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRowSecurity:
+				tmp = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "disable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_GenericOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE OPTIONS (...)");
+				break;
+
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+	}
+
+	heap_close(rel, AccessShareLock);
+
+	if (list_length(subcmds) == 0)
+		return NULL;
+
+	append_array_object(alterTableStmt, "subcmds", subcmds);
+	return alterTableStmt;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -3681,6 +4295,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_Simple:
 			tree = deparse_simple_command(cmd);
 			break;
+		case SCT_AlterTable:
+			tree = deparse_AlterTableStmt(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 624754c..3bc2387 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1028,6 +1028,10 @@ ProcessUtilitySlow(Node *parsetree,
 						stmts = transformAlterTableStmt(relid, atstmt,
 														queryString);
 
+						/* ... ensure we have an event trigger context ... */
+						EventTriggerAlterTableStart(parsetree);
+						EventTriggerAlterTableRelid(relid);
+
 						/* ... and do it */
 						foreach(l, stmts)
 						{
@@ -1041,19 +1045,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.
+								 */
+								EventTriggerAlterTableEnd();
 								ProcessUtility(stmt,
 											   queryString,
 											   PROCESS_UTILITY_SUBCOMMAND,
 											   params,
 											   None_Receiver,
 											   NULL);
+								EventTriggerAlterTableStart(parsetree);
+								EventTriggerAlterTableRelid(relid);
 							}
 
 							/* Need CCI between commands */
 							if (lnext(l) != NULL)
 								CommandCounterIncrement();
 						}
+
+						/* done */
+						EventTriggerAlterTableEnd();
 					}
 					else
 						ereport(NOTICE,
@@ -1211,6 +1228,7 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
+					EventTriggerAlterTableStart(parsetree);
 					address =
 						DefineIndex(relid,	/* OID of heap relation */
 									stmt,
@@ -1226,6 +1244,7 @@ ProcessUtilitySlow(Node *parsetree,
 					 */
 					EventTriggerStashCommand(address, NULL, parsetree);
 					commandStashed = true;
+					EventTriggerAlterTableEnd();
 				}
 				break;
 
@@ -1300,10 +1319,12 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
+				EventTriggerAlterTableStart(parsetree);
 				address = DefineView((ViewStmt *) parsetree, queryString);
 				EventTriggerStashCommand(address, NULL, parsetree);
 				/* stashed internally */
 				commandStashed = true;
+				EventTriggerAlterTableEnd();
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 2ca88ee..d5f59ff 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -65,4 +65,10 @@ extern void EventTriggerStashCommand(ObjectAddress address,
 extern void EventTriggerInhibitCommandCollection(void);
 extern void EventTriggerUndoInhibitCommandCollection(void);
 
+extern void EventTriggerAlterTableStart(Node *parsetree);
+extern void EventTriggerAlterTableRelid(Oid objectId);
+extern void EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid,
+								  AttrNumber attnum, Oid newoid);
+extern void EventTriggerAlterTableEnd(void);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index bc6fa10..9cefe22 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -26,6 +26,7 @@
 typedef enum StashedCommandType
 {
 	SCT_Simple,
+	SCT_AlterTable
 } StashedCommandType;
 
 /*
@@ -43,6 +44,7 @@ typedef struct StashedCommand
 	StashedCommandType type;
 	bool		in_extension;
 	Node	   *parsetree;
+	slist_node	link;
 
 	union
 	{
@@ -51,6 +53,13 @@ typedef struct StashedCommand
 			ObjectAddress address;
 			ObjectAddress secondaryObject;
 		} simple;
+
+		struct AlterTableCommand
+		{
+			Oid		objectId;
+			Oid		classId;
+			List   *subcmds;
+		} alterTable;
 	} d;
 } StashedCommand;
 
-- 
2.1.4

0014-deparse-Support-CREATE-VIEW.patchtext/x-diff; charset=us-asciiDownload
>From 87fd33209271d23ada1cc9e9d0a42d5535ba191f Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 14:34:00 -0300
Subject: [PATCH 14/29] deparse: Support CREATE VIEW

---
 src/backend/tcop/deparse_utility.c | 38 +++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  7 +++++++
 src/include/utils/ruleutils.h      |  1 +
 3 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 96b16dc..a51c6dc 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -917,6 +917,42 @@ deparse_AlterDomainStmt(Oid objectId, Node *parsetree,
 }
 
 /*
+ * deparse_ViewStmt
+ *		deparse a ViewStmt
+ *
+ * Given a view OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_ViewStmt(Oid objectId, Node *parsetree)
+{
+	ViewStmt   *node = (ViewStmt *) parsetree;
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	Relation	relation;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s",
+							  2,
+							  "or_replace", ObjTypeString,
+							  node->replace ? "OR REPLACE" : "",
+							  "persistence", ObjTypeString,
+					  get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(viewStmt, "identity", tmp);
+
+	append_string_object(viewStmt, "query",
+						 pg_get_viewdef_internal(objectId));
+
+	relation_close(relation, AccessShareLock);
+
+	return viewStmt;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -4116,7 +4152,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_ViewStmt(objectId, parsetree);
 			break;
 
 		case T_CreateFunctionStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7fbd4f9..1a2f03c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -669,6 +669,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
  */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index ce6a1b1..7b22ea0 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -32,6 +32,7 @@ extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
 					   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
 
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
-- 
2.1.4

0015-deparse-Support-CREATE-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From 1ff8967404c6a70fd77438b95852a8d5660d1f0d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 14:34:29 -0300
Subject: [PATCH 15/29] deparse: Support CREATE OPERATOR FAMILY

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index a51c6dc..3a0616d 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3493,6 +3493,41 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateOpFamily(Oid objectId, Node *parsetree)
+{
+	HeapTuple   opfTup;
+	HeapTuple   amTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_am  amForm;
+	ObjTree	   *copfStmt;
+	ObjTree	   *tmp;
+
+	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}I",
+							  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));
+
+	ReleaseSysCache(amTup);
+	ReleaseSysCache(opfTup);
+
+	return copfStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -4209,7 +4244,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateOpFamilyStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateOpFamily(objectId, parsetree);
 			break;
 
 		case T_AlterOpFamilyStmt:
-- 
2.1.4

0016-deparse-Support-CREATE-CONVERSION.patchtext/x-diff; charset=us-asciiDownload
>From 40051ef71478cfb5fc8a86423df8d9cfcf364be7 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 16/29] deparse: Support CREATE CONVERSION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 3a0616d..e28eb89 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3493,6 +3493,41 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+	HeapTuple   conTup;
+	Relation	convrel;
+	Form_pg_conversion conForm;
+	ObjTree	   *ccStmt;
+
+	convrel = heap_open(ConversionRelationId, AccessShareLock);
+	conTup = get_catalog_object_by_oid(convrel, 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));
+
+	heap_close(convrel, AccessShareLock);
+
+	return ccStmt;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -4232,7 +4267,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateConversionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateConversion(objectId, parsetree);
 			break;
 
 		case T_CreateCastStmt:
-- 
2.1.4

0017-deparse-Support-DefineStmt-commands.patchtext/x-diff; charset=us-asciiDownload
>From 962352a5194c439d4a7ada887ce34908cfc49e72 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 17/29] deparse: Support DefineStmt commands

CREATE AGGREGATE
CREATE COLLATION
CREATE OPERATOR
CREATE TEXT SEARCH CONFIGURATION
CREATE TEXT SEARCH PARSER
CREATE TEXT SEARCH DICTIONARY
CREATE TEXT SEARCH TEMPLATE
CREATE TYPE
---
 src/backend/tcop/deparse_utility.c | 875 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 874 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index e28eb89..435c8e2 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -694,6 +694,878 @@ get_persistence_str(char persistence)
 }
 
 /*
+ * Form an ObjElem to be used as a single argument in an aggregate argument
+ * list
+ */
+static ObjElem *
+form_agg_argument(int idx, char *modes, char **names, Oid *types)
+{
+	ObjTree	   *arg;
+
+	arg = new_objtree_VA("%{mode}s %{name}s %{type}T", 0);
+
+	append_string_object(arg, "mode",
+						 (modes && modes[idx] == 'v') ? "VARIADIC" : "");
+	append_string_object(arg, "name", names ? names[idx] : "");
+	append_object_object(arg, "type",
+						 new_objtree_for_type(types[idx], -1));
+
+	return new_object_object(arg);
+}
+
+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)));
+
+	/*
+	 * Add the argument list.  There are three cases to consider:
+	 *
+	 * 1. no arguments, in which case the signature is (*).
+	 *
+	 * 2. Not an ordered-set aggregate.  In this case there's one or more
+	 * arguments.
+	 *
+	 * 3. Ordered-set aggregates. These have zero or more direct arguments, and
+	 * one or more ordered arguments.
+	 *
+	 * We don't need to consider default values or table parameters, and the
+	 * only mode that we need to consider is VARIADIC.
+	 */
+
+	if (proc->pronargs == 0)
+	{
+		append_string_object(stmt, "agg type", "noargs");
+		append_string_object(stmt, "types", "*");
+	}
+	else if (!AGGKIND_IS_ORDERED_SET(agg->aggkind))
+	{
+		int			i;
+		int			nargs;
+		Oid		   *types;
+		char	   *modes;
+		char	  **names;
+
+		nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+		/* only direct arguments in this case */
+		list = NIL;
+		for (i = 0; i < nargs; i++)
+		{
+			list = lappend(list, form_agg_argument(i, modes, names, types));
+		}
+
+		tmp = new_objtree_VA("%{direct:, }s", 1,
+							 "direct", ObjTypeArray, list);
+		append_object_object(stmt, "types", tmp);
+		append_string_object(stmt, "agg type", "plain");
+	}
+	else
+	{
+		int			i;
+		int			nargs;
+		Oid		   *types;
+		char	   *modes;
+		char	  **names;
+
+		nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+		tmp = new_objtree_VA("%{direct:, }s ORDER BY %{aggregated:, }s", 0);
+
+		/* direct arguments ... */
+		list = NIL;
+		for (i = 0; i < agg->aggnumdirectargs; i++)
+		{
+			list = lappend(list, form_agg_argument(i, modes, names, types));
+		}
+		append_array_object(tmp, "direct", list);
+
+		/*
+		 * ... and aggregated arguments.  If the last direct argument is
+		 * VARIADIC, we need to repeat it here rather than searching for more
+		 * arguments.  (See aggr_args production in gram.y for an explanation.)
+		 */
+		if (modes && modes[agg->aggnumdirectargs - 1] == 'v')
+		{
+			list = list_make1(form_agg_argument(agg->aggnumdirectargs - 1,
+												modes, names, types));
+		}
+		else
+		{
+			list = NIL;
+			for (i = agg->aggnumdirectargs; i < nargs; i++)
+			{
+				list = lappend(list, form_agg_argument(i, modes, names, types));
+			}
+		}
+		append_array_object(tmp, "aggregated", list);
+
+		append_object_object(stmt, "types", tmp);
+		append_string_object(stmt, "agg type", "ordered-set");
+	}
+
+	/*
+	 * Now add the definition clause
+	 */
+	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(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(tmp));
+
+	if (agg->aggtransspace != 0)
+	{
+		tmp = new_objtree_VA("SSPACE=%{space}n", 1,
+							 "space", ObjTypeInteger,
+							 agg->aggtransspace);
+		list = lappend(list, new_object_object(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(tmp));
+	}
+
+	if (agg->aggfinalextra)
+	{
+		tmp = new_objtree_VA("FINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(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(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(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(tmp));
+	}
+
+	if (agg->aggmtransspace != 0)
+	{
+		tmp = new_objtree_VA("MSSPACE=%{space}n", 1,
+							 "space", ObjTypeInteger,
+							 agg->aggmtransspace);
+		list = lappend(list, new_object_object(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(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(tmp));
+	}
+
+	if (agg->aggmfinalextra)
+	{
+		tmp = new_objtree_VA("MFINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(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(tmp));
+	}
+
+	if (agg->aggkind == AGGKIND_HYPOTHETICAL)
+	{
+		tmp = new_objtree_VA("HYPOTHETICAL=true", 0);
+		list = lappend(list, new_object_object(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}D", 0);
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname(op->oprnamespace,
+													  NameStr(op->oprname)));
+		list = lappend(list, new_object_object(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;
+	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}D "
+						  "(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;
+	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(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(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(tmp));
+	}
+
+	if (OidIsValid(oprForm->oprcom))
+	{
+		tmp = new_objtree_VA("COMMUTATOR=%{oper}D", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprcom));
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	if (OidIsValid(oprForm->oprnegate))
+	{
+		tmp = new_objtree_VA("NEGATOR=%{oper}D", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprnegate));
+		list = lappend(list, new_object_object(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(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(tmp));
+	}
+
+	if (oprForm->oprcanmerge)
+		list = lappend(list, new_object_object(new_objtree_VA("MERGES", 0)));
+	if (oprForm->oprcanhash)
+		list = lappend(list, new_object_object(new_objtree_VA("HASHES", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(oprTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define,
+							ObjectAddress copied)
+{
+	HeapTuple   tscTup;
+	HeapTuple   tspTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	Form_pg_ts_config tscForm;
+	Form_pg_ts_parser tspForm;
+	List	   *list;
+
+	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 "
+						  "(%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(tscForm->cfgnamespace,
+												  NameStr(tscForm->cfgname)));
+
+	/*
+	 * If tsconfig was copied from another one, define it like so.
+	 */
+	list = NIL;
+	if (copied.objectId != InvalidOid)
+	{
+		tmp = new_objtree_VA("COPY=%{tsconfig}D", 0);
+		append_object_object(tmp, "tsconfig",
+							 new_objtree_for_qualname_id(TSConfigRelationId,
+														 copied.objectId));
+	}
+	else
+	{
+		tmp = new_objtree_VA("PARSER=%{parser}D", 0);
+		append_object_object(tmp, "parser",
+							 new_objtree_for_qualname(tspForm->prsnamespace,
+													  NameStr(tspForm->prsname)));
+	}
+	list = lappend(list, new_object_object(tmp));
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+	ReleaseSysCache(tscTup);
+
+	return stmt;
+}
+
+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(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(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(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(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(tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+
+	return stmt;
+}
+
+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(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(tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tsdTup);
+
+	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(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(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tstTup);
+
+	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(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(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(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(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(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(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(tmp));
+	}
+
+	/* INTERNALLENGTH */
+	if (typForm->typlen == -1)
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=VARIABLE", 0);
+	}
+	else
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}n", 1,
+							 "typlen", ObjTypeInteger, typForm->typlen);
+	}
+	list = lappend(list, new_object_object(tmp));
+
+	/* PASSEDBYVALUE */
+	if (typForm->typbyval)
+		list = lappend(list, new_object_object(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(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(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(tmp));
+
+	/* PREFERRED */
+	if (typForm->typispreferred)
+		list = lappend(list, new_object_object(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(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(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(tmp));
+
+	/* COLLATABLE */
+	if (OidIsValid(typForm->typcollation))
+		list = lappend(list,
+					   new_object_object(new_objtree_VA("COLLATABLE=true", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(typTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt(Oid objectId, Node *parsetree, ObjectAddress secondaryObj)
+{
+	DefineStmt *define = (DefineStmt *) parsetree;
+	ObjTree	   *defStmt;
+
+	switch (define->kind)
+	{
+		case OBJECT_AGGREGATE:
+			defStmt = deparse_DefineStmt_Aggregate(objectId, define);
+			break;
+
+		case OBJECT_COLLATION:
+			defStmt = deparse_DefineStmt_Collation(objectId, define);
+			break;
+
+		case OBJECT_OPERATOR:
+			defStmt = deparse_DefineStmt_Operator(objectId, define);
+			break;
+
+		case OBJECT_TSCONFIGURATION:
+			defStmt = deparse_DefineStmt_TSConfig(objectId, define, secondaryObj);
+			break;
+
+		case OBJECT_TSPARSER:
+			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;
+
+		case OBJECT_TYPE:
+			defStmt = deparse_DefineStmt_Type(objectId, define);
+			break;
+
+		default:
+			elog(ERROR, "unsupported object kind");
+			return NULL;
+	}
+
+	return defStmt;
+}
+
+/*
  * deparse_CreateExtensionStmt
  *		deparse a CreateExtensionStmt
  *
@@ -4151,7 +5023,8 @@ deparse_simple_command(StashedCommand *cmd)
 
 			/* other local objects */
 		case T_DefineStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_DefineStmt(objectId, parsetree,
+										 cmd->d.simple.secondaryObject);
 			break;
 
 		case T_IndexStmt:
-- 
2.1.4

0018-deparse-support-CREATE-CAST.patchtext/x-diff; charset=us-asciiDownload
>From 939612289cde85057ff76fccca081fddf605a33a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 10 Feb 2015 14:09:51 -0300
Subject: [PATCH 18/29] deparse: support CREATE CAST

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 435c8e2..5f32ee6 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4400,6 +4400,81 @@ deparse_CreateConversion(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateCastStmt(Oid objectId, Node *parsetree)
+{
+	CreateCastStmt *node = (CreateCastStmt *) parsetree;
+	Relation	castrel;
+	HeapTuple	castTup;
+	Form_pg_cast castForm;
+	ObjTree	   *createCast;
+	char	   *context;
+
+	castrel = heap_open(CastRelationId, AccessShareLock);
+	castTup = get_catalog_object_by_oid(castrel, objectId);
+	if (!HeapTupleIsValid(castTup))
+		elog(ERROR, "cache lookup failed for cast with OID %u", objectId);
+	castForm = (Form_pg_cast) GETSTRUCT(castTup);
+
+	createCast = new_objtree_VA("CREATE CAST (%{sourcetype}T AS %{targettype}T) %{mechanism}s %{context}s",
+								2, "sourcetype", ObjTypeObject,
+								new_objtree_for_type(castForm->castsource, -1),
+								"targettype", ObjTypeObject,
+								new_objtree_for_type(castForm->casttarget, -1));
+
+	if (node->inout)
+		append_string_object(createCast, "mechanism", "WITH INOUT");
+	else if (node->func == NULL)
+		append_string_object(createCast, "mechanism", "WITHOUT FUNCTION");
+	else
+	{
+		ObjTree	   *tmp;
+		StringInfoData func;
+		HeapTuple	funcTup;
+		Form_pg_proc funcForm;
+		int			i;
+
+		funcTup = SearchSysCache1(PROCOID, castForm->castfunc);
+		funcForm = (Form_pg_proc) GETSTRUCT(funcTup);
+
+		initStringInfo(&func);
+		appendStringInfo(&func, "%s(",
+						quote_qualified_identifier(get_namespace_name(funcForm->pronamespace),
+												   NameStr(funcForm->proname)));
+		for (i = 0; i < funcForm->pronargs; i++)
+			appendStringInfoString(&func,
+								   format_type_be_qualified(funcForm->proargtypes.values[i]));
+		appendStringInfoChar(&func, ')');
+
+		tmp = new_objtree_VA("WITH FUNCTION %{castfunction}s", 1,
+							 "castfunction", ObjTypeString, func.data);
+		append_object_object(createCast, "mechanism", tmp);
+
+		ReleaseSysCache(funcTup);
+	}
+
+	switch (node->context)
+	{
+		case COERCION_IMPLICIT:
+			context = "AS IMPLICIT";
+			break;
+		case COERCION_ASSIGNMENT:
+			context = "AS ASSIGNMENT";
+			break;
+		case COERCION_EXPLICIT:
+			context = "";
+			break;
+		default:
+			elog(ERROR, "invalid coercion code %c", node->context);
+			return NULL;	/* keep compiler quiet */
+	}
+	append_string_object(createCast, "context", context);
+
+	heap_close(castrel, AccessShareLock);
+
+	return createCast;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -5144,7 +5219,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateCastStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateCastStmt(objectId, parsetree);
 			break;
 
 		case T_CreateOpClassStmt:
-- 
2.1.4

0019-deparse-Support-GRANT-REVOKE.patchtext/x-diff; charset=us-asciiDownload
>From 492e68daf2007e0822d5dd455ce397d28f49f09c 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 19/29] deparse: Support GRANT/REVOKE

---
 src/backend/catalog/aclchk.c         |  37 ++-----
 src/backend/commands/event_trigger.c | 106 +++++++++++++++++++
 src/backend/tcop/deparse_utility.c   | 193 +++++++++++++++++++++++++++++++++++
 src/include/commands/event_trigger.h |   3 +
 src/include/tcop/deparse_utility.h   |  11 +-
 src/include/utils/aclchk.h           |  45 ++++++++
 6 files changed, 368 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 8e75c27..ca9556f 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
@@ -605,6 +581,15 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	if (EventTriggerSupportsGrantObjectType(istmt->objtype))
+		EventTriggerStashGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 12bfad4..8c7f6e7 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1756,6 +1756,88 @@ EventTriggerAlterTableEnd(void)
 	currentEventTriggerState->curcmd = NULL;
 }
 
+static const char *
+stringify_grantobjtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+			return "COLUMN";
+		case ACL_OBJECT_RELATION:
+			return "TABLE";
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case ACL_OBJECT_DATABASE:
+			return "DATABASE";
+		case ACL_OBJECT_DOMAIN:
+			return "DOMAIN";
+		case ACL_OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case ACL_OBJECT_FOREIGN_SERVER:
+			return "FOREIGN SERVER";
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTION";
+		case ACL_OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case ACL_OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case ACL_OBJECT_NAMESPACE:
+			return "SCHEMA";
+		case ACL_OBJECT_TABLESPACE:
+			return "TABLESPACE";
+		case ACL_OBJECT_TYPE:
+			return "TYPE";
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
+
+/*
+ * 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;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	/*
+	 * copying the node is moderately challenging ... 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 = creating_extension;
+	stashed->d.grant.type = stringify_grantobjtype(istmt->objtype);
+	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)
 {
@@ -1915,6 +1997,30 @@ 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(cmd->d.grant.istmt->is_grant ?
+												  "GRANT" : "REVOKE");
+				/* object_type */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.type);
+				/* 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 5f32ee6..747e076 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4510,6 +4510,196 @@ deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_GrantStmt(StashedCommand *cmd)
+{
+	InternalGrant *istmt;
+	ObjTree	   *grantStmt;
+	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 = "FOREIGN 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(tmp));
+	}
+	else
+	{
+		list = NIL;
+
+		if (istmt->privileges & ACL_INSERT)
+			list = lappend(list, new_string_object("INSERT"));
+		if (istmt->privileges & ACL_SELECT)
+			list = lappend(list, new_string_object("SELECT"));
+		if (istmt->privileges & ACL_UPDATE)
+			list = lappend(list, new_string_object("UPDATE"));
+		if (istmt->privileges & ACL_DELETE)
+			list = lappend(list, new_string_object("DELETE"));
+		if (istmt->privileges & ACL_TRUNCATE)
+			list = lappend(list, new_string_object("TRUNCATE"));
+		if (istmt->privileges & ACL_REFERENCES)
+			list = lappend(list, new_string_object("REFERENCES"));
+		if (istmt->privileges & ACL_TRIGGER)
+			list = lappend(list, new_string_object("TRIGGER"));
+		if (istmt->privileges & ACL_EXECUTE)
+			list = lappend(list, new_string_object("EXECUTE"));
+		if (istmt->privileges & ACL_USAGE)
+			list = lappend(list, new_string_object("USAGE"));
+		if (istmt->privileges & ACL_CREATE)
+			list = lappend(list, new_string_object("CREATE"));
+		if (istmt->privileges & ACL_CREATE_TEMP)
+			list = lappend(list, new_string_object("TEMPORARY"));
+		if (istmt->privileges & ACL_CONNECT)
+			list = lappend(list, new_string_object("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(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(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(tmp));
+	}
+	append_array_object(grantStmt, "privtarget", list);
+
+	/* list of grantees */
+	list = NIL;
+	foreach(cell, istmt->grantees)
+	{
+		Oid		grantee = lfirst_oid(cell);
+
+		tmp = new_objtree_for_role(grantee);
+		list = lappend(list, new_object_object(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", "");
+	}
+
+	return grantStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -5352,6 +5542,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterTable:
 			tree = deparse_AlterTableStmt(cmd);
 			break;
+		case SCT_Grant:
+			tree = 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 d5f59ff..cabb9be 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
 {
@@ -71,4 +72,6 @@ extern void EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid,
 								  AttrNumber attnum, Oid newoid);
 extern void EventTriggerAlterTableEnd(void);
 
+extern void EventTriggerStashGrant(InternalGrant *istmt);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 9cefe22..bf8f5f6 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_Simple,
-	SCT_AlterTable
+	SCT_AlterTable,
+	SCT_Grant
 } StashedCommandType;
 
 /*
@@ -60,6 +63,12 @@ typedef struct StashedCommand
 			Oid		classId;
 			List   *subcmds;
 		} alterTable;
+
+		struct GrantCommand
+		{
+			InternalGrant *istmt;
+			const char *type;
+		} 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 */
-- 
2.1.4

0020-deparse-support-COMMENT-ON-and-SECURITY-LABEL.patchtext/x-diff; charset=us-asciiDownload
>From 3180e288160a2d2e3325f211ff086e0eda75fbf8 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:45:04 -0300
Subject: [PATCH 20/29] deparse: support COMMENT ON and SECURITY LABEL

---
 src/backend/tcop/deparse_utility.c | 155 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 153 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 747e076..479d46b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4364,6 +4364,157 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 	return alterEnum;
 }
 
+/*
+ * Append a NULL-or-quoted-literal clause.  Useful for COMMENT and SECURITY
+ * LABEL.
+ */
+static void
+append_literal_or_null(ObjTree *mainobj, char *elemname, char *value)
+{
+	ObjTree	*top;
+	ObjTree *part;
+
+	top = new_objtree_VA("%{null}s%{literal}s", 0);
+	part = new_objtree_VA("NULL", 1,
+						  "present", ObjTypeBool,
+						  !value);
+	append_object_object(top, "null", part);
+	part = new_objtree_VA("%{value}L", 1,
+						  "present", ObjTypeBool,
+						  !!value);
+	if (value)
+		append_string_object(part, "value", value);
+	append_object_object(top, "literal", part);
+
+	append_object_object(mainobj, elemname, top);
+}
+
+/*
+ * Deparse a CommentStmt when it pertains to a constraint.
+ */
+static ObjTree *
+deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	HeapTuple	constrTup;
+	Form_pg_constraint constrForm;
+	char	   *fmt;
+	ObjectAddress addr;
+
+	Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
+
+	constrTup = SearchSysCache1(CONSTROID, objectId);
+	if (!HeapTupleIsValid(constrTup))
+		elog(ERROR, "cache lookup failed for constraint %u", objectId);
+	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
+
+	if (OidIsValid(constrForm->conrelid))
+		ObjectAddressSet(addr, RelationRelationId, constrForm->conrelid);
+	else
+		ObjectAddressSet(addr, TypeRelationId, constrForm->contypid);
+
+	fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}s",
+				   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	comment = new_objtree_VA(fmt, 0);
+
+	/* Add the comment clause */
+	append_literal_or_null(comment, "comment", node->comment);
+
+	append_string_object(comment, "identity", pstrdup(NameStr(constrForm->conname)));
+
+	append_string_object(comment, "parentobj",
+						 getObjectIdentity(&addr));
+
+	ReleaseSysCache(constrTup);
+
+	return comment;
+}
+
+static ObjTree *
+deparse_CommentStmt(ObjectAddress address, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	char	   *fmt;
+	char	   *identity;
+
+	/*
+	 * Constraints are sufficiently different that it is easier to handle them
+	 * separately.
+	 */
+	if (node->objtype == OBJECT_DOMCONSTRAINT ||
+		node->objtype == OBJECT_TABCONSTRAINT)
+	{
+		Assert(address.classId == ConstraintRelationId);
+		return deparse_CommentOnConstraintSmt(address.objectId, parsetree);
+	}
+
+	fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}s",
+				   stringify_objtype(node->objtype));
+	comment = new_objtree_VA(fmt, 0);
+
+	/* Add the comment clause; can be either NULL or a quoted literal.  */
+	append_literal_or_null(comment, "comment", node->comment);
+
+	/*
+	 * Add the object identity clause.  For zero argument aggregates we need to
+	 * add the (*) bit; in all other cases we can just use getObjectIdentity.
+	 *
+	 * XXX shouldn't we instead fix the object identities for zero-argument
+	 * aggregates?
+	 */
+	if (node->objtype == OBJECT_AGGREGATE)
+	{
+		HeapTuple		procTup;
+		Form_pg_proc	procForm;
+
+		procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(address.objectId));
+		if (!HeapTupleIsValid(procTup))
+			elog(ERROR, "cache lookup failed for procedure %u", address.objectId);
+		procForm = (Form_pg_proc) GETSTRUCT(procTup);
+		if (procForm->pronargs == 0)
+			identity = psprintf("%s(*)",
+								quote_qualified_identifier(get_namespace_name(procForm->pronamespace),
+														   NameStr(procForm->proname)));
+		else
+			identity = getObjectIdentity(&address);
+		ReleaseSysCache(procTup);
+	}
+	else
+		identity = getObjectIdentity(&address);
+
+	append_string_object(comment, "identity", identity);
+
+	return comment;
+}
+
+static ObjTree *
+deparse_SecLabelStmt(ObjectAddress address, Node *parsetree)
+{
+	SecLabelStmt *node = (SecLabelStmt *) parsetree;
+	ObjTree	   *label;
+	char	   *fmt;
+
+	Assert(node->provider);
+
+	fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}s",
+				   stringify_objtype(node->objtype));
+	label = new_objtree_VA(fmt, 0);
+
+	/* Add the label clause; can be either NULL or a quoted literal. */
+	append_literal_or_null(label, "label", node->label);
+
+	/* Add the security provider clause */
+	append_string_object(label, "provider", node->provider);
+
+	/* Add the object identity clause */
+	append_string_object(label, "identity",
+						 getObjectIdentity(&address));
+
+	return label;
+}
+
 static ObjTree *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
@@ -5452,7 +5603,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CommentStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CommentStmt(cmd->d.simple.address, parsetree);
 			break;
 
 		case T_GrantStmt:
@@ -5478,7 +5629,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_SecLabelStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_SecLabelStmt(cmd->d.simple.address, parsetree);
 			break;
 
 		default:
-- 
2.1.4

0021-deparse-Handle-default-security-provider.patchtext/x-diff; charset=us-asciiDownload
>From 9a26529e10a51c758b828808467e349ca342e015 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Sat, 20 Dec 2014 08:56:54 +0100
Subject: [PATCH 21/29] deparse: Handle default security provider.

---
 src/backend/commands/seclabel.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 1ef98ce..aa4de97 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -63,6 +63,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("must specify provider when multiple security label providers have been loaded")));
 		provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
 	}
 	else
 	{
-- 
2.1.4

0022-deparse-support-CREATE-TABLE-AS.patchtext/x-diff; charset=us-asciiDownload
>From 18b51f12c21e4977e5cfadb54219d370b06170c4 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:30:04 -0300
Subject: [PATCH 22/29] deparse: support CREATE TABLE AS

---
 src/backend/tcop/deparse_utility.c | 132 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  15 +++++
 src/include/utils/ruleutils.h      |   1 +
 3 files changed, 146 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 479d46b..b27f5f4 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4291,6 +4291,135 @@ deparse_RuleStmt(Oid objectId, Node *parsetree)
 	return ruleStmt;
 }
 
+static ObjTree *
+deparse_CreateTableAsStmt(Oid objectId, Node *parsetree)
+{
+	CreateTableAsStmt *node = (CreateTableAsStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	ObjTree	   *createStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	char	   *fmt;
+	List	   *list;
+	ListCell   *cell;
+
+	/*
+	 * Reject unsupported case right away.
+	 */
+	if (((Query *) (node->query))->commandType == CMD_UTILITY)
+		elog(ERROR, "unimplemented deparse of CREATE TABLE AS EXECUTE");
+
+	/*
+	 * Note that INSERT INTO is deparsed as CREATE TABLE AS.  They are
+	 * functionally equivalent synonyms so there is no harm from this.
+	 */
+	if (node->relkind == OBJECT_MATVIEW)
+		fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with_clause}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+	else
+		fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with_clause}s %{on_commit}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+
+	createStmt =
+		new_objtree_VA(fmt, 2,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(node->into->rel->relpersistence),
+					   "if_not_exists", ObjTypeString,
+					   node->if_not_exists ? "IF NOT EXISTS" : "");
+	append_object_object(createStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+
+	/* Add an ON COMMIT clause.  CREATE MATERIALIZED VIEW doesn't have one */
+	if (node->relkind == OBJECT_TABLE)
+	{
+		append_object_object(createStmt, "on_commit",
+							 deparse_OnCommitClause(node->into->onCommit));
+	}
+
+	/* add a TABLESPACE clause */
+	tmp = new_objtree_VA("TABLESPACE %{tablespace}I", 0);
+	if (node->into->tableSpaceName)
+		append_string_object(tmp, "tablespace", node->into->tableSpaceName);
+	else
+	{
+		append_null_object(tmp, "tablespace");
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(createStmt, "tablespace", tmp);
+
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->into->skipData ? true : false);
+	append_object_object(createStmt, "with_no_data", tmp);
+
+	/* add the column list, if any */
+	if (node->into->colNames == NIL)
+		tmp = new_objtree_VA("", 1,
+							 "present", ObjTypeBool, false);
+	else
+	{
+		ListCell *cell;
+
+		list = NIL;
+		foreach (cell, node->into->colNames)
+			list = lappend(list, new_string_object(strVal(lfirst(cell))));
+
+		tmp = new_objtree_VA("(%{columns:, }I)", 1,
+							 "columns", ObjTypeArray, list);
+	}
+	append_object_object(createStmt, "columns", tmp);
+
+	/* add the query */
+	Assert(IsA(node->query, Query));
+	append_string_object(createStmt, "query",
+						 pg_get_createtableas_def((Query *) node->query));
+
+
+	/*
+	 * WITH clause.  In CREATE TABLE AS, like for CREATE TABLE, we always emit
+	 * one, containing at least the OIDS option; CREATE MATERIALIZED VIEW
+	 * doesn't support the oids option, so we have to make the clause reduce to
+	 * empty if no other options are specified.
+	 */
+	tmp = new_objtree_VA("WITH (%{with:, }s)", 0);
+	if (node->relkind == OBJECT_MATVIEW && node->into->options == NIL)
+		append_bool_object(tmp, "present", false);
+	else
+	{
+		if (node->relkind == OBJECT_TABLE)
+		{
+			tmp2 = new_objtree_VA("oids=%{value}s", 2,
+								  "option", ObjTypeString, "oids",
+								  "value", ObjTypeString,
+								  relation->rd_rel->relhasoids ? "ON" : "OFF");
+			list = list_make1(new_object_object(tmp2));
+		}
+		else
+			list = NIL;
+
+		foreach (cell, node->into->options)
+		{
+			DefElem	*opt = (DefElem *) lfirst(cell);
+
+			if (strcmp(opt->defname, "oids") == 0)
+				continue;
+
+			tmp2 = deparse_DefElem(opt, false);
+			list = lappend(list, new_object_object(tmp2));
+		}
+		append_array_object(tmp, "with", list);
+	}
+	append_object_object(createStmt, "with_clause", tmp);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -5535,8 +5664,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateTableAsStmt:
-			/* XXX handle at least the CREATE MATVIEW case? */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateTableAsStmt(objectId, parsetree);
 			break;
 
 		case T_RefreshMatViewStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1a2f03c..ce459b5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -750,6 +750,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def	- Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * get_triggerdef			- Get the definition of a trigger
  * ----------
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 7b22ea0..2c020e9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -33,6 +33,7 @@ extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
 					   char **whereClause, List **actions);
 extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
 
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
-- 
2.1.4

0023-deparse-support-CREATE-ALTER-POLICY.patchtext/x-diff; charset=us-asciiDownload
>From c9ea55c35e48f64be9ad7cd11f2142839291577d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:30:49 -0300
Subject: [PATCH 23/29] deparse: support CREATE/ALTER POLICY

---
 src/backend/tcop/deparse_utility.c | 132 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 130 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index b27f5f4..0071853 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4618,6 +4618,134 @@ deparse_CommentStmt(ObjectAddress address, Node *parsetree)
 	return comment;
 }
 
+/*
+ * Add common clauses to CreatePolicy or AlterPolicy deparse objects
+ */
+static void
+add_policy_clauses(ObjTree *policyStmt, Oid policyOid, List *roles,
+				   bool do_qual, bool do_with_check)
+{
+	Relation	polRel = heap_open(PolicyRelationId, AccessShareLock);
+	HeapTuple	polTup = get_catalog_object_by_oid(polRel, policyOid);
+	ObjTree	   *tmp;
+	Form_pg_policy polForm;
+
+	if (!HeapTupleIsValid(polTup))
+		elog(ERROR, "cache lookup failed for policy %u", policyOid);
+
+	polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+	/* add the "ON table" clause */
+	append_object_object(policyStmt, "table",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 polForm->polrelid));
+
+	/*
+	 * Add the "TO role" clause, if any.  In the CREATE case, it always
+	 * contains at least PUBLIC, but in the ALTER case it might be empty.
+	 */
+	tmp = new_objtree_VA("TO %{role:, }I", 0);
+	if (roles)
+	{
+		List   *list = NIL;
+		ListCell *cell;
+
+		foreach (cell, roles)
+		{
+			RoleSpec   *spec = (RoleSpec *) lfirst(cell);
+			char	   *role = spec->roletype == ROLESPEC_PUBLIC ? "PUBLIC" :
+				get_rolespec_name((Node *) spec);
+
+			list = lappend(list, new_string_object(role));
+		}
+		append_array_object(tmp, "role", list);
+	}
+	else
+	{
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(policyStmt, "to_role", tmp);
+
+	/* add the USING clause, if any */
+	tmp = new_objtree_VA("USING (%{expression}s)", 0);
+	if (do_qual)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polqual expression in policy %u", policyOid);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policyStmt, "using", tmp);
+
+	/* add the WITH CHECK clause, if any */
+	tmp = new_objtree_VA("WITH CHECK (%{expression}s)", 0);
+	if (do_with_check)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polwithcheck expression in policy %u", policyOid);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policyStmt, "with_check", tmp);
+
+	relation_close(polRel, AccessShareLock);
+}
+
+static ObjTree *
+deparse_CreatePolicyStmt(Oid objectId, Node *parsetree)
+{
+	CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree;
+	ObjTree	   *policy;
+
+	policy = new_objtree_VA("CREATE POLICY %{identity}I ON %{table}D %{for_command}s "
+							"%{to_role}s %{using}s %{with_check}s", 2,
+							"identity", ObjTypeString, node->policy_name,
+							"for_command", ObjTypeObject,
+							new_objtree_VA("FOR %{command}s", 1,
+										   "command", ObjTypeString, node->cmd));
+
+	/* add the rest of the stuff */
+	add_policy_clauses(policy, objectId, node->roles, !!node->qual,
+					   !!node->with_check);
+
+	return policy;
+}
+
+static ObjTree *
+deparse_AlterPolicyStmt(Oid objectId, Node *parsetree)
+{
+	AlterPolicyStmt *node = (AlterPolicyStmt *) parsetree;
+	ObjTree	   *policy;
+
+	policy = new_objtree_VA("ALTER POLICY %{identity}I ON %{table}D "
+							"%{to_role}s %{using}s %{with_check}s", 1,
+							"identity", ObjTypeString, node->policy_name);
+
+	/* add the rest of the stuff */
+	add_policy_clauses(policy, objectId, node->roles, !!node->qual,
+					   !!node->with_check);
+
+	return policy;
+}
+
 static ObjTree *
 deparse_SecLabelStmt(ObjectAddress address, Node *parsetree)
 {
@@ -5749,11 +5877,11 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreatePolicyStmt:	/* CREATE POLICY */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreatePolicyStmt(objectId, parsetree);
 			break;
 
 		case T_AlterPolicyStmt:		/* ALTER POLICY */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterPolicyStmt(objectId, parsetree);
 			break;
 
 		case T_SecLabelStmt:
-- 
2.1.4

0024-deparse-support-REFRESH-MATERIALIZED-VIEW.patchtext/x-diff; charset=us-asciiDownload
>From 817c200b3437c0e7c6bddcad6249a6f3d3ca680f Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:38:28 -0300
Subject: [PATCH 24/29] deparse: support REFRESH MATERIALIZED VIEW

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 0071853..f3f343f 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2022,6 +2022,31 @@ deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
 	return trigger;
 }
 
+static ObjTree *
+deparse_RefreshMatViewStmt(Oid objectId, Node *parsetree)
+{
+	RefreshMatViewStmt *node = (RefreshMatViewStmt *) parsetree;
+	ObjTree	   *refreshStmt;
+	ObjTree	   *tmp;
+
+	refreshStmt = new_objtree_VA("REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D "
+								 "%{with_no_data}s", 0);
+	/* add a CONCURRENTLY clause */
+	append_string_object(refreshStmt, "concurrently",
+						 node->concurrent ? "CONCURRENTLY" : "");
+	/* add the matview name */
+	append_object_object(refreshStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->skipData ? true : false);
+	append_object_object(refreshStmt, "with_no_data", tmp);
+
+	return refreshStmt;
+}
+
 /*
  * deparse_ColumnDef
  *		Subroutine for CREATE TABLE deparsing
@@ -5796,7 +5821,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RefreshMatViewStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RefreshMatViewStmt(objectId, parsetree);
 			break;
 
 		case T_CreateTrigStmt:
-- 
2.1.4

0025-deparse-support-FDW-related-commands.patchtext/x-diff; charset=us-asciiDownload
>From 1845d6b620aa2209f5beaa4220103ca1c95dbc9b Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:37:57 -0300
Subject: [PATCH 25/29] deparse: support FDW-related commands

CREATE SERVER
ALTER TABLE ... OPTIONS
ALTER COLUMN OPTIONS
CREATE FOREIGN TABLE
ALTER FOREIGN TABLE
CREATE USER MAPPING
ALTER USER MAPPING

Mostly from Andres Freund
---
 src/backend/tcop/deparse_utility.c | 405 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 396 insertions(+), 9 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index f3f343f..c2da9d3 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1789,6 +1789,347 @@ deparse_AlterDomainStmt(Oid objectId, Node *parsetree,
 }
 
 /*
+ * If a column name is specified, add an element for it; otherwise it's a
+ * table-level option.
+ */
+static ObjTree *
+deparse_FdwOptions(List *options, char *colname)
+{
+	ObjTree	   *tmp;
+
+	if (colname)
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I OPTIONS (%{option:, }s)",
+							 1, "column", ObjTypeString, colname);
+	else
+		tmp = new_objtree_VA("OPTIONS (%{option:, }s)", 0);
+
+	if (options != NIL)
+	{
+		List	   *optout = NIL;
+		ListCell   *cell;
+
+		foreach(cell, options)
+		{
+			DefElem	   *elem;
+			ObjTree	   *opt;
+
+			elem = (DefElem *) lfirst(cell);
+
+			switch (elem->defaction)
+			{
+				case DEFELEM_UNSPEC:
+					opt = new_objtree_VA("%{label}I %{value}L", 0);
+					break;
+				case DEFELEM_SET:
+					opt = new_objtree_VA("SET %{label}I %{value}L", 0);
+					break;
+				case DEFELEM_ADD:
+					opt = new_objtree_VA("ADD %{label}I %{value}L", 0);
+					break;
+				case DEFELEM_DROP:
+					opt = new_objtree_VA("DROP %{label}I", 0);
+					break;
+				default:
+					elog(ERROR, "invalid def action %d", elem->defaction);
+					opt = NULL;
+			}
+
+			append_string_object(opt, "label", elem->defname);
+			append_string_object(opt, "value",
+								 elem->arg ? defGetString(elem) :
+								 defGetBoolean(elem) ? "TRUE" : "FALSE");
+
+			optout = lappend(optout, new_object_object(opt));
+		}
+
+		append_array_object(tmp, "option", optout);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+
+	return tmp;
+}
+
+static ObjTree *
+deparse_CreateFdwStmt(Oid objectId, Node *parsetree)
+{
+	CreateFdwStmt *node = (CreateFdwStmt *) parsetree;
+	HeapTuple		fdwTup;
+	Form_pg_foreign_data_wrapper fdwForm;
+	Relation	rel;
+
+	ObjTree	   *createStmt;
+	ObjTree	   *tmp;
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+							 ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(fdwTup))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", objectId);
+
+	fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup);
+
+	createStmt = new_objtree_VA("CREATE FOREIGN DATA WRAPPER %{identity}I"
+								" %{handler}s %{validator}s %{generic_options}s", 1,
+								"identity", ObjTypeString, NameStr(fdwForm->fdwname));
+
+	/* add HANDLER clause */
+	if (fdwForm->fdwhandler == InvalidOid)
+		tmp = new_objtree_VA("NO HANDLER", 0);
+	else
+	{
+		tmp = new_objtree_VA("HANDLER %{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 fdwForm->fdwhandler));
+	}
+	append_object_object(createStmt, "handler", tmp);
+
+	/* add VALIDATOR clause */
+	if (fdwForm->fdwvalidator == InvalidOid)
+		tmp = new_objtree_VA("NO VALIDATOR", 0);
+	else
+	{
+		tmp = new_objtree_VA("VALIDATOR %{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 fdwForm->fdwvalidator));
+	}
+	append_object_object(createStmt, "validator", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(fdwTup);
+	heap_close(rel, RowExclusiveLock);
+
+	return createStmt;
+}
+
+static ObjTree *
+deparse_AlterFdwStmt(Oid objectId, Node *parsetree)
+{
+	AlterFdwStmt *node = (AlterFdwStmt *) parsetree;
+	HeapTuple		fdwTup;
+	Form_pg_foreign_data_wrapper fdwForm;
+	Relation	rel;
+	ObjTree	   *alterStmt;
+	List	   *fdw_options = NIL;
+	ListCell   *cell;
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+							 ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(fdwTup))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", objectId);
+
+	fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup);
+
+	alterStmt = new_objtree_VA("ALTER FOREIGN DATA WRAPPER %{identity}I"
+							   " %{fdw_options: }s %{generic_options}s", 1,
+							   "identity", ObjTypeString, NameStr(fdwForm->fdwname));
+
+	/*
+	 * Iterate through options, to see what changed, but use catalog as basis
+	 * for new values.
+	 */
+	foreach(cell, node->func_options)
+	{
+		DefElem	   *elem;
+		ObjTree	   *tmp;
+
+		elem = lfirst(cell);
+
+		if (pg_strcasecmp(elem->defname, "handler") == 0)
+		{
+			/* add HANDLER clause */
+			if (fdwForm->fdwhandler == InvalidOid)
+				tmp = new_objtree_VA("NO HANDLER", 0);
+			else
+			{
+				tmp = new_objtree_VA("HANDLER %{procedure}D", 0);
+				append_object_object(tmp, "procedure",
+									 new_objtree_for_qualname_id(ProcedureRelationId,
+																 fdwForm->fdwhandler));
+			}
+			fdw_options = lappend(fdw_options, new_object_object(tmp));
+		}
+		else if (pg_strcasecmp(elem->defname, "validator") == 0)
+		{
+			/* add VALIDATOR clause */
+			if (fdwForm->fdwvalidator == InvalidOid)
+				tmp = new_objtree_VA("NO VALIDATOR", 0);
+			else
+			{
+				tmp = new_objtree_VA("VALIDATOR %{procedure}D", 0);
+				append_object_object(tmp, "procedure",
+									 new_objtree_for_qualname_id(ProcedureRelationId,
+																 fdwForm->fdwvalidator));
+			}
+			fdw_options = lappend(fdw_options, new_object_object(tmp));
+		}
+	}
+
+	/* Add HANDLER/VALIDATOR if specified */
+	append_array_object(alterStmt, "fdw_options", fdw_options);
+
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(fdwTup);
+	heap_close(rel, RowExclusiveLock);
+
+	return alterStmt;
+}
+
+static ObjTree *
+deparse_CreateForeignServerStmt(Oid objectId, Node *parsetree)
+{
+	CreateForeignServerStmt *node = (CreateForeignServerStmt *) parsetree;
+	ObjTree	   *createServer;
+	ObjTree	   *tmp;
+
+	createServer = new_objtree_VA("CREATE SERVER %{identity}I %{type}s %{version}s "
+								  "FOREIGN DATA WRAPPER %{fdw}I %{generic_options}s", 2,
+								  "identity", ObjTypeString, node->servername,
+								  "fdw",  ObjTypeString, node->fdwname);
+
+	/* add a TYPE clause, if any */
+	tmp = new_objtree_VA("TYPE %{type}L", 0);
+	if (node->servertype)
+		append_string_object(tmp, "type", node->servertype);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "type", tmp);
+
+	/* add a VERSION clause, if any */
+	tmp = new_objtree_VA("VERSION %{version}L", 0);
+	if (node->version)
+		append_string_object(tmp, "version", node->version);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "version", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createServer, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	return createServer;
+}
+
+static ObjTree *
+deparse_AlterForeignServerStmt(Oid objectId, Node *parsetree)
+{
+	AlterForeignServerStmt *node = (AlterForeignServerStmt *) parsetree;
+	ObjTree	   *alterServer;
+	ObjTree	   *tmp;
+
+	alterServer = new_objtree_VA("ALTER SERVER %{identity}I %{version}s "
+								  "%{generic_options}s", 1,
+								  "identity", ObjTypeString, node->servername);
+
+	/* add a VERSION clause, if any */
+	tmp = new_objtree_VA("VERSION %{version}s", 0);
+	if (node->has_version && node->version)
+		append_string_object(tmp, "version", quote_literal_cstr(node->version));
+	else if (node->has_version)
+		append_string_object(tmp, "version", "NULL");
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterServer, "version", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterServer, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	return alterServer;
+}
+
+static ObjTree *
+deparse_CreateUserMappingStmt(Oid objectId, Node *parsetree)
+{
+	CreateUserMappingStmt *node = (CreateUserMappingStmt *) parsetree;
+	ObjTree	   *createStmt;
+	Relation	rel;
+	HeapTuple	tp;
+	Form_pg_user_mapping form;
+	ForeignServer *server;
+
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	/*
+	 * Lookup up object in the catalog, so we don't have to deal with
+	 * current_user and such.
+	 */
+	tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for user mapping %u", objectId);
+
+	form = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+	server = GetForeignServer(form->umserver);
+
+	createStmt = new_objtree_VA("CREATE USER MAPPING FOR %{role}s SERVER %{server}I "
+								"%{generic_options}s", 1,
+								"server", ObjTypeString, server->servername);
+
+	append_object_object(createStmt, "role", new_objtree_for_role(form->umuser));
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(tp);
+	heap_close(rel, RowExclusiveLock);
+	return createStmt;
+}
+
+static ObjTree *
+deparse_AlterUserMappingStmt(Oid objectId, Node *parsetree)
+{
+	AlterUserMappingStmt *node = (AlterUserMappingStmt *) parsetree;
+	ObjTree	   *alterStmt;
+	Relation	rel;
+	HeapTuple	tp;
+	Form_pg_user_mapping form;
+	ForeignServer *server;
+
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	/*
+	 * Lookup up object in the catalog, so we don't have to deal with
+	 * current_user and such.
+	 */
+
+	tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for user mapping %u", objectId);
+
+	form = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+	server = GetForeignServer(form->umserver);
+
+	alterStmt = new_objtree_VA("ALTER USER MAPPING FOR %{role}s SERVER %{server}I "
+								"%{generic_options}s", 1,
+								"server", ObjTypeString, server->servername);
+
+	append_object_object(alterStmt, "role", new_objtree_for_role(form->umuser));
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(tp);
+	heap_close(rel, RowExclusiveLock);
+	return alterStmt;
+}
+
+/*
  * deparse_ViewStmt
  *		deparse a ViewStmt
  *
@@ -2705,6 +3046,49 @@ deparse_CreateStmt(Oid objectId, Node *parsetree)
 	return createStmt;
 }
 
+static ObjTree *
+deparse_CreateForeignTableStmt(Oid objectId, Node *parsetree)
+{
+	CreateForeignTableStmt *stmt = (CreateForeignTableStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *tableelts = NIL;
+
+	createStmt = new_objtree_VA(
+			"CREATE FOREIGN TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) SERVER %{server}I "
+			"%{generic_options}s", 0);
+
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(createStmt, "identity", tmp);
+
+	append_string_object(createStmt, "if_not_exists",
+						 stmt->base.if_not_exists ? "IF NOT EXISTS" : "");
+
+	append_string_object(createStmt, "server", stmt->servername);
+
+	dpcontext = deparse_context_for(RelationGetRelationName(relation),
+									objectId);
+
+	tableelts = deparseTableElements(relation, stmt->base.tableElts, dpcontext,
+									 false,		/* not typed table */
+									 false);	/* not composite */
+	tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+	append_array_object(createStmt, "table_elements", tableelts);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(stmt->options, NULL));
+
+	relation_close(relation, AccessShareLock);
+	return createStmt;
+}
+
+
 /*
  * deparse_CompositeTypeStmt
  *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
@@ -5446,7 +5830,9 @@ deparse_AlterTableStmt(StashedCommand *cmd)
 				break;
 
 			case AT_AlterColumnGenericOptions:
-				elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS");
+				tmp = deparse_FdwOptions((List *) subcmd->def,
+										 subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
 				break;
 
 			case AT_ChangeOwner:
@@ -5656,7 +6042,8 @@ deparse_AlterTableStmt(StashedCommand *cmd)
 				break;
 
 			case AT_GenericOptions:
-				elog(ERROR, "unimplemented deparse of ALTER TABLE OPTIONS (...)");
+				tmp = deparse_FdwOptions((List *) subcmd->def, NULL);
+				subcmds = lappend(subcmds, new_object_object(tmp));
 				break;
 
 			default:
@@ -5705,7 +6092,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateForeignTableStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateForeignTableStmt(objectId, parsetree);
 			break;
 
 		case T_AlterTableStmt:
@@ -5743,27 +6130,27 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateFdwStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateFdwStmt(objectId, parsetree);
 			break;
 
 		case T_AlterFdwStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterFdwStmt(objectId, parsetree);
 			break;
 
 		case T_CreateForeignServerStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateForeignServerStmt(objectId, parsetree);
 			break;
 
 		case T_AlterForeignServerStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterForeignServerStmt(objectId, parsetree);
 			break;
 
 		case T_CreateUserMappingStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateUserMappingStmt(objectId, parsetree);
 			break;
 
 		case T_AlterUserMappingStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterUserMappingStmt(objectId, parsetree);
 			break;
 
 		case T_DropUserMappingStmt:
-- 
2.1.4

0026-infrastructure-for-ALTER-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From 6665f6f958a459ceb1cb6a04c7f1a99a793b2f00 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 13 Mar 2015 14:17:58 -0300
Subject: [PATCH 26/29] infrastructure for ALTER OPERATOR FAMILY

---
 src/backend/commands/event_trigger.c | 39 +++++++++++++++++++++++++++++++++++-
 src/backend/commands/opclasscmds.c   | 15 +-------------
 src/backend/tcop/deparse_utility.c   |  6 +++++-
 src/include/catalog/opfam_internal.h | 28 ++++++++++++++++++++++++++
 src/include/commands/event_trigger.h |  2 ++
 src/include/tcop/deparse_utility.h   | 10 ++++++++-
 6 files changed, 83 insertions(+), 17 deletions(-)
 create mode 100644 src/include/catalog/opfam_internal.h

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 8c7f6e7..c19a6a5 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -20,6 +20,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -1838,6 +1839,37 @@ EventTriggerStashGrant(InternalGrant *istmt)
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * EventTriggerStashAlterOpFam
+ * 		Save data about an ALTER OPERATOR FAMILY ADD/DROP command being
+ * 		executed
+ */
+void
+EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
+							List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+	stashed->type = SCT_AlterOpFamily;
+	stashed->in_extension = creating_extension;
+	stashed->d.opfam.opfamOid = opfamoid;
+	stashed->d.opfam.operators = operators;		/* XXX prolly need to copy */
+	stashed->d.opfam.procedures = procedures;	/* XXX ditto */
+	stashed->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1918,7 +1950,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 			MemSet(nulls, 0, sizeof(nulls));
 
 			if (cmd->type == SCT_Simple ||
-				cmd->type == SCT_AlterTable)
+				cmd->type == SCT_AlterTable ||
+				cmd->type == SCT_AlterOpFamily)
 			{
 				const char *tag;
 				char	   *identity;
@@ -1931,6 +1964,10 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 					ObjectAddressSet(addr,
 									 cmd->d.alterTable.classId,
 									 cmd->d.alterTable.objectId);
+				else if (cmd->type == SCT_AlterOpFamily)
+					ObjectAddressSet(addr,
+									 OperatorFamilyRelationId,
+									 cmd->d.opfam.opfamOid);
 
 				tag = CreateCommandTag(cmd->parsetree);
 
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index c327cc0..d5ca502 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -25,6 +25,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
+#include "catalog/opfam_internal.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_namespace.h"
@@ -47,20 +48,6 @@
 #include "utils/tqual.h"
 
 
-/*
- * We use lists of this struct type to keep track of both operators and
- * procedures while building or adding to an opfamily.
- */
-typedef struct
-{
-	Oid			object;			/* operator or support proc's OID */
-	int			number;			/* strategy or support proc number */
-	Oid			lefttype;		/* lefttype */
-	Oid			righttype;		/* righttype */
-	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
-} OpFamilyMember;
-
-
 static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items);
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index c2da9d3..fb46f1a 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -6240,7 +6240,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterOpFamilyStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
 			break;
 
 		case T_AlterTSDictionaryStmt:
@@ -6364,6 +6365,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_Grant:
 			tree = deparse_GrantStmt(cmd);
 			break;
+		case SCT_AlterOpFamily:
+			tree = deparse_AlterOpFamily(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/include/catalog/opfam_internal.h b/src/include/catalog/opfam_internal.h
new file mode 100644
index 0000000..f01dcbe
--- /dev/null
+++ b/src/include/catalog/opfam_internal.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * opfam_internal.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/opfam_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OPFAM_INTERNAL_H
+#define OPFAM_INTERNAL_H
+
+/*
+ * We use lists of this struct type to keep track of both operators and
+ * procedures while building or adding to an opfamily.
+ */
+typedef struct
+{
+	Oid			object;			/* operator or support proc's OID */
+	int			number;			/* strategy or support proc number */
+	Oid			lefttype;		/* lefttype */
+	Oid			righttype;		/* righttype */
+	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
+} OpFamilyMember;
+
+#endif		/* OPFAM_INTERNAL_H */
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index cabb9be..fd34001 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -73,5 +73,7 @@ extern void EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid,
 extern void EventTriggerAlterTableEnd(void);
 
 extern void EventTriggerStashGrant(InternalGrant *istmt);
+extern void EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
+							List *operators, List *procedures);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index bf8f5f6..b663ba2 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -29,7 +29,8 @@ typedef enum StashedCommandType
 {
 	SCT_Simple,
 	SCT_AlterTable,
-	SCT_Grant
+	SCT_Grant,
+	SCT_AlterOpFamily
 } StashedCommandType;
 
 /*
@@ -69,6 +70,13 @@ typedef struct StashedCommand
 			InternalGrant *istmt;
 			const char *type;
 		} grant;
+
+		struct AlterOpFamily
+		{
+			Oid		opfamOid;
+			List   *operators;
+			List   *procedures;
+		} opfam;
 	} d;
 } StashedCommand;
 
-- 
2.1.4

0027-deparse-Support-ALTER-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From 99b71c32e75569f9ff2788b742f33b871312b5e4 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:43:29 -0300
Subject: [PATCH 27/29] deparse: Support ALTER OPERATOR FAMILY

---
 src/backend/commands/opclasscmds.c |  25 +++++--
 src/backend/tcop/deparse_utility.c | 141 +++++++++++++++++++++++++++++++++++--
 2 files changed, 156 insertions(+), 10 deletions(-)

diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index d5ca502..68624c3 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
@@ -48,10 +49,12 @@
 #include "utils/tqual.h"
 
 
-static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+				 List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items);
-static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+				  List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				  int maxOpNumber, int maxProcNumber,
 				  List *items);
 static void processTypesSpec(List *args, Oid *lefttype, Oid *righttype);
@@ -809,11 +812,11 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
 	 * ADD and DROP cases need separate code from here on down.
 	 */
 	if (stmt->isDrop)
-		AlterOpFamilyDrop(stmt->opfamilyname, amoid, opfamilyoid,
+		AlterOpFamilyDrop(stmt, stmt->opfamilyname, amoid, opfamilyoid,
 						  maxOpNumber, maxProcNumber,
 						  stmt->items);
 	else
-		AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid,
+		AlterOpFamilyAdd(stmt, stmt->opfamilyname, amoid, opfamilyoid,
 						 maxOpNumber, maxProcNumber,
 						 stmt->items);
 
@@ -824,7 +827,8 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
  * ADD part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+				 List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items)
 {
@@ -949,13 +953,19 @@ AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				   InvalidOid, operators, true);
 	storeProcedures(opfamilyname, amoid, opfamilyoid,
 					InvalidOid, procedures, true);
+
+	/*
+	 * make information available to event triggers */
+	EventTriggerStashAlterOpFam(stmt, opfamilyoid,
+								operators, procedures);
 }
 
 /*
  * DROP part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+				  List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				  int maxOpNumber, int maxProcNumber,
 				  List *items)
 {
@@ -1022,6 +1032,9 @@ AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 	 */
 	dropOperators(opfamilyname, amoid, opfamilyoid, operators);
 	dropProcedures(opfamilyname, amoid, opfamilyoid, procedures);
+
+	EventTriggerStashAlterOpFam(stmt, opfamilyoid,
+								operators, procedures);
 }
 
 
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index fb46f1a..4a00479 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -31,6 +31,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/opfam_internal.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
@@ -138,9 +139,7 @@ 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);
-#ifdef NOT_USED
 static void append_integer_object(ObjTree *tree, char *name, int64 value);
-#endif
 static void append_float_object(ObjTree *tree, char *name, float8 value);
 static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
 static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
@@ -344,7 +343,6 @@ new_float_object(float8 value)
 	return param;
 }
 
-#ifdef NOT_USED
 /*
  * Append an int64 parameter to a tree.
  */
@@ -357,7 +355,6 @@ append_integer_object(ObjTree *tree, char *name, int64 value)
 	param->name = name;
 	append_premade_object(tree, param);
 }
-#endif
 
 /*
  * Append a float8 parameter to a tree.
@@ -5516,6 +5513,142 @@ deparse_GrantStmt(StashedCommand *cmd)
 	return grantStmt;
 }
 
+/*
+ * Deparse an ALTER OPERATOR FAMILY ADD/DROP command.
+ */
+static ObjTree *
+deparse_AlterOpFamily(StashedCommand *cmd)
+{
+	ObjTree	   *alterOpFam;
+	AlterOpFamilyStmt *stmt = (AlterOpFamilyStmt *) cmd->parsetree;
+	HeapTuple	ftp;
+	Form_pg_opfamily opfForm;
+	List	   *list;
+	ListCell   *cell;
+
+	ftp = SearchSysCache1(OPFAMILYOID,
+						  ObjectIdGetDatum(cmd->d.opfam.opfamOid));
+	if (!HeapTupleIsValid(ftp))
+		elog(ERROR, "cache lookup failed for operator family %u", cmd->d.opfam.opfamOid);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(ftp);
+
+	if (!stmt->isDrop)
+		alterOpFam = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D "
+									"USING %{amname}I ADD %{items:, }s", 0);
+	else
+		alterOpFam = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D "
+									"USING %{amname}I DROP %{items:, }s", 0);
+	append_object_object(alterOpFam, "identity",
+						 new_objtree_for_qualname(opfForm->opfnamespace,
+												  NameStr(opfForm->opfname)));
+	append_string_object(alterOpFam, "amname", stmt->amname);
+
+	list = NIL;
+	foreach(cell, cmd->d.opfam.operators)
+	{
+		OpFamilyMember *oper = lfirst(cell);
+		ObjTree	   *tmp;
+
+		if (!stmt->isDrop)
+			tmp = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s",
+								 0);
+		else
+			tmp = new_objtree_VA("OPERATOR %{num}n (%{ltype}T, %{rtype}T)",
+								 0);
+		append_integer_object(tmp, "num", oper->number);
+
+		/* Add the operator name; the DROP case doesn't have this */
+		if (!stmt->isDrop)
+		{
+			append_object_object(tmp, "operator",
+								 new_objtree_for_qualname_id(OperatorRelationId,
+															 oper->object));
+		}
+
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(oper->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(oper->righttype, -1));
+
+		/* Add the FOR SEARCH / FOR ORDER BY clause; not in the DROP case */
+		if (!stmt->isDrop)
+		{
+			if (oper->sortfamily == InvalidOid)
+				append_string_object(tmp, "purpose", "FOR SEARCH");
+			else
+			{
+				ObjTree	   *tmp2;
+
+				tmp2 = new_objtree_VA("FOR ORDER BY %{opfamily}D", 0);
+				append_object_object(tmp2, "opfamily",
+									 new_objtree_for_qualname_id(OperatorFamilyRelationId,
+																 oper->sortfamily));
+				append_object_object(tmp, "purpose", tmp2);
+			}
+		}
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	foreach(cell, cmd->d.opfam.procedures)
+	{
+		OpFamilyMember *proc = lfirst(cell);
+		ObjTree	   *tmp;
+
+		if (!stmt->isDrop)
+			tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D(%{argtypes:, }T)", 0);
+		else
+			tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T)", 0);
+		append_integer_object(tmp, "num", proc->number);
+
+		/* Add the function name and arg types; the DROP case doesn't have this */
+		if (!stmt->isDrop)
+		{
+			HeapTuple	procTup;
+			Form_pg_proc procForm;
+			Oid		   *proargtypes;
+			List	   *arglist;
+			int			i;
+
+			procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object));
+			if (!HeapTupleIsValid(procTup))
+				elog(ERROR, "cache lookup failed for procedure %u", proc->object);
+			procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+			append_object_object(tmp, "function",
+								 new_objtree_for_qualname(procForm->pronamespace,
+														  NameStr(procForm->proname)));
+			proargtypes = procForm->proargtypes.values;
+			arglist = NIL;
+			for (i = 0; i < procForm->pronargs; i++)
+			{
+				ObjTree	   *arg;
+
+				arg = new_objtree_for_type(proargtypes[i], -1);
+				arglist = lappend(arglist, new_object_object(arg));
+			}
+			append_array_object(tmp, "argtypes", arglist);
+
+			ReleaseSysCache(procTup);
+		}
+
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(proc->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(proc->righttype, -1));
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	append_array_object(alterOpFam, "items", list);
+
+	ReleaseSysCache(ftp);
+
+	return alterOpFam;
+}
+
 static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
-- 
2.1.4

0028-deparse-support-ALTER-DEFAULT-PRIVILEGES.patchtext/x-diff; charset=us-asciiDownload
>From c84133168628f0eb919d2a607542a6ad804cf445 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 16 Mar 2015 14:41:13 -0300
Subject: [PATCH 28/29] deparse: support ALTER DEFAULT PRIVILEGES

---
 src/backend/commands/event_trigger.c |  67 +++++++++++++++++++
 src/backend/tcop/deparse_utility.c   | 124 ++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c           |   3 +-
 src/include/commands/event_trigger.h |   1 +
 src/include/tcop/deparse_utility.h   |   8 ++-
 5 files changed, 200 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index c19a6a5..002f999 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1870,6 +1870,52 @@ EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * EventTriggerStashAlterDefPrivs
+ * 		Save data about an ALTER DEFAULT PRIVILEGES command being
+ * 		executed
+ */
+void
+EventTriggerStashAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc0(sizeof(StashedCommand));
+	stashed->type = SCT_AlterDefaultPrivileges;
+
+	switch (stmt->action->objtype)
+	{
+		case ACL_OBJECT_RELATION:
+			stashed->d.defprivs.objtype = "TABLES";
+			break;
+		case ACL_OBJECT_FUNCTION:
+			stashed->d.defprivs.objtype = "FUNCTIONS";
+			break;
+		case ACL_OBJECT_SEQUENCE:
+			stashed->d.defprivs.objtype = "SEQUENCES";
+			break;
+		case ACL_OBJECT_TYPE:
+			stashed->d.defprivs.objtype = "TYPES";
+			break;
+		default:
+			elog(ERROR, "unexpected object type %d", stmt->action->objtype);
+	}
+
+
+	stashed->in_extension = creating_extension;
+	stashed->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+	MemoryContextSwitchTo(oldcxt);
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -2034,6 +2080,27 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 				/* command */
 				values[i++] = CStringGetTextDatum(command);
 			}
+			else if (cmd->type == SCT_AlterDefaultPrivileges)
+			{
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree));
+				/* object_type */
+				values[i++] = CStringGetTextDatum(cmd->d.defprivs.objtype);
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+			}
 			else
 			{
 				Assert(cmd->type == SCT_Grant);
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 4a00479..a0f5849 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -5650,6 +5650,124 @@ deparse_AlterOpFamily(StashedCommand *cmd)
 }
 
 static ObjTree *
+deparse_AlterDefaultPrivilegesStmt(StashedCommand *cmd)
+{
+	ObjTree	   *alterStmt;
+	AlterDefaultPrivilegesStmt *stmt = (AlterDefaultPrivilegesStmt *) cmd->parsetree;
+	List	   *roles = NIL;
+	List	   *schemas = NIL;
+	List	   *grantees;
+	List	   *privs;
+	ListCell   *cell;
+	ObjTree	   *tmp;
+	ObjTree	   *grant;
+
+	alterStmt = new_objtree_VA("ALTER DEFAULT PRIVILEGES %{in_schema}s "
+							   "%{for_roles}s %{grant}s", 0);
+
+	/* Scan the parse node to dig out the FOR ROLE and IN SCHEMA clauses */
+	foreach(cell, stmt->options)
+	{
+		DefElem	   *opt = (DefElem *) lfirst(cell);
+		ListCell   *cell2;
+
+		Assert(IsA(opt, DefElem));
+		Assert(IsA(opt->arg, List));
+		if (strcmp(opt->defname, "roles") == 0)
+		{
+			foreach(cell2, (List *) opt->arg)
+			{
+				Value  *val = lfirst(cell2);
+
+				roles = lappend(roles,
+								new_string_object(strVal(val)));
+			}
+		}
+		else if (strcmp(opt->defname, "schemas") == 0)
+		{
+			foreach(cell2, (List *) opt->arg)
+			{
+				Value  *val = lfirst(cell2);
+
+				schemas = lappend(schemas,
+								  new_string_object(strVal(val)));
+			}
+		}
+	}
+
+	/* Add the FOR ROLE clause, if any */
+	tmp = new_objtree_VA("FOR ROLE %{roles:, }I", 0);
+	append_array_object(tmp, "roles", roles);
+	if (roles == NIL)
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterStmt, "for_roles", tmp);
+
+	/* Add the IN SCHEMA clause, if any */
+	tmp = new_objtree_VA("IN SCHEMA %{schemas:, }I", 0);
+	append_array_object(tmp, "schemas", schemas);
+	if (schemas == NIL)
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterStmt, "in_schema", tmp);
+
+	/* Add the GRANT subcommand */
+	if (stmt->action->is_grant)
+		grant = new_objtree_VA("GRANT %{privileges:, }s ON %{target}s "
+							   "TO %{grantees:, }I %{grant_option}s", 0);
+	else
+		grant = new_objtree_VA("REVOKE %{grant_option}s %{privileges:, }s "
+							   "ON %{target}s FROM %{grantees:, }I", 0);
+
+	/* add the GRANT OPTION clause */
+	tmp = new_objtree_VA(stmt->action->is_grant ?
+						   "WITH GRANT OPTION" : "GRANT OPTION FOR",
+						   1, "present", ObjTypeBool,
+						   stmt->action->grant_option);
+	append_object_object(grant, "grant_option", tmp);
+
+	/* add the target object type */
+	append_string_object(grant, "target", cmd->d.defprivs.objtype);
+
+	/* add the grantee list */
+	grantees = NIL;
+	foreach(cell, stmt->action->grantees)
+	{
+		RoleSpec   *spec = (RoleSpec *) lfirst(cell);
+		char	   *role = spec->roletype == ROLESPEC_PUBLIC ? "PUBLIC" :
+			get_rolespec_name((Node *) spec);
+
+		grantees = lappend(grantees, new_string_object(role));
+	}
+	append_array_object(grant, "grantees", grantees);
+
+	/*
+	 * Add the privileges list.  This uses the parser struct, as opposed to the
+	 * InternalGrant format used by GRANT.  There are enough other differences
+	 * that this doesn't seem worth improving.
+	 */
+	if (stmt->action->privileges == NIL)
+		privs = list_make1(new_string_object("ALL PRIVILEGES"));
+	else
+	{
+		privs = NIL;
+
+		foreach(cell, stmt->action->privileges)
+		{
+			AccessPriv *priv = lfirst(cell);
+
+			Assert(priv->cols == NIL);
+			privs = lappend(privs,
+							new_string_object(priv->priv_name));
+		}
+	}
+
+	append_array_object(grant, "privileges", privs);
+
+	append_object_object(alterStmt, "grant", grant);
+
+	return alterStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -6419,7 +6537,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterDefaultPrivilegesStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
 			break;
 
 		case T_CreatePolicyStmt:	/* CREATE POLICY */
@@ -6501,6 +6620,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterOpFamily:
 			tree = deparse_AlterOpFamily(cmd);
 			break;
+		case SCT_AlterDefaultPrivileges:
+			tree = deparse_AlterDefaultPrivilegesStmt(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3bc2387..868ae13 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1462,7 +1462,8 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_AlterDefaultPrivilegesStmt:
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
-				/* XXX FIXME WTF? */
+				EventTriggerStashAlterDefPrivs(parsetree);
+				commandStashed = true;
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index fd34001..b8b39f9 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -75,5 +75,6 @@ extern void EventTriggerAlterTableEnd(void);
 extern void EventTriggerStashGrant(InternalGrant *istmt);
 extern void EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
 							List *operators, List *procedures);
+extern void EventTriggerStashAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index b663ba2..a0ed8e4 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -30,7 +30,8 @@ typedef enum StashedCommandType
 	SCT_Simple,
 	SCT_AlterTable,
 	SCT_Grant,
-	SCT_AlterOpFamily
+	SCT_AlterOpFamily,
+	SCT_AlterDefaultPrivileges
 } StashedCommandType;
 
 /*
@@ -77,6 +78,11 @@ typedef struct StashedCommand
 			List   *operators;
 			List   *procedures;
 		} opfam;
+
+		struct
+		{
+			char   *objtype;
+		} defprivs;
 	} d;
 } StashedCommand;
 
-- 
2.1.4

0029-deparse-support-CREATE-OPERATOR-CLASS.patchtext/x-diff; charset=us-asciiDownload
>From 38113a650b2b417cec3e69cf0ab05bdb129c4057 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 16 Mar 2015 15:54:52 -0300
Subject: [PATCH 29/29] deparse: support CREATE OPERATOR CLASS

---
 src/backend/commands/event_trigger.c |  35 +++++++-
 src/backend/commands/opclasscmds.c   |   5 +-
 src/backend/tcop/deparse_utility.c   | 158 ++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c           |   2 +
 src/include/commands/defrem.h        |   1 +
 src/include/commands/event_trigger.h |   2 +
 src/include/tcop/deparse_utility.h   |  10 ++-
 7 files changed, 209 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 002f999..f8bb489 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -20,6 +20,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
@@ -1870,6 +1871,33 @@ EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
 	MemoryContextSwitchTo(oldcxt);
 }
 
+void
+EventTriggerStashCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
+							   List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc0(sizeof(StashedCommand));
+	stashed->type = SCT_CreateOpClass;
+	stashed->in_extension = creating_extension;
+	stashed->d.createopc.opcOid = opcoid;
+	stashed->d.createopc.operators = operators;	/* XXX prolly need to copy */
+	stashed->d.createopc.procedures = procedures;
+	stashed->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+
 /*
  * EventTriggerStashAlterDefPrivs
  * 		Save data about an ALTER DEFAULT PRIVILEGES command being
@@ -1997,7 +2025,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
 			if (cmd->type == SCT_Simple ||
 				cmd->type == SCT_AlterTable ||
-				cmd->type == SCT_AlterOpFamily)
+				cmd->type == SCT_AlterOpFamily ||
+				cmd->type == SCT_CreateOpClass)
 			{
 				const char *tag;
 				char	   *identity;
@@ -2014,6 +2043,10 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 					ObjectAddressSet(addr,
 									 OperatorFamilyRelationId,
 									 cmd->d.opfam.opfamOid);
+				else if (cmd->type == SCT_CreateOpClass)
+					ObjectAddressSet(addr,
+									 OperatorClassRelationId,
+									 cmd->d.createopc.opcOid);
 
 				tag = CreateCommandTag(cmd->parsetree);
 
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 68624c3..38b2ff1 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -665,6 +665,9 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	storeProcedures(stmt->opfamilyname, amoid, opfamilyoid,
 					opclassoid, procedures, false);
 
+	/* let event triggers know what happened */
+	EventTriggerStashCreateOpClass(stmt, opclassoid, operators, procedures);
+
 	/*
 	 * Create dependencies for the opclass proper.  Note: we do not create a
 	 * dependency link to the AM, because we don't currently support DROP
@@ -1673,7 +1676,7 @@ RemoveAmProcEntryById(Oid entryOid)
 	heap_close(rel, RowExclusiveLock);
 }
 
-static char *
+char *
 get_am_name(Oid amOid)
 {
 	HeapTuple	tup;
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index a0f5849..314ffde 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -5289,6 +5289,158 @@ deparse_CreateCastStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateOpClassStmt(StashedCommand *cmd)
+{
+	Oid			opcoid = cmd->d.createopc.opcOid;
+	HeapTuple   opcTup;
+	HeapTuple   opfTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_opclass opcForm;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	ListCell   *cell;
+
+	opcTup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opcoid));
+	if (!HeapTupleIsValid(opcTup))
+		elog(ERROR, "cache lookup failed for opclass with OID %u", opcoid);
+	opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
+
+	opfTup = SearchSysCache1(OPFAMILYOID, opcForm->opcfamily);
+	if (!HeapTupleIsValid(opfTup))
+		elog(ERROR, "cache lookup failed for operator family with OID %u", opcForm->opcfamily);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+	stmt = new_objtree_VA("CREATE OPERATOR CLASS %{identity}D %{default}s "
+						  "FOR TYPE %{type}T USING %{amname}I %{opfamily}s "
+						  "AS %{items:, }s", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(opcForm->opcnamespace,
+												  NameStr(opcForm->opcname)));
+
+	/*
+	 * Add the FAMILY clause; but if it has the same name and namespace as the
+	 * opclass, then have it expand to empty, because it would cause a failure
+	 * if the opfamily was created internally.
+	 */
+	tmp = new_objtree_VA("FAMILY %{opfamily}D", 1,
+						 "opfamily", ObjTypeObject,
+						 new_objtree_for_qualname(opfForm->opfnamespace,
+												  NameStr(opfForm->opfname)));
+	if (strcmp(NameStr(opfForm->opfname), NameStr(opcForm->opcname)) == 0 &&
+		opfForm->opfnamespace == opcForm->opcnamespace)
+		append_bool_object(tmp, "present", false);
+	append_object_object(stmt, "opfamily",  tmp);
+
+	/* Add the DEFAULT clause */
+	append_string_object(stmt, "default",
+						 opcForm->opcdefault ? "DEFAULT" : "");
+
+	/* Add the FOR TYPE clause */
+	append_object_object(stmt, "type",
+						 new_objtree_for_type(opcForm->opcintype, -1));
+
+	/* Add the USING clause */
+	append_string_object(stmt, "amname", get_am_name(opcForm->opcmethod));
+
+	/*
+	 * Add the initial item list.  Note we always add the STORAGE clause, even
+	 * when it is implicit in the original command.
+	 */
+	tmp = new_objtree_VA("STORAGE %{type}T", 0);
+	append_object_object(tmp, "type",
+						 new_objtree_for_type(opcForm->opckeytype != InvalidOid ?
+											  opcForm->opckeytype : opcForm->opcintype,
+											  -1));
+	list = list_make1(new_object_object(tmp));
+
+	/* Add the declared operators */
+	/* XXX this duplicates code in deparse_AlterOpFamily */
+	foreach(cell, cmd->d.createopc.operators)
+	{
+		OpFamilyMember *oper = lfirst(cell);
+		ObjTree	   *tmp;
+
+		tmp = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s",
+							 0);
+		append_integer_object(tmp, "num", oper->number);
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oper->object));
+		/* add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(oper->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(oper->righttype, -1));
+		/* Add the FOR SEARCH / FOR ORDER BY clause */
+		if (oper->sortfamily == InvalidOid)
+			append_string_object(tmp, "purpose", "FOR SEARCH");
+		else
+		{
+			ObjTree	   *tmp2;
+
+			tmp2 = new_objtree_VA("FOR ORDER BY %{opfamily}D", 0);
+			append_object_object(tmp2, "opfamily",
+								 new_objtree_for_qualname_id(OperatorFamilyRelationId,
+															 oper->sortfamily));
+			append_object_object(tmp, "purpose", tmp2);
+		}
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	/* Add the declared support functions */
+	foreach(cell, cmd->d.createopc.procedures)
+	{
+		OpFamilyMember *proc = lfirst(cell);
+		ObjTree	   *tmp;
+		HeapTuple	procTup;
+		Form_pg_proc procForm;
+		Oid		   *proargtypes;
+		List	   *arglist;
+		int			i;
+
+		tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D(%{argtypes:, }T)", 0);
+		append_integer_object(tmp, "num", proc->number);
+		procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object));
+		if (!HeapTupleIsValid(procTup))
+			elog(ERROR, "cache lookup failed for procedure %u", proc->object);
+		procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+		append_object_object(tmp, "function",
+							 new_objtree_for_qualname(procForm->pronamespace,
+													  NameStr(procForm->proname)));
+		proargtypes = procForm->proargtypes.values;
+		arglist = NIL;
+		for (i = 0; i < procForm->pronargs; i++)
+		{
+			ObjTree	   *arg;
+
+			arg = new_objtree_for_type(proargtypes[i], -1);
+			arglist = lappend(arglist, new_object_object(arg));
+		}
+		append_array_object(tmp, "argtypes", arglist);
+
+		ReleaseSysCache(procTup);
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(proc->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(proc->righttype, -1));
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	append_array_object(stmt, "items", list);
+
+	ReleaseSysCache(opfTup);
+	ReleaseSysCache(opcTup);
+
+	return stmt;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -6483,7 +6635,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateOpClassStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
 			break;
 
 		case T_CreateOpFamilyStmt:
@@ -6620,6 +6773,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterOpFamily:
 			tree = deparse_AlterOpFamily(cmd);
 			break;
+		case SCT_CreateOpClass:
+			tree = deparse_CreateOpClassStmt(cmd);
+			break;
 		case SCT_AlterDefaultPrivileges:
 			tree = deparse_AlterDefaultPrivilegesStmt(cmd);
 			break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 868ae13..fc744d3 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1398,6 +1398,8 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_CreateOpClassStmt:
 				address = DefineOpClass((CreateOpClassStmt *) parsetree);
+				/* command is stashed in DefineOpClass */
+				commandStashed = true;
 				break;
 
 			case T_CreateOpFamilyStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 595f93f..89a7e49 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -87,6 +87,7 @@ extern void IsThereOpClassInNamespace(const char *opcname, Oid opcmethod,
 extern void IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod,
 						   Oid opfnamespace);
 extern Oid	get_am_oid(const char *amname, bool missing_ok);
+extern char *get_am_name(Oid amOid);
 extern Oid	get_opclass_oid(Oid amID, List *opclassname, bool missing_ok);
 extern Oid	get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok);
 
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index b8b39f9..4c71f34 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -75,6 +75,8 @@ extern void EventTriggerAlterTableEnd(void);
 extern void EventTriggerStashGrant(InternalGrant *istmt);
 extern void EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
 							List *operators, List *procedures);
+extern void EventTriggerStashCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
+							   List *operators, List *procedures);
 extern void EventTriggerStashAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index a0ed8e4..ff7d64a 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -31,7 +31,8 @@ typedef enum StashedCommandType
 	SCT_AlterTable,
 	SCT_Grant,
 	SCT_AlterOpFamily,
-	SCT_AlterDefaultPrivileges
+	SCT_AlterDefaultPrivileges,
+	SCT_CreateOpClass
 } StashedCommandType;
 
 /*
@@ -81,6 +82,13 @@ typedef struct StashedCommand
 
 		struct
 		{
+			Oid		opcOid;
+			List   *operators;
+			List   *procedures;
+		} createopc;
+
+		struct
+		{
 			char   *objtype;
 		} defprivs;
 	} d;
-- 
2.1.4

#34Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#33)
Re: deparsing utility commands

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

One thing that Stephen commented on was the ALTER TABLE preparatory
patch. He said why not return ObjectAddress from the subcommand
routines instead of just Oid/attnum? The reason is that to interpret
the address correctly you will still need a lot more context; the OID
and attnum are only part of the truth anyway. I think there are a small
number of cases where we could meaningfully return an ObjectAddress and
have the whole thing be useful, but I'm not sure it's worth the bother.

Alright, fair enough.

In patch 0004, I removed most of the Stash() calls in ProcessUtility,
instead adding one at the bottom that takes care of most of the simple
cases. That means that a developer adding support for something new in
ProcessUtilitySlow without realizing that something must be added to the
command stash will get an error fairly early, which I think is helpful.

Right, I like this.

Patch 0021 (fixing a bug in SECURITY LABEL support) I'm not really sure
about. I don't like modifying a parse node during execution -- seems a
bad habit. It seems better to me to return the chosen security label as
an ObjectAddress in ExecSecLabelStmt, as pass that as "secondaryOid" to
the deparse_utility.c routine.

I agree, changing a parse node during execution definitely seems like a
bad idea, particularly if there's a way you could avoid doing so, which
it sounds like there is. Any reason to not follow up on that?

In patch 0023 (CREATE/ALTER POLICY support) I ran into trouble. I
represent the role in the json like this:
tmp = new_objtree_VA("TO %{role:, }I", 0);
which means that role names are quoted as identifiers. This means it
doesn't work as soon as we get a command referencing PUBLIC (which many
in the regression test do, because when the TO clause is omitted, PUBLIC
is the default). So this ends up as "PUBLIC" and everything goes
downhill. I'm not sure what to do about this, except to invent a new
%{} code.

How is this any different from the GRANT/REVOKE cases..? Surely you
have to deal with 'public' being special there also.

Subject: [PATCH 01/29] add secondaryOid to DefineTSConfiguration()

This looks fine to me.

Subject: [PATCH 02/29] deparse/core: have ALTER TABLE return table OID and
other data

[...]

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5ce4395..9675d21 100644
@@ -1853,7 +1853,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)
{

Missing a comment about the return value?

@@ -2074,7 +2083,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 */

/*

While this is used in a few places, it's by far more common to use:

if (cooked_constraints == NIL)

or to keep it the way it is..

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 623e6bf..fc2c8a9 100644
@@ -3416,78 +3418,95 @@ static void
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_ReplicaIdentity:
-			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def,
+								  lockmode);
break;
case AT_EnableRowSecurity:
ATExecEnableRowSecurity(rel);

I realize the whole thing is changing anyway, but the random whitespace
changes don't make reviewing any easier.

-static void
+static AttrNumber
ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
ColumnDef *colDef, bool isOid,
bool recurse, bool recursing, LOCKMODE lockmode)
@@ -4698,7 +4720,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
colDef->colname, RelationGetRelationName(rel))));

heap_close(attrdesc, RowExclusiveLock);
- return;
+ return InvalidAttrNumber;
}
}

@@ -4944,6 +4966,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,

heap_close(childrel, NoLock);
}
+
+ return newattnum;
}

This doesn't need to do anything for inheiriting children..? I suppose
perhaps not, but might be useful to add a comment noting that only the
attnum for the parent table is returned.

@@ -5058,7 +5082,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;
@@ -5143,18 +5167,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? */
}

I don't think I commented on this the first time through, but on
reflection, I'm inclined to say "yes" that InvalidAttrNumber should be
returned if there was no change.

Comments about what's being returned wouldn't hurt either.

/*
* ALTER TABLE ALTER COLUMN SET NOT NULL
*/
-static void
+static AttrNumber
ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode)
{
@@ -5198,18 +5226,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? */
}

ditto.

*
* Subroutine for ATExecAddConstraint.
*
@@ -5914,7 +5975,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)
@@ -5923,6 +5984,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)
@@ -5965,6 +6027,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;
}

I feel like this Assert() should really be outside of this, like so:

Assert(list_length(newcons) <= 1);

With appropriate comment that we should only ever have a list of 0 or 1
being returned from this call to AddRelationNewConstraints.

Further, it might be better as just an if() instead of a foreach, since
we aren't actually going to be ever looping here.

I further wonder if this whole thing could be simplified by moving the
CommandCounterIncrement(), if (newcons == NIL) and is_no_inherit checks
up to right under the call to AddRelationNewConstraints and then we
don't need a conditional or a foreach.

@@ -6573,7 +6652,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)
{
@@ -6583,6 +6662,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);

@@ -6624,9 +6704,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;

/*
@@ -6641,7 +6722,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,

validateForeignKeyConstraint(constrName, rel, refrel,
con->conindid,
- conid);
+ constrOid);
heap_close(refrel, NoLock);

/*
@@ -6722,6 +6803,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
systable_endscan(scan);

heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
}

This whole thing would be nicer if we inverted the second half to check
if (con->convalidated) { ... cleanup and return ... } ... continue, imv.

Regardless, a comment about how this will return InvalidOid if nothing
is done (because the constraint has already been validated) would be
good.

I thought the rest looked fine.

Subject: [PATCH 03/29] deparse: infrastructure needed for command deparsing

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
+void
+EventTriggerStashCommand(ObjectAddress address, ObjectAddress *secondaryObject,
+						 Node *parsetree)

Why pass secondaryObject in as a pointer here?

+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_Simple;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.simple.address = address;
+	stashed->d.simple.secondaryObject =
+		secondaryObject ? *secondaryObject : InvalidObjectAddress;

Per above, this could just be the same as 'address' if it wasn't a
pointer being passed in but rather either a valid object or
InvalidObjectAddress. Perhaps a helper function could be used if that
seems awkward, which only takes a single address and then calls this
function with the secondaryObject set to InvalidObjectAddress?

+Datum
+pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)

I agree with the earlier comment, seems like this should get a different
name.. pg_event_trigger_get_commands()?

+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid; in those cases, return an empty
+		 * command instead of trying to soldier on.

That's not quite accurate, is it? We don't return an empty command, we
simply don't return anything, right?

+		command = deparse_utility_command(cmd);
+
+		/*
+		 * Some parse trees return NULL when deparse is attempted; we don't
+		 * emit anything for them.
+		 */
+		if (command != NULL)
+		{

Couldn't this be flipped around to be if (!command) continue; instead
then, similar to earlier where we got an InvalidId returned?

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c

Is it just me, or does this feel a bit odd to have in backend/tcop/?

@@ -0,0 +1,992 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.c
+ *	  Functions to convert utility commands to machine-parseable
+ *	  representation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group

Pretty sure this should be up to 2015. :)

+#ifdef NOT_USED
+static void append_integer_object(ObjTree *tree, char *name, int64 value);
+#endif

...?

+/*
+ * Given a utility command parsetree and the OID of the corresponding object,
+ * return a JSON representation of the command.

[...]

+char *
+deparse_utility_command(StashedCommand *cmd)

That's not quite right now, is it? It's the parsetree and the
ObjectAddress. It'd also be good to point out that those are what's in
that StashedCommand object.

Subject: [PATCH 04/29] deparse: add EventTriggerStashCommand() calls to
ProcessUtilitySlow

No particular complaints here.

Subject: [PATCH 05/29] deparse: Support CREATE
SCHEMA/TABLE/SEQUENCE/INDEX/TRIGGER/TYPE AS

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
+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 "???";
+	}
+}

??? strikes me as less than ideal? Should we ereport() there?

@@ -977,6 +987,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.
*/

Isn't that more of a 'Note:' or similar, not an 'XXX'?

... There was a lot here and a lot of it looked pretty familiar, so I
didn't press too hard on it. I'm sure you've been testing it with
Andres, etc, which is all good, but to the extent that issues remain,
they aren't really in our critical paths and the buildfarm along with
other testing by users will flush them out. The structure and approach
certainly seems good to me.

Subject: [PATCH 06/29] deparse: Support CREATE TYPE AS ENUM / RANGE

Looked fine.

Subject: [PATCH 07/29] deparse: Support EXTENSION commands

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
/*
+ * 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?
+ */

I think requiring the same version is perfectly sensible and, yeah, if
that version isn't installed then things go boom, but that shouldn't
really surprise anyone at this point.

Having been looking at pg_dump, extensions, and binary upgrades
recently, it does make me wonder if any thought has been put towards
those functions which extensions (and binary upgrade) can call to
associated objects with extensions..? Those are modifying objects but
they don't go through the normal standard_ProcessUtility() framework.
I'm inclined to think that's a mistake, as we build this kind of
introspection into what happens in utility, but we have those cases now
and we should probably think about how to handle them.

Subject: [PATCH 08/29] deparse: Support CREATE RULE

Didn't see any issues here.

Subject: [PATCH 09/29] deparse: Support ALTER TYPE / ADD VALUE (enums)

Ditto.

Subject: [PATCH 10/29] deparse: Support ALTER .. {RENAME/OWNER/SCHEMA}

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 3ddd7ec..d28758c 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -446,7 +446,6 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
Relation	relation;
Oid			classId;
Oid			nspOid;
-				ObjectAddress address;

address = get_object_address(stmt->objectType,
stmt->object,

Wow. I'm surprised one of the various compilers we have running around
wasn't bitching about that shadowing. Is there any reason to think we
need to worry about this? We don't have any code that cares, but
perhaps some external users might?

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c

The comments in here seemed to, particularly, have a lot of unanswered
questions. Would be good to get those addressed.

Subject: [PATCH 11/29] deparse: Support CREATE/ALTER DOMAIN
Subject: [PATCH 12/29] deparse: Support CREATE/ALTER FUNCTION
Subject: [PATCH 13/29] deparse: Support ALTER TABLE
Subject: [PATCH 14/29] deparse: Support CREATE VIEW
Subject: [PATCH 15/29] deparse: Support CREATE OPERATOR FAMILY
Subject: [PATCH 16/29] deparse: Support CREATE CONVERSION
Subject: [PATCH 17/29] deparse: Support DefineStmt commands
Subject: [PATCH 18/29] deparse: support CREATE CAST
Subject: [PATCH 19/29] deparse: Support GRANT/REVOKE

These looked alright.

pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
{
@@ -1915,6 +1997,30 @@ 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(cmd->d.grant.istmt->is_grant ?
+												  "GRANT" : "REVOKE");
+				/* object_type */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.type);
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+			}

Really seems like we need to do better here, no? That's a lot of things
just getting returned as NULLs...

Subject: [PATCH 20/29] deparse: support COMMENT ON and SECURITY LABEL
Subject: [PATCH 21/29] deparse: Handle default security provider.

diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 1ef98ce..aa4de97 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -63,6 +63,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must specify provider when multiple security label providers have been loaded")));
provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
}
else
{

This should really be in the SECURITY LABEL patch, no? Not a big deal
tho.

Subject: [PATCH 22/29] deparse: support CREATE TABLE AS
Subject: [PATCH 23/29] deparse: support CREATE/ALTER POLICY
Subject: [PATCH 24/29] deparse: support REFRESH MATERIALIZED VIEW
Subject: [PATCH 25/29] deparse: support FDW-related commands
Subject: [PATCH 26/29] infrastructure for ALTER OPERATOR FAMILY
Subject: [PATCH 27/29] deparse: Support ALTER OPERATOR FAMILY
Subject: [PATCH 28/29] deparse: support ALTER DEFAULT PRIVILEGES
Subject: [PATCH 29/29] deparse: support CREATE OPERATOR CLASS

Looked alright.

Again though, as I got at above, I'm not sure we're really going to see
a lot of improvements or changes to this until it gets more exposure.
I'd really like to see this happen because I think it's a fantastic
introspective capability, but I do wish it had gone in about 4 months
ago. Still, I'm on board with moving forward on this and look forward
to it being in the tree where we can get the buildfarm working on it and
other people testing and playing with it.

Thanks!

Stephen

#35Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#1)
37 attachment(s)
Re: deparsing utility commands

Here's an updated version of this series. I have fixed many issues; in
particular the supported command set is now complete, with the only
exception being CREATE TABLE AS EXECUTE.

I went to all the prior reviews and compiled a list of unhandled issues,
on which I will be working shortly. I copied it below. I wanted to
post an updated version so in case someone wants to give this another
look while I do so.

I have changed the ALTER TABLE preparatory patch: Stephen had previously
commented that it'd be better to have it return ObjAddress rather than
Oid/attnum. I originally thought this wasn't an improvement, but after
looking again, it turns out that it is, so I changed it that way.
Things look better this way.

Note patch 0004: this modifies pg_event_trigger_dropped_objects to
report temporary objects as they are dropped. In the original coding,
all temp objects are silently ignored, but for the purposes of the
testing module of this patch, this is a problem; consider the case where
a test create a temp object, then drops it, then creates another object
with the same name. We need the sql_drop event trigger in the test
harness to report the dropped object, so that it can be dropped in the
regression_deparse database as well -- otherwise the second create
command fails because the first object still exists.

I have applied numerous other fixes.

For the first time I also include the regression test harness. To run
this, execute "make deparsecheck" in src/test/regress. This runs a
special schedule file, corresponding to serial_schedule minus the
tablespace test, and adds one test at the beginning and one test after
the end; those add event triggers to capture all commands and then run
the commands so captured in another database. The output of that is
grepped for errors; there's a very short list of errors that are always
present. Later, pg_dump is run on both databases, and the dumps are
compared. Currently there are some differences in the dumps; those
correspond to remaining issues in the deparse code. Regarding CREATE
TABLE AS EXECUTE, I had to add two "expected" files for two tests that
have an extra WARNING line when that command fails to deparse. I would
love to, instead, be able to deparse the command to something sensible.
(This is a bit brittle, because it's run from a makefile rather than
inside pg_regress; it's not pretty when things fail. Must be improved,
perhaps as a separate pg_regress binary that does all the process
control internally.)

Known remaining issues:

* Rename pg_event_trigger_get_creation_command(). Some ideas:
- pg_event_trigger_commands()
- pg_event_trigger_ddl_commands()
- pg_event_trigger_deparsed_commands()
- pg_event_trigger_deparsed_ddl_commands()

* CREATE SEQUENCE OWNED BY is not handled properly for freestanding
sequences.

* Handle default_tablespace GUC in CREATE TABLE and others.

* Handle the extension version in some way to CREATE EXTENSION.

* RENAME and others need to handle the inheritance flags (ONLY, etc)

* Many constant strings in SQL syntax ought to use a JSON object
representation with the "present" boolean thingy, instead of an empty
string as currently. That makes it easier to turn them on/off from
event triggers without forcing the user to be aware of the exact
spelling of each option.

* Should we prohibit DDL from within event triggers?

* Handle subxact aborts in ALTER TABLE processing.

* Maybe stash of commands should be an slist rather than List, just like
the dropped objects list.

* For CREATE TABLE AS, the generated command does not have a column list,
and the deparsed version might depend on temp tables for instance;
maybe we need some way to control that one from producing a normal
CREATE TABLE instead.

* The "provider" bit in SECURITY LABEL is messy. There is no Oid for
security providers, they're just loadable modules.

* event trigger currentEventTriggerContext was only being set up when
there were sql_drop/table_rewrite events; we remove that and set it up
always now. That should be restored.

* There are several XXX/FIXME/TODO comments in deparse_utility.c.

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

Attachments:

0013-deparse-Support-CREATE-ALTER-DOMAIN.patchtext/x-diff; charset=us-asciiDownload
>From da618f7bd90c2f54786584d264d7aa48e954c294 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 13/37] deparse: Support CREATE/ALTER DOMAIN

---
 src/backend/tcop/deparse_utility.c | 140 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  23 ++++++
 src/include/utils/ruleutils.h      |   2 +
 3 files changed, 163 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index ec36834..fe17b8a 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -876,6 +876,88 @@ deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree,
 	return stmt;
 }
 
+static ObjTree *
+deparse_AlterDomainStmt(Oid objectId, Node *parsetree,
+						ObjectAddress constraintAddr)
+{
+	AlterDomainStmt *node = (AlterDomainStmt *) parsetree;
+	HeapTuple	domTup;
+	Form_pg_type domForm;
+	ObjTree	   *alterDom;
+	char	   *fmt;
+
+	/* ALTER DOMAIN DROP CONSTRAINT is handled by the DROP support code */
+	if (node->subtype == 'X')
+		return NULL;
+
+	domTup = SearchSysCache1(TYPEOID, objectId);
+	if (!HeapTupleIsValid(domTup))
+		elog(ERROR, "cache lookup failed for domain with OID %u",
+			 objectId);
+	domForm = (Form_pg_type) GETSTRUCT(domTup);
+
+	switch (node->subtype)
+	{
+		case 'T':
+			/* SET DEFAULT / DROP DEFAULT */
+			if (node->def == NULL)
+				fmt = "ALTER DOMAIN %{identity}D DROP DEFAULT";
+			else
+				fmt = "ALTER DOMAIN %{identity}D SET DEFAULT %{default}s";
+			break;
+		case 'N':
+			/* DROP NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D DROP NOT NULL";
+			break;
+		case 'O':
+			/* SET NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D SET NOT NULL";
+			break;
+		case 'C':
+			/* ADD CONSTRAINT.  Only CHECK constraints are supported by domains */
+			fmt = "ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}I %{definition}s";
+			break;
+		case 'V':
+			/* VALIDATE CONSTRAINT */
+			fmt = "ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}I";
+			break;
+		default:
+			elog(ERROR, "invalid subtype %c", node->subtype);
+	}
+
+	alterDom = new_objtree_VA(fmt, 0);
+	append_object_object(alterDom, "identity",
+						 new_objtree_for_qualname(domForm->typnamespace,
+												  NameStr(domForm->typname)));
+
+	/*
+	 * Process subtype-specific options.  Validating a constraint
+	 * requires its name ...
+	 */
+	if (node->subtype == 'V')
+		append_string_object(alterDom, "constraint_name", node->name);
+
+	/* ... a new constraint has a name and definition ... */
+	if (node->subtype == 'C')
+	{
+		append_string_object(alterDom, "definition",
+							 pg_get_constraintdef_string(constraintAddr.objectId,
+														 false));
+		/* can't rely on node->name here; might not be defined */
+		append_string_object(alterDom, "constraint_name",
+							 get_constraint_name(constraintAddr.objectId));
+	}
+
+	/* ... and setting a default has a definition only. */
+	if (node->subtype == 'T' && node->def != NULL)
+		append_string_object(alterDom, "default", DomainGetDefault(domTup));
+
+	/* done */
+	ReleaseSysCache(domTup);
+
+	return alterDom;
+}
+
 /*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
@@ -1837,6 +1919,59 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return range;
 }
 
+static ObjTree *
+deparse_CreateDomain(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *createDomain;
+	ObjTree	   *tmp;
+	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}T %{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_type(typForm->typbasetype, typForm->typtypmod));
+
+	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);
+
+	return createDomain;
+}
+
 /*
  * Return the given object type as a string.
  */
@@ -2869,7 +3004,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterDomainStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterDomainStmt(objectId, parsetree,
+											  cmd->d.simple.secondaryObject);
 			break;
 
 			/* other local objects */
@@ -2986,7 +3122,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateDomainStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateDomain(objectId, parsetree);
 			break;
 
 		case T_CreateConversionStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 169119c..791673a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9728,3 +9728,26 @@ RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
 
 	return defstr;
 }
+
+/*
+ * Return the default value of a domain.
+ */
+char *
+DomainGetDefault(HeapTuple domTup)
+{
+	Datum	def;
+	Node   *defval;
+	char   *defstr;
+	bool	isnull;
+
+	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+							 &isnull);
+	if (isnull)
+		elog(ERROR, "domain \"%s\" does not have a default value",
+			 NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+	defval = stringToNode(TextDatumGetCString(def));
+	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
+									   false, false, 0, 0);
+
+	return defstr;
+}
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 9989cdd..828c80b 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -46,4 +46,6 @@ extern char *generate_collation_name(Oid collid);
 extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
 						 List *dpcontext);
 
+extern char *DomainGetDefault(HeapTuple domTup);
+
 #endif	/* RULEUTILS_H */
-- 
2.1.4

0014-deparse-Support-CREATE-ALTER-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From 6935739717b4133e082b0869b29536dcdb523eb3 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 14/37] deparse: Support CREATE/ALTER FUNCTION

Petr Jelinek, Andres Freund
---
 src/backend/tcop/deparse_utility.c | 467 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  25 ++
 src/include/utils/ruleutils.h      |   1 +
 3 files changed, 491 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index fe17b8a..8e0a460 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1972,6 +1972,469 @@ deparse_CreateDomain(Oid objectId, Node *parsetree)
 	return createDomain;
 }
 
+static ObjTree *
+deparse_FunctionSet(VariableSetKind kind, char *name, char *value)
+{
+	ObjTree	   *r;
+
+	if (kind == VAR_RESET_ALL)
+	{
+		r = new_objtree_VA("RESET ALL", 0);
+	}
+	else if (value != NULL)
+	{
+		/*
+		 * Some GUC variable names are 'LIST' type and hence must not be
+		 * quoted. FIXME: shouldn't this and pg_get_functiondef() rather use
+		 * guc.c to check for GUC_LIST?
+		 */
+		if (pg_strcasecmp(name, "DateStyle") == 0
+			|| pg_strcasecmp(name, "search_path") == 0)
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}s", 0);
+		else
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}L", 0);
+
+		append_string_object(r, "set_name", name);
+		append_string_object(r, "set_value", value);
+	}
+	else
+	{
+		r = new_objtree_VA("RESET %{set_name}I", 0);
+		append_string_object(r, "set_name", name);
+	}
+
+	return r;
+}
+
+/*
+ * 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.
+ */
+static ObjTree *
+deparse_CreateFunction(Oid objectId, Node *parsetree)
+{
+	CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree;
+	ObjTree	   *createFunc;
+	ObjTree	   *sign;
+	ObjTree	   *tmp;
+	Datum		tmpdatum;
+	char	   *fmt;
+	char	   *definition;
+	char	   *source;
+	char	   *probin;
+	List	   *params;
+	List	   *defaults;
+	List	   *sets = NIL;
+	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)
+			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(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(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}n", 1,
+										"cost", ObjTypeFloat,
+										procForm->procost));
+
+	tmp = new_objtree_VA("ROWS %{rows}n", 0);
+	if (procForm->prorows == 0)
+		append_bool_object(tmp, "present", false);
+	else
+		append_float_object(tmp, "rows", procForm->prorows);
+	append_object_object(createFunc, "rows", tmp);
+
+	foreach(cell, node->options)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+			sets = lappend(sets, new_object_object(tmp));
+		}
+	}
+	append_array_object(createFunc, "set_options", sets);
+
+	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);
+
+	return createFunc;
+}
+
+/*
+ * deparse_AlterFunctionStmt
+ *		Deparse a AlterFunctionStmt (ALTER FUNCTION)
+ *
+ * Given a function OID and the parsetree that created it, return the JSON
+ * blob representing the alter command.
+ */
+static ObjTree *
+deparse_AlterFunction(Oid objectId, Node *parsetree)
+{
+	AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree;
+	ObjTree	   *alterFunc;
+	ObjTree	   *sign;
+	HeapTuple	procTup;
+	Form_pg_proc procForm;
+	List	   *params;
+	List	   *elems = NIL;
+	ListCell   *cell;
+	int			i;
+
+	/* 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);
+
+	alterFunc = new_objtree_VA("ALTER FUNCTION %{signature}s %{definition: }s", 0);
+
+	sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0);
+
+	params = NIL;
+
+	/*
+	 * ALTER FUNCTION does not change signature so we can use catalog
+	 * to get input type Oids.
+	 */
+	for (i = 0; i < procForm->pronargs; i++)
+	{
+		ObjTree	   *tmp = new_objtree_VA("%{type}T", 0);
+
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(procForm->proargtypes.values[i], -1));
+		params = lappend(params, new_object_object(tmp));
+	}
+
+	append_array_object(sign, "arguments", params);
+	append_object_object(sign, "identity",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 objectId));
+	append_object_object(alterFunc, "signature", sign);
+
+	foreach(cell, node->actions)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "volatility") == 0)
+		{
+			tmp = new_objtree_VA(strVal(defel->arg), 0);
+		}
+		else if (strcmp(defel->defname, "strict") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "RETURNS NULL ON NULL INPUT" :
+								 "CALLED ON NULL INPUT", 0);
+		}
+		else if (strcmp(defel->defname, "security") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "SECURITY DEFINER" : "SECURITY INVOKER", 0);
+		}
+		else if (strcmp(defel->defname, "leakproof") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "LEAKPROOF" : "NOT LEAKPROOF", 0);
+		}
+		else if (strcmp(defel->defname, "cost") == 0)
+		{
+			tmp = new_objtree_VA("COST %{cost}n", 1,
+								 "cost", ObjTypeFloat,
+								 defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "rows") == 0)
+		{
+			tmp = new_objtree_VA("ROWS %{rows}n", 0);
+			if (defGetNumeric(defel) == 0)
+				append_bool_object(tmp, "present", false);
+			else
+				append_float_object(tmp, "rows",
+									defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+		}
+
+		elems = lappend(elems, new_object_object(tmp));
+	}
+
+	append_array_object(alterFunc, "definition", elems);
+
+	ReleaseSysCache(procTup);
+
+	return alterFunc;
+}
+
 /*
  * Return the given object type as a string.
  */
@@ -3085,11 +3548,11 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateFunctionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateFunction(objectId, parsetree);
 			break;
 
 		case T_AlterFunctionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterFunction(objectId, parsetree);
 			break;
 
 		case T_RuleStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 791673a..7fbd4f9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9751,3 +9751,28 @@ DomainGetDefault(HeapTuple domTup)
 
 	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 828c80b..ce6a1b1 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -42,6 +42,7 @@ extern List *set_deparse_context_planstate(List *dpcontext,
 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);
-- 
2.1.4

0015-deparse-Support-ALTER-TABLE.patchtext/x-diff; charset=us-asciiDownload
>From 3a124424c1dc7b1e0b51ffc81b479b8175ed5511 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 15/37] deparse: Support ALTER TABLE

With help from Andres Freund
---
 src/backend/commands/event_trigger.c | 133 +++++++-
 src/backend/commands/tablecmds.c     |  11 +
 src/backend/tcop/deparse_utility.c   | 639 ++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c           |  23 +-
 src/include/commands/event_trigger.h |   6 +
 src/include/tcop/deparse_utility.h   |  13 +-
 6 files changed, 819 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index d80ffa5..b3b8627 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -58,6 +58,7 @@ typedef struct EventTriggerQueryState
 
 	bool		commandCollectionInhibited;
 	MemoryContext cxt;
+	StashedCommand *curcmd;
 	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
@@ -1220,8 +1221,10 @@ EventTriggerBeginCompleteQuery(void)
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
+
 	state->commandCollectionInhibited = currentEventTriggerState ?
 		currentEventTriggerState->commandCollectionInhibited : false;
+	state->curcmd = NULL;
 	state->stash = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
@@ -1647,6 +1650,129 @@ EventTriggerUndoInhibitCommandCollection(void)
 	currentEventTriggerState->commandCollectionInhibited = false;
 }
 
+/*
+ * EventTriggerAlterTableStart
+ *		Prepare to receive data on an ALTER TABLE 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.
+ *
+ * XXX -- this API isn't considering the possibility of an ALTER TABLE command
+ * being called reentrantly by an event trigger function.  Do we need stackable
+ * commands at this level?  Perhaps at least we should detect the condition and
+ * raise an error.
+ */
+void
+EventTriggerAlterTableStart(Node *parsetree)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_AlterTable;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.alterTable.classId = RelationRelationId;
+	stashed->d.alterTable.objectId = InvalidOid;
+	stashed->d.alterTable.subcmds = NIL;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->curcmd = stashed;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remember the OID of the object being affected by a complex command.
+ *
+ * This is needed because in some cases we don't know the OID until later.
+ */
+void
+EventTriggerAlterTableRelid(Oid objectId)
+{
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	currentEventTriggerState->curcmd->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerAlterTableStashSubcmd
+ * 		Save data about a single part of a complex DDL command
+ *
+ * Several different commands go through this path, but apart from ALTER TABLE
+ * itself, they are all concerned with AlterTableCmd nodes that are generated
+ * internally, so that's all that this code needs to handle at the moment.
+ */
+void
+EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid,
+								  ObjectAddress address)
+{
+	MemoryContext	oldcxt;
+	StashedATSubcmd *newsub;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	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->address = address;
+	newsub->parsetree = copyObject(subcmd);
+
+	currentEventTriggerState->curcmd->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->curcmd->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableEnd
+ * 		Finish up saving an ALTER TABLE command, and add it to stash
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerAlterTableEnd(void)
+{
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	/* 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)
 {
@@ -1725,7 +1851,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
 		MemSet(nulls, 0, sizeof(nulls));
 
-		if (cmd->type == SCT_Simple)
+		if (cmd->type == SCT_Simple ||
+			cmd->type == SCT_AlterTable)
 		{
 			const char *tag;
 			char	   *identity;
@@ -1734,6 +1861,10 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
 			if (cmd->type == SCT_Simple)
 				addr = cmd->d.simple.address;
+			else if (cmd->type == SCT_AlterTable)
+				ObjectAddressSet(addr,
+								 cmd->d.alterTable.classId,
+								 cmd->d.alterTable.objectId);
 
 			tag = CreateCommandTag(cmd->parsetree);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index eaac0b6..5b0d0a1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2781,6 +2781,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerAlterTableRelid(relid);
+
 	ATController(NULL, rel, cmds, recurse, lockmode);
 }
 
@@ -3661,6 +3663,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	}
 
 	/*
+	 * Report the subcommand to interested event triggers.
+	 */
+	EventTriggerAlterTableStashSubcmd((Node *) cmd, RelationGetRelid(rel),
+									  address);
+
+	/*
 	 * Bump the command counter to ensure the next subcommand in the sequence
 	 * can see the changes so far
 	 */
@@ -9693,7 +9701,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		cmds = lappend(cmds, cmd);
 
+		EventTriggerAlterTableStart((Node *) stmt);
+		/* OID is set by AlterTableInternal */
 		AlterTableInternal(lfirst_oid(l), cmds, false);
+		EventTriggerAlterTableEnd();
 	}
 
 	return new_tablespaceoid;
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 8e0a460..ac3175b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1168,7 +1168,7 @@ deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
  */
 static ObjTree *
 deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
-				  ColumnDef *coldef)
+				  ColumnDef *coldef, bool is_alter)
 {
 	ObjTree    *column;
 	ObjTree    *tmp;
@@ -1236,6 +1236,9 @@ deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
 		 * we scan the list of constraints attached to this column to determine
 		 * whether we need to emit anything.
 		 * (Fortunately, NOT NULL constraints cannot be table constraints.)
+		 *
+		 * In the ALTER TABLE cases, we also add a NOT NULL if the colDef is
+		 * marked is_not_null.
 		 */
 		saw_notnull = false;
 		foreach(cell, coldef->constraints)
@@ -1245,6 +1248,8 @@ deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
 			if (constr->contype == CONSTR_NOTNULL)
 				saw_notnull = true;
 		}
+		if (is_alter && coldef->is_not_null)
+			saw_notnull = true;
 
 		if (saw_notnull)
 			append_string_object(column, "not_null", "NOT NULL");
@@ -1309,6 +1314,7 @@ deparse_ColumnDef_typed(Relation relation, List *dpcontext, ColumnDef *coldef)
 	/*
 	 * 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.
+	 * (This routine is never used for ALTER cases.)
 	 */
 	saw_notnull = false;
 	foreach(cell, coldef->constraints)
@@ -1362,7 +1368,8 @@ deparseTableElements(Relation relation, List *tableElements, List *dpcontext,
 						deparse_ColumnDef_typed(relation, dpcontext,
 												(ColumnDef *) elt) :
 						deparse_ColumnDef(relation, dpcontext,
-										  composite, (ColumnDef *) elt);
+										  composite, (ColumnDef *) elt,
+										  false);
 					if (tree != NULL)
 					{
 						ObjElem    *column;
@@ -1553,6 +1560,70 @@ deparse_DefElem(DefElem *elem, bool is_reset)
 }
 
 /*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ */
+static ObjTree *
+deparse_ColumnSetOptions(AlterTableCmd *subcmd)
+{
+	List	   *sets = NIL;
+	ListCell   *cell;
+	ObjTree    *tmp;
+	bool		is_reset = subcmd->subtype == AT_ResetOptions;
+
+	if (is_reset)
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I RESET (%{options:, }s)", 0);
+	else
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I SET (%{options:, }s)", 0);
+
+	append_string_object(tmp, "column", subcmd->name);
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem	   *elem;
+		ObjTree	   *set;
+
+		elem = (DefElem *) lfirst(cell);
+		set = deparse_DefElem(elem, is_reset);
+		sets = lappend(sets, new_object_object(set));
+	}
+
+	append_array_object(tmp, "options", sets);
+
+	return tmp;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ */
+static ObjTree *
+deparse_RelSetOptions(AlterTableCmd *subcmd)
+{
+	List	   *sets = NIL;
+	ListCell   *cell;
+	ObjTree    *tmp;
+	bool		is_reset = subcmd->subtype == AT_ResetRelOptions;
+
+	if (is_reset)
+		tmp = new_objtree_VA("RESET (%{options:, }s)", 0);
+	else
+		tmp = new_objtree_VA("SET (%{options:, }s)", 0);
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem	   *elem;
+		ObjTree	   *set;
+
+		elem = (DefElem *) lfirst(cell);
+		set = deparse_DefElem(elem, is_reset);
+		sets = lappend(sets, new_object_object(set));
+	}
+
+	append_array_object(tmp, "options", sets);
+
+	return tmp;
+}
+
+/*
  * deparse_CreateStmt
  *		Deparse a CreateStmt (CREATE TABLE)
  *
@@ -3427,6 +3498,567 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 	return alterEnum;
 }
 
+static ObjTree *
+deparse_AlterTableStmt(StashedCommand *cmd)
+{
+	ObjTree	   *alterTableStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	List	   *dpcontext;
+	Relation	rel;
+	List	   *subcmds = NIL;
+	ListCell   *cell;
+	char	   *fmtstr;
+	const char *reltype;
+	bool		istype = false;
+
+	Assert(cmd->type == SCT_AlterTable);
+
+	rel = relation_open(cmd->d.alterTable.objectId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									cmd->d.alterTable.objectId);
+
+	switch (rel->rd_rel->relkind)
+	{
+		case RELKIND_RELATION:
+			reltype = "TABLE";
+			break;
+		case RELKIND_INDEX:
+			reltype = "INDEX";
+			break;
+		case RELKIND_VIEW:
+			reltype = "VIEW";
+			break;
+		case RELKIND_COMPOSITE_TYPE:
+			reltype = "TYPE";
+			istype = true;
+			break;
+		case RELKIND_FOREIGN_TABLE:
+			reltype = "FOREIGN TABLE";
+			break;
+
+		default:
+			elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind);
+			reltype = NULL;;
+	}
+
+	fmtstr = psprintf("ALTER %s %%{identity}D %%{subcmds:, }s", reltype);
+	alterTableStmt = new_objtree_VA(fmtstr, 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, true);
+				fmtstr = psprintf("ADD %s %%{definition}s",
+								  istype ? "ATTRIBUTE" : "COLUMN");
+				tmp = new_objtree_VA(fmtstr, 2,
+									 "type", ObjTypeString, "add column",
+									 "definition", ObjTypeObject, tree);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			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(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(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(tmp));
+				break;
+
+			case AT_SetStatistics:
+				{
+					Assert(IsA(subcmd->def, Integer));
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n",
+										 3, "type", ObjTypeString, "set statistics",
+										 "column", ObjTypeString, subcmd->name,
+										 "statistics", ObjTypeInteger,
+										 intVal((Value *) subcmd->def));
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_SetOptions:
+			case AT_ResetOptions:
+				subcmds = lappend(subcmds, new_object_object(
+									  deparse_ColumnSetOptions(subcmd)));
+				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(tmp));
+				break;
+
+			case AT_DropColumnRecurse:
+			case AT_DropColumn:
+				fmtstr = psprintf("DROP %s %%{column}I %%{cascade}s",
+								  istype ? "ATTRIBUTE" : "COLUMN");
+				tmp = new_objtree_VA(fmtstr, 2,
+									 "type", ObjTypeString, "drop column",
+									 "column", ObjTypeString, subcmd->name);
+				tmp2 = new_objtree_VA("CASCADE", 1,
+									  "present", ObjTypeBool, subcmd->behavior);
+				append_object_object(tmp, "cascade", tmp2);
+
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddIndex:
+				{
+					Oid			idxOid = substashed->address.objectId;
+					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(tmp));
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;
+
+			case AT_AddConstraint:
+			case AT_AddConstraintRecurse:
+				{
+					/* XXX need to set the "recurse" bit somewhere? */
+					Oid			constrOid = substashed->address.objectId;
+
+					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(tmp));
+				}
+				break;
+
+			case AT_AlterConstraint:
+				{
+					Oid		constrOid = substashed->address.objectId;
+					Constraint *c = (Constraint *) subcmd->def;
+
+					/* if no constraint was altered, silently skip it */
+					if (!OidIsValid(constrOid))
+						break;
+
+					Assert(IsA(c, Constraint));
+					tmp = new_objtree_VA("ALTER CONSTRAINT %{name}I %{deferrable}s %{init_deferred}s",
+										 2, "type", ObjTypeString, "alter constraint",
+										 "name", ObjTypeString, get_constraint_name(constrOid));
+					append_string_object(tmp, "deferrable", c->deferrable ?
+										 "DEFERRABLE" : "NOT DEFERRABLE");
+					append_string_object(tmp, "init_deferred", c->initdeferred ?
+										 "INITIALLY DEFERRED" : "INITIALLY IMMEDIATE");
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_ValidateConstraintRecurse:
+			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(tmp));
+				break;
+
+			case AT_DropConstraintRecurse:
+			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(tmp));
+				break;
+
+			case AT_AlterColumnType:
+				{
+					TupleDesc tupdesc = RelationGetDescr(rel);
+					Form_pg_attribute att;
+					ColumnDef	   *def;
+
+					att = tupdesc->attrs[substashed->address.objectSubId - 1];
+					def = (ColumnDef *) subcmd->def;
+					Assert(IsA(def, ColumnDef));
+
+					fmtstr = psprintf("ALTER %s %%{column}I SET DATA TYPE %%{datatype}T %%{collation}s %s",
+									  istype ? "ATTRIBUTE" : "COLUMN",
+									  istype ? "%{cascade}s" : "%{using}s");
+
+					tmp = new_objtree_VA(fmtstr, 2,
+										 "type", ObjTypeString, "alter column type",
+										 "column", ObjTypeString, subcmd->name);
+					/* add the TYPE clause */
+					append_object_object(tmp, "datatype",
+										 new_objtree_for_type(att->atttypid,
+															  att->atttypmod));
+
+					/* add a COLLATE clause, if needed */
+					tmp2 = new_objtree_VA("COLLATE %{name}D", 0);
+					if (OidIsValid(att->attcollation))
+					{
+						ObjTree *collname;
+
+						collname = new_objtree_for_qualname_id(CollationRelationId,
+															   att->attcollation);
+						append_object_object(tmp2, "name", collname);
+					}
+					else
+						append_bool_object(tmp2, "present", false);
+					append_object_object(tmp, "collation", tmp2);
+
+					/* if not a composite type, add the USING clause */
+					if (!istype)
+					{
+						/*
+						 * If there's a USING clause, transformAlterTableStmt
+						 * ran it through transformExpr and stored the
+						 * resulting node in cooked_default, which we can use
+						 * here.
+						 */
+						tmp2 = new_objtree_VA("USING %{expression}s", 0);
+						if (def->raw_default)
+						{
+							Datum	deparsed;
+							char   *defexpr;
+
+							defexpr = nodeToString(def->cooked_default);
+							deparsed = DirectFunctionCall2(pg_get_expr,
+														   CStringGetTextDatum(defexpr),
+														   RelationGetRelid(rel));
+							append_string_object(tmp2, "expression",
+												 TextDatumGetCString(deparsed));
+						}
+						else
+							append_bool_object(tmp2, "present", false);
+						append_object_object(tmp, "using", tmp2);
+					}
+
+					/* if it's a composite type, add the CASCADE clause */
+					if (istype)
+					{
+						tmp2 = new_objtree_VA("CASCADE", 0);
+						if (subcmd->behavior != DROP_CASCADE)
+							append_bool_object(tmp2, "present", false);
+						append_object_object(tmp, "cascade", tmp2);
+					}
+
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS");
+				break;
+
+			case AT_ChangeOwner:
+				tmp = new_objtree_VA("OWNER TO %{owner}I",
+									 2, "type", ObjTypeString, "change owner",
+									 "owner",  ObjTypeString,
+									 get_rolespec_name(subcmd->newowner));
+				subcmds = lappend(subcmds, new_object_object(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(tmp));
+				break;
+
+			case AT_DropCluster:
+				tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+									 "type", ObjTypeString, "set without cluster");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetLogged:
+				tmp = new_objtree_VA("SET LOGGED", 1,
+									 "type", ObjTypeString, "set logged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetUnLogged:
+				tmp = new_objtree_VA("SET UNLOGGED", 1,
+									 "type", ObjTypeString, "set unlogged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOidsRecurse:
+			case AT_AddOids:
+				tmp = new_objtree_VA("SET WITH OIDS", 1,
+									 "type", ObjTypeString, "set with oids");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOids:
+				tmp = new_objtree_VA("SET WITHOUT OIDS", 1,
+									 "type", ObjTypeString, "set without oids");
+				subcmds = lappend(subcmds, new_object_object(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(tmp));
+				break;
+
+			case AT_SetRelOptions:
+			case AT_ResetRelOptions:
+				subcmds = lappend(subcmds, new_object_object(
+									  deparse_RelSetOptions(subcmd)));
+				break;
+
+			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(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(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(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(tmp));
+				break;
+
+			case AT_EnableTrigAll:
+				tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "enable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigAll:
+				tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "disable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableTrigUser:
+				tmp = new_objtree_VA("ENABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "enable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigUser:
+				tmp = new_objtree_VA("DISABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "disable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableRule:
+				tmp = new_objtree_VA("ENABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableAlwaysRule:
+				tmp = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable always rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableReplicaRule:
+				tmp = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable replica rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRule:
+				tmp = new_objtree_VA("DISABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "disable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddInherit:
+				tmp = new_objtree_VA("INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 substashed->address.objectId));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropInherit:
+				tmp = new_objtree_VA("NO INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "drop inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 substashed->address.objectId));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOf:
+				tmp = new_objtree_VA("OF %{type_of}T",
+									 2, "type", ObjTypeString, "add of",
+									 "type_of", ObjTypeObject,
+									 new_objtree_for_type(substashed->address.objectId, -1));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOf:
+				tmp = new_objtree_VA("NOT OF",
+									 1, "type", ObjTypeString, "not of");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				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(tmp));
+				break;
+
+			case AT_EnableRowSecurity:
+				tmp = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "enable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRowSecurity:
+				tmp = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "disable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_GenericOptions:
+				elog(ERROR, "unimplemented deparse of ALTER TABLE OPTIONS (...)");
+				break;
+
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+	}
+
+	heap_close(rel, AccessShareLock);
+
+	if (list_length(subcmds) == 0)
+		return NULL;
+
+	append_array_object(alterTableStmt, "subcmds", subcmds);
+	return alterTableStmt;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -3722,6 +4354,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_Simple:
 			tree = deparse_simple_command(cmd);
 			break;
+		case SCT_AlterTable:
+			tree = deparse_AlterTableStmt(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index fde1ddb..0bd239c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1030,6 +1030,10 @@ ProcessUtilitySlow(Node *parsetree,
 						stmts = transformAlterTableStmt(relid, atstmt,
 														queryString);
 
+						/* ... ensure we have an event trigger context ... */
+						EventTriggerAlterTableStart(parsetree);
+						EventTriggerAlterTableRelid(relid);
+
 						/* ... and do it */
 						foreach(l, stmts)
 						{
@@ -1043,19 +1047,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.
+								 */
+								EventTriggerAlterTableEnd();
 								ProcessUtility(stmt,
 											   queryString,
 											   PROCESS_UTILITY_SUBCOMMAND,
 											   params,
 											   None_Receiver,
 											   NULL);
+								EventTriggerAlterTableStart(parsetree);
+								EventTriggerAlterTableRelid(relid);
 							}
 
 							/* Need CCI between commands */
 							if (lnext(l) != NULL)
 								CommandCounterIncrement();
 						}
+
+						/* done */
+						EventTriggerAlterTableEnd();
 					}
 					else
 						ereport(NOTICE,
@@ -1213,6 +1230,7 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
+					EventTriggerAlterTableStart(parsetree);
 					address =
 						DefineIndex(relid,	/* OID of heap relation */
 									stmt,
@@ -1229,6 +1247,7 @@ ProcessUtilitySlow(Node *parsetree,
 					EventTriggerStashCommand(address, secondaryObject,
 											 parsetree);
 					commandStashed = true;
+					EventTriggerAlterTableEnd();
 				}
 				break;
 
@@ -1303,10 +1322,12 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
+				EventTriggerAlterTableStart(parsetree);
 				address = DefineView((ViewStmt *) parsetree, queryString);
 				EventTriggerStashCommand(address, secondaryObject, parsetree);
 				/* stashed internally */
 				commandStashed = true;
+				EventTriggerAlterTableEnd();
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index a38d5aa..7a449cc 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -66,4 +66,10 @@ extern void EventTriggerUndoInhibitCommandCollection(void);
 extern void EventTriggerStashCommand(ObjectAddress address,
 						 ObjectAddress secondaryObject, Node *parsetree);
 
+extern void EventTriggerAlterTableStart(Node *parsetree);
+extern void EventTriggerAlterTableRelid(Oid objectId);
+extern void EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid,
+								  ObjectAddress address);
+extern void EventTriggerAlterTableEnd(void);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 7f355cb..3b68290 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -26,6 +26,7 @@
 typedef enum StashedCommandType
 {
 	SCT_Simple,
+	SCT_AlterTable
 } StashedCommandType;
 
 /*
@@ -33,8 +34,7 @@ typedef enum StashedCommandType
  */
 typedef struct StashedATSubcmd
 {
-	AttrNumber		attnum;	/* affected column number */
-	Oid				oid;	/* affected constraint, default value or index */
+	ObjectAddress	address; /* affected column, constraint, index, ... */
 	Node		   *parsetree;
 } StashedATSubcmd;
 
@@ -43,6 +43,7 @@ typedef struct StashedCommand
 	StashedCommandType type;
 	bool		in_extension;
 	Node	   *parsetree;
+	slist_node	link;
 
 	union
 	{
@@ -52,6 +53,14 @@ typedef struct StashedCommand
 			ObjectAddress address;
 			ObjectAddress secondaryObject;
 		} simple;
+
+		/* ALTER TABLE, and internal uses thereof */
+		struct
+		{
+			Oid		objectId;
+			Oid		classId;
+			List   *subcmds;
+		} alterTable;
 	} d;
 } StashedCommand;
 
-- 
2.1.4

0016-deparse-Support-CREATE-VIEW.patchtext/x-diff; charset=us-asciiDownload
>From a2f9116ffcb16aecf4b9dbd8051c0e03d372ba4f Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 14:34:00 -0300
Subject: [PATCH 16/37] deparse: Support CREATE VIEW

---
 src/backend/tcop/deparse_utility.c | 38 +++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  7 +++++++
 src/include/utils/ruleutils.h      |  1 +
 3 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index ac3175b..a4f1d71 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -959,6 +959,42 @@ deparse_AlterDomainStmt(Oid objectId, Node *parsetree,
 }
 
 /*
+ * deparse_ViewStmt
+ *		deparse a ViewStmt
+ *
+ * Given a view OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_ViewStmt(Oid objectId, Node *parsetree)
+{
+	ViewStmt   *node = (ViewStmt *) parsetree;
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	Relation	relation;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s",
+							  2,
+							  "or_replace", ObjTypeString,
+							  node->replace ? "OR REPLACE" : "",
+							  "persistence", ObjTypeString,
+					  get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(viewStmt, "identity", tmp);
+
+	append_string_object(viewStmt, "query",
+						 pg_get_viewdef_internal(objectId));
+
+	relation_close(relation, AccessShareLock);
+
+	return viewStmt;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -4176,7 +4212,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_ViewStmt(objectId, parsetree);
 			break;
 
 		case T_CreateFunctionStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7fbd4f9..1a2f03c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -669,6 +669,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
  */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index ce6a1b1..7b22ea0 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -32,6 +32,7 @@ extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
 					   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
 
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
-- 
2.1.4

0017-deparse-Support-CREATE-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From 63977a7004db0776b54432ee93e9f6c517b4fa6f Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 14:34:29 -0300
Subject: [PATCH 17/37] deparse: Support CREATE OPERATOR FAMILY

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index a4f1d71..07daf08 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3535,6 +3535,41 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateOpFamily(Oid objectId, Node *parsetree)
+{
+	HeapTuple   opfTup;
+	HeapTuple   amTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_am  amForm;
+	ObjTree	   *copfStmt;
+	ObjTree	   *tmp;
+
+	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}I",
+							  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));
+
+	ReleaseSysCache(amTup);
+	ReleaseSysCache(opfTup);
+
+	return copfStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -4269,7 +4304,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateOpFamilyStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateOpFamily(objectId, parsetree);
 			break;
 
 		case T_AlterOpFamilyStmt:
-- 
2.1.4

0018-deparse-Support-CREATE-CONVERSION.patchtext/x-diff; charset=us-asciiDownload
>From 3afebb870a844653f106e21d6cb378f50ddfbc71 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 18/37] deparse: Support CREATE CONVERSION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 07daf08..3439d15 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3535,6 +3535,41 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+	HeapTuple   conTup;
+	Relation	convrel;
+	Form_pg_conversion conForm;
+	ObjTree	   *ccStmt;
+
+	convrel = heap_open(ConversionRelationId, AccessShareLock);
+	conTup = get_catalog_object_by_oid(convrel, 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));
+
+	heap_close(convrel, AccessShareLock);
+
+	return ccStmt;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -4292,7 +4327,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateConversionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateConversion(objectId, parsetree);
 			break;
 
 		case T_CreateCastStmt:
-- 
2.1.4

0019-deparse-Support-DefineStmt-commands.patchtext/x-diff; charset=us-asciiDownload
>From 078da021febf30a5fedb10a833cc199c38118aa1 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 19/37] deparse: Support DefineStmt commands

CREATE AGGREGATE
CREATE COLLATION
CREATE OPERATOR
CREATE TEXT SEARCH CONFIGURATION
CREATE TEXT SEARCH PARSER
CREATE TEXT SEARCH DICTIONARY
CREATE TEXT SEARCH TEMPLATE
CREATE TYPE
---
 src/backend/tcop/deparse_utility.c | 875 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 874 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 3439d15..83dd59b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -736,6 +736,878 @@ get_persistence_str(char persistence)
 }
 
 /*
+ * Form an ObjElem to be used as a single argument in an aggregate argument
+ * list
+ */
+static ObjElem *
+form_agg_argument(int idx, char *modes, char **names, Oid *types)
+{
+	ObjTree	   *arg;
+
+	arg = new_objtree_VA("%{mode}s %{name}s %{type}T", 0);
+
+	append_string_object(arg, "mode",
+						 (modes && modes[idx] == 'v') ? "VARIADIC" : "");
+	append_string_object(arg, "name", names ? names[idx] : "");
+	append_object_object(arg, "type",
+						 new_objtree_for_type(types[idx], -1));
+
+	return new_object_object(arg);
+}
+
+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)));
+
+	/*
+	 * Add the argument list.  There are three cases to consider:
+	 *
+	 * 1. no arguments, in which case the signature is (*).
+	 *
+	 * 2. Not an ordered-set aggregate.  In this case there's one or more
+	 * arguments.
+	 *
+	 * 3. Ordered-set aggregates. These have zero or more direct arguments, and
+	 * one or more ordered arguments.
+	 *
+	 * We don't need to consider default values or table parameters, and the
+	 * only mode that we need to consider is VARIADIC.
+	 */
+
+	if (proc->pronargs == 0)
+	{
+		append_string_object(stmt, "agg type", "noargs");
+		append_string_object(stmt, "types", "*");
+	}
+	else if (!AGGKIND_IS_ORDERED_SET(agg->aggkind))
+	{
+		int			i;
+		int			nargs;
+		Oid		   *types;
+		char	   *modes;
+		char	  **names;
+
+		nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+		/* only direct arguments in this case */
+		list = NIL;
+		for (i = 0; i < nargs; i++)
+		{
+			list = lappend(list, form_agg_argument(i, modes, names, types));
+		}
+
+		tmp = new_objtree_VA("%{direct:, }s", 1,
+							 "direct", ObjTypeArray, list);
+		append_object_object(stmt, "types", tmp);
+		append_string_object(stmt, "agg type", "plain");
+	}
+	else
+	{
+		int			i;
+		int			nargs;
+		Oid		   *types;
+		char	   *modes;
+		char	  **names;
+
+		nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+		tmp = new_objtree_VA("%{direct:, }s ORDER BY %{aggregated:, }s", 0);
+
+		/* direct arguments ... */
+		list = NIL;
+		for (i = 0; i < agg->aggnumdirectargs; i++)
+		{
+			list = lappend(list, form_agg_argument(i, modes, names, types));
+		}
+		append_array_object(tmp, "direct", list);
+
+		/*
+		 * ... and aggregated arguments.  If the last direct argument is
+		 * VARIADIC, we need to repeat it here rather than searching for more
+		 * arguments.  (See aggr_args production in gram.y for an explanation.)
+		 */
+		if (modes && modes[agg->aggnumdirectargs - 1] == 'v')
+		{
+			list = list_make1(form_agg_argument(agg->aggnumdirectargs - 1,
+												modes, names, types));
+		}
+		else
+		{
+			list = NIL;
+			for (i = agg->aggnumdirectargs; i < nargs; i++)
+			{
+				list = lappend(list, form_agg_argument(i, modes, names, types));
+			}
+		}
+		append_array_object(tmp, "aggregated", list);
+
+		append_object_object(stmt, "types", tmp);
+		append_string_object(stmt, "agg type", "ordered-set");
+	}
+
+	/*
+	 * Now add the definition clause
+	 */
+	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(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(tmp));
+
+	if (agg->aggtransspace != 0)
+	{
+		tmp = new_objtree_VA("SSPACE=%{space}n", 1,
+							 "space", ObjTypeInteger,
+							 agg->aggtransspace);
+		list = lappend(list, new_object_object(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(tmp));
+	}
+
+	if (agg->aggfinalextra)
+	{
+		tmp = new_objtree_VA("FINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(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(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(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(tmp));
+	}
+
+	if (agg->aggmtransspace != 0)
+	{
+		tmp = new_objtree_VA("MSSPACE=%{space}n", 1,
+							 "space", ObjTypeInteger,
+							 agg->aggmtransspace);
+		list = lappend(list, new_object_object(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(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(tmp));
+	}
+
+	if (agg->aggmfinalextra)
+	{
+		tmp = new_objtree_VA("MFINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(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(tmp));
+	}
+
+	if (agg->aggkind == AGGKIND_HYPOTHETICAL)
+	{
+		tmp = new_objtree_VA("HYPOTHETICAL=true", 0);
+		list = lappend(list, new_object_object(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}D", 0);
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname(op->oprnamespace,
+													  NameStr(op->oprname)));
+		list = lappend(list, new_object_object(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;
+	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}D "
+						  "(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;
+	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(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(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(tmp));
+	}
+
+	if (OidIsValid(oprForm->oprcom))
+	{
+		tmp = new_objtree_VA("COMMUTATOR=%{oper}D", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprcom));
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	if (OidIsValid(oprForm->oprnegate))
+	{
+		tmp = new_objtree_VA("NEGATOR=%{oper}D", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprnegate));
+		list = lappend(list, new_object_object(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(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(tmp));
+	}
+
+	if (oprForm->oprcanmerge)
+		list = lappend(list, new_object_object(new_objtree_VA("MERGES", 0)));
+	if (oprForm->oprcanhash)
+		list = lappend(list, new_object_object(new_objtree_VA("HASHES", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(oprTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define,
+							ObjectAddress copied)
+{
+	HeapTuple   tscTup;
+	HeapTuple   tspTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	Form_pg_ts_config tscForm;
+	Form_pg_ts_parser tspForm;
+	List	   *list;
+
+	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 "
+						  "(%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(tscForm->cfgnamespace,
+												  NameStr(tscForm->cfgname)));
+
+	/*
+	 * If tsconfig was copied from another one, define it like so.
+	 */
+	list = NIL;
+	if (copied.objectId != InvalidOid)
+	{
+		tmp = new_objtree_VA("COPY=%{tsconfig}D", 0);
+		append_object_object(tmp, "tsconfig",
+							 new_objtree_for_qualname_id(TSConfigRelationId,
+														 copied.objectId));
+	}
+	else
+	{
+		tmp = new_objtree_VA("PARSER=%{parser}D", 0);
+		append_object_object(tmp, "parser",
+							 new_objtree_for_qualname(tspForm->prsnamespace,
+													  NameStr(tspForm->prsname)));
+	}
+	list = lappend(list, new_object_object(tmp));
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+	ReleaseSysCache(tscTup);
+
+	return stmt;
+}
+
+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(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(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(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(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(tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+
+	return stmt;
+}
+
+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(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(tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tsdTup);
+
+	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(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(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tstTup);
+
+	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(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(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(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(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(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(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(tmp));
+	}
+
+	/* INTERNALLENGTH */
+	if (typForm->typlen == -1)
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=VARIABLE", 0);
+	}
+	else
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}n", 1,
+							 "typlen", ObjTypeInteger, typForm->typlen);
+	}
+	list = lappend(list, new_object_object(tmp));
+
+	/* PASSEDBYVALUE */
+	if (typForm->typbyval)
+		list = lappend(list, new_object_object(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(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(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(tmp));
+
+	/* PREFERRED */
+	if (typForm->typispreferred)
+		list = lappend(list, new_object_object(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(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(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(tmp));
+
+	/* COLLATABLE */
+	if (OidIsValid(typForm->typcollation))
+		list = lappend(list,
+					   new_object_object(new_objtree_VA("COLLATABLE=true", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(typTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt(Oid objectId, Node *parsetree, ObjectAddress secondaryObj)
+{
+	DefineStmt *define = (DefineStmt *) parsetree;
+	ObjTree	   *defStmt;
+
+	switch (define->kind)
+	{
+		case OBJECT_AGGREGATE:
+			defStmt = deparse_DefineStmt_Aggregate(objectId, define);
+			break;
+
+		case OBJECT_COLLATION:
+			defStmt = deparse_DefineStmt_Collation(objectId, define);
+			break;
+
+		case OBJECT_OPERATOR:
+			defStmt = deparse_DefineStmt_Operator(objectId, define);
+			break;
+
+		case OBJECT_TSCONFIGURATION:
+			defStmt = deparse_DefineStmt_TSConfig(objectId, define, secondaryObj);
+			break;
+
+		case OBJECT_TSPARSER:
+			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;
+
+		case OBJECT_TYPE:
+			defStmt = deparse_DefineStmt_Type(objectId, define);
+			break;
+
+		default:
+			elog(ERROR, "unsupported object kind");
+			return NULL;
+	}
+
+	return defStmt;
+}
+
+/*
  * deparse_CreateExtensionStmt
  *		deparse a CreateExtensionStmt
  *
@@ -4211,7 +5083,8 @@ deparse_simple_command(StashedCommand *cmd)
 
 			/* other local objects */
 		case T_DefineStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_DefineStmt(objectId, parsetree,
+										 cmd->d.simple.secondaryObject);
 			break;
 
 		case T_IndexStmt:
-- 
2.1.4

0020-deparse-support-CREATE-CAST.patchtext/x-diff; charset=us-asciiDownload
>From 1fa9cd7da4e4a091843435909ae1390c2532b275 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 10 Feb 2015 14:09:51 -0300
Subject: [PATCH 20/37] deparse: support CREATE CAST

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 83dd59b..46d571a 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4442,6 +4442,81 @@ deparse_CreateConversion(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateCastStmt(Oid objectId, Node *parsetree)
+{
+	CreateCastStmt *node = (CreateCastStmt *) parsetree;
+	Relation	castrel;
+	HeapTuple	castTup;
+	Form_pg_cast castForm;
+	ObjTree	   *createCast;
+	char	   *context;
+
+	castrel = heap_open(CastRelationId, AccessShareLock);
+	castTup = get_catalog_object_by_oid(castrel, objectId);
+	if (!HeapTupleIsValid(castTup))
+		elog(ERROR, "cache lookup failed for cast with OID %u", objectId);
+	castForm = (Form_pg_cast) GETSTRUCT(castTup);
+
+	createCast = new_objtree_VA("CREATE CAST (%{sourcetype}T AS %{targettype}T) %{mechanism}s %{context}s",
+								2, "sourcetype", ObjTypeObject,
+								new_objtree_for_type(castForm->castsource, -1),
+								"targettype", ObjTypeObject,
+								new_objtree_for_type(castForm->casttarget, -1));
+
+	if (node->inout)
+		append_string_object(createCast, "mechanism", "WITH INOUT");
+	else if (node->func == NULL)
+		append_string_object(createCast, "mechanism", "WITHOUT FUNCTION");
+	else
+	{
+		ObjTree	   *tmp;
+		StringInfoData func;
+		HeapTuple	funcTup;
+		Form_pg_proc funcForm;
+		int			i;
+
+		funcTup = SearchSysCache1(PROCOID, castForm->castfunc);
+		funcForm = (Form_pg_proc) GETSTRUCT(funcTup);
+
+		initStringInfo(&func);
+		appendStringInfo(&func, "%s(",
+						quote_qualified_identifier(get_namespace_name(funcForm->pronamespace),
+												   NameStr(funcForm->proname)));
+		for (i = 0; i < funcForm->pronargs; i++)
+			appendStringInfoString(&func,
+								   format_type_be_qualified(funcForm->proargtypes.values[i]));
+		appendStringInfoChar(&func, ')');
+
+		tmp = new_objtree_VA("WITH FUNCTION %{castfunction}s", 1,
+							 "castfunction", ObjTypeString, func.data);
+		append_object_object(createCast, "mechanism", tmp);
+
+		ReleaseSysCache(funcTup);
+	}
+
+	switch (node->context)
+	{
+		case COERCION_IMPLICIT:
+			context = "AS IMPLICIT";
+			break;
+		case COERCION_ASSIGNMENT:
+			context = "AS ASSIGNMENT";
+			break;
+		case COERCION_EXPLICIT:
+			context = "";
+			break;
+		default:
+			elog(ERROR, "invalid coercion code %c", node->context);
+			return NULL;	/* keep compiler quiet */
+	}
+	append_string_object(createCast, "context", context);
+
+	heap_close(castrel, AccessShareLock);
+
+	return createCast;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -5204,7 +5279,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateCastStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateCastStmt(objectId, parsetree);
 			break;
 
 		case T_CreateOpClassStmt:
-- 
2.1.4

0021-deparse-Support-GRANT-REVOKE.patchtext/x-diff; charset=us-asciiDownload
>From 2b3b3ea0d87bb546d2b3e2c18f311873b7eaa693 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 21/37] deparse: Support GRANT/REVOKE

---
 src/backend/catalog/aclchk.c         |  37 ++-----
 src/backend/commands/event_trigger.c | 106 +++++++++++++++++++
 src/backend/tcop/deparse_utility.c   | 193 +++++++++++++++++++++++++++++++++++
 src/include/commands/event_trigger.h |   3 +
 src/include/tcop/deparse_utility.h   |  12 ++-
 src/include/utils/aclchk_internal.h  |  45 ++++++++
 6 files changed, 369 insertions(+), 27 deletions(-)
 create mode 100644 src/include/utils/aclchk_internal.h

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 8e75c27..6eefb9c 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "foreign/foreign.h"
@@ -56,6 +57,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
 #include "utils/acl.h"
+#include "utils/aclchk_internal.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
@@ -65,32 +67,6 @@
 
 
 /*
- * The information about one Grant/Revoke statement, in internal format: object
- * and grantees names have been turned into Oids, the privilege list is an
- * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
- * all_privs is true, 'privileges' will be internally set to the right kind of
- * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
- * InternalGrant struct!)
- *
- * Note: 'all_privs' and 'privileges' represent object-level privileges only.
- * There might also be column-level privilege specifications, which are
- * represented in col_privs (this is a list of untransformed AccessPriv nodes).
- * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
- */
-typedef struct
-{
-	bool		is_grant;
-	GrantObjectType objtype;
-	List	   *objects;
-	bool		all_privs;
-	AclMode		privileges;
-	List	   *col_privs;
-	List	   *grantees;
-	bool		grant_option;
-	DropBehavior behavior;
-} InternalGrant;
-
-/*
  * Internal format used by ALTER DEFAULT PRIVILEGES.
  */
 typedef struct
@@ -605,6 +581,15 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	if (EventTriggerSupportsGrantObjectType(istmt->objtype))
+		EventTriggerStashGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index b3b8627..3c57951 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1773,6 +1773,88 @@ EventTriggerAlterTableEnd(void)
 	currentEventTriggerState->curcmd = NULL;
 }
 
+static const char *
+stringify_grantobjtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+			return "COLUMN";
+		case ACL_OBJECT_RELATION:
+			return "TABLE";
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case ACL_OBJECT_DATABASE:
+			return "DATABASE";
+		case ACL_OBJECT_DOMAIN:
+			return "DOMAIN";
+		case ACL_OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case ACL_OBJECT_FOREIGN_SERVER:
+			return "FOREIGN SERVER";
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTION";
+		case ACL_OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case ACL_OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case ACL_OBJECT_NAMESPACE:
+			return "SCHEMA";
+		case ACL_OBJECT_TABLESPACE:
+			return "TABLESPACE";
+		case ACL_OBJECT_TYPE:
+			return "TYPE";
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
+
+/*
+ * 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;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	/*
+	 * copying the node is moderately challenging ... 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 = creating_extension;
+	stashed->d.grant.type = stringify_grantobjtype(istmt->objtype);
+	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)
 {
@@ -1931,6 +2013,30 @@ 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(cmd->d.grant.istmt->is_grant ?
+											  "GRANT" : "REVOKE");
+			/* object_type */
+			values[i++] = CStringGetTextDatum(cmd->d.grant.type);
+			/* 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 46d571a..b7659ba 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4552,6 +4552,196 @@ deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_GrantStmt(StashedCommand *cmd)
+{
+	InternalGrant *istmt;
+	ObjTree	   *grantStmt;
+	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 = "FOREIGN 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:, }R %%{grant_option}s",
+					   objtype);
+	else
+		fmt = psprintf("REVOKE %%{grant_option}s %%{privileges:, }s ON %s %%{privtarget:, }s "
+					   "FROM %%{grantees:, }R %%{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(tmp));
+	}
+	else
+	{
+		list = NIL;
+
+		if (istmt->privileges & ACL_INSERT)
+			list = lappend(list, new_string_object("INSERT"));
+		if (istmt->privileges & ACL_SELECT)
+			list = lappend(list, new_string_object("SELECT"));
+		if (istmt->privileges & ACL_UPDATE)
+			list = lappend(list, new_string_object("UPDATE"));
+		if (istmt->privileges & ACL_DELETE)
+			list = lappend(list, new_string_object("DELETE"));
+		if (istmt->privileges & ACL_TRUNCATE)
+			list = lappend(list, new_string_object("TRUNCATE"));
+		if (istmt->privileges & ACL_REFERENCES)
+			list = lappend(list, new_string_object("REFERENCES"));
+		if (istmt->privileges & ACL_TRIGGER)
+			list = lappend(list, new_string_object("TRIGGER"));
+		if (istmt->privileges & ACL_EXECUTE)
+			list = lappend(list, new_string_object("EXECUTE"));
+		if (istmt->privileges & ACL_USAGE)
+			list = lappend(list, new_string_object("USAGE"));
+		if (istmt->privileges & ACL_CREATE)
+			list = lappend(list, new_string_object("CREATE"));
+		if (istmt->privileges & ACL_CREATE_TEMP)
+			list = lappend(list, new_string_object("TEMPORARY"));
+		if (istmt->privileges & ACL_CONNECT)
+			list = lappend(list, new_string_object("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(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(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(tmp));
+	}
+	append_array_object(grantStmt, "privtarget", list);
+
+	/* list of grantees */
+	list = NIL;
+	foreach(cell, istmt->grantees)
+	{
+		Oid		grantee = lfirst_oid(cell);
+
+		tmp = new_objtree_for_role_id(grantee);
+		list = lappend(list, new_object_object(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", "");
+	}
+
+	return grantStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -5411,6 +5601,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterTable:
 			tree = deparse_AlterTableStmt(cmd);
 			break;
+		case SCT_Grant:
+			tree = 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 7a449cc..932630f 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_internal.h"
 
 typedef struct EventTriggerData
 {
@@ -72,4 +73,6 @@ extern void EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid,
 								  ObjectAddress address);
 extern void EventTriggerAlterTableEnd(void);
 
+extern void EventTriggerStashGrant(InternalGrant *istmt);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 3b68290..7f70ede 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_internal.h"
+
 
 /*
  * Support for keeping track of a command to deparse.
@@ -26,7 +28,8 @@
 typedef enum StashedCommandType
 {
 	SCT_Simple,
-	SCT_AlterTable
+	SCT_AlterTable,
+	SCT_Grant
 } StashedCommandType;
 
 /*
@@ -61,6 +64,13 @@ typedef struct StashedCommand
 			Oid		classId;
 			List   *subcmds;
 		} alterTable;
+
+		/* GRANT / REVOKE */
+		struct
+		{
+			InternalGrant *istmt;
+			const char *type;
+		} grant;
 	} d;
 } StashedCommand;
 
diff --git a/src/include/utils/aclchk_internal.h b/src/include/utils/aclchk_internal.h
new file mode 100644
index 0000000..bac2728
--- /dev/null
+++ b/src/include/utils/aclchk_internal.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * aclchk_internal.h
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/aclchk_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLCHK_INTERNAL_H
+#define ACLCHK_INTERNAL_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+/*
+ * The information about one Grant/Revoke statement, in internal format: object
+ * and grantees names have been turned into Oids, the privilege list is an
+ * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
+ * all_privs is true, 'privileges' will be internally set to the right kind of
+ * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
+ * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
+ */
+typedef struct
+{
+	bool		is_grant;
+	GrantObjectType objtype;
+	List	   *objects;
+	bool		all_privs;
+	AclMode		privileges;
+	List	   *col_privs;
+	List	   *grantees;
+	bool		grant_option;
+	DropBehavior behavior;
+} InternalGrant;
+
+
+#endif	/* ACLCHK_INTERNAL_H */
-- 
2.1.4

0022-deparse-support-COMMENT-ON-and-SECURITY-LABEL.patchtext/x-diff; charset=us-asciiDownload
>From 5e803d54d854ab28fb24f07ca1b8816b7959c6f0 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:45:04 -0300
Subject: [PATCH 22/37] deparse: support COMMENT ON and SECURITY LABEL

---
 src/backend/tcop/deparse_utility.c | 155 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 153 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index b7659ba..c84ed08 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4406,6 +4406,157 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 	return alterEnum;
 }
 
+/*
+ * Append a NULL-or-quoted-literal clause.  Useful for COMMENT and SECURITY
+ * LABEL.
+ */
+static void
+append_literal_or_null(ObjTree *mainobj, char *elemname, char *value)
+{
+	ObjTree	*top;
+	ObjTree *part;
+
+	top = new_objtree_VA("%{null}s%{literal}s", 0);
+	part = new_objtree_VA("NULL", 1,
+						  "present", ObjTypeBool,
+						  !value);
+	append_object_object(top, "null", part);
+	part = new_objtree_VA("%{value}L", 1,
+						  "present", ObjTypeBool,
+						  !!value);
+	if (value)
+		append_string_object(part, "value", value);
+	append_object_object(top, "literal", part);
+
+	append_object_object(mainobj, elemname, top);
+}
+
+/*
+ * Deparse a CommentStmt when it pertains to a constraint.
+ */
+static ObjTree *
+deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	HeapTuple	constrTup;
+	Form_pg_constraint constrForm;
+	char	   *fmt;
+	ObjectAddress addr;
+
+	Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
+
+	constrTup = SearchSysCache1(CONSTROID, objectId);
+	if (!HeapTupleIsValid(constrTup))
+		elog(ERROR, "cache lookup failed for constraint %u", objectId);
+	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
+
+	if (OidIsValid(constrForm->conrelid))
+		ObjectAddressSet(addr, RelationRelationId, constrForm->conrelid);
+	else
+		ObjectAddressSet(addr, TypeRelationId, constrForm->contypid);
+
+	fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}s",
+				   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	comment = new_objtree_VA(fmt, 0);
+
+	/* Add the comment clause */
+	append_literal_or_null(comment, "comment", node->comment);
+
+	append_string_object(comment, "identity", pstrdup(NameStr(constrForm->conname)));
+
+	append_string_object(comment, "parentobj",
+						 getObjectIdentity(&addr));
+
+	ReleaseSysCache(constrTup);
+
+	return comment;
+}
+
+static ObjTree *
+deparse_CommentStmt(ObjectAddress address, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	char	   *fmt;
+	char	   *identity;
+
+	/*
+	 * Constraints are sufficiently different that it is easier to handle them
+	 * separately.
+	 */
+	if (node->objtype == OBJECT_DOMCONSTRAINT ||
+		node->objtype == OBJECT_TABCONSTRAINT)
+	{
+		Assert(address.classId == ConstraintRelationId);
+		return deparse_CommentOnConstraintSmt(address.objectId, parsetree);
+	}
+
+	fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}s",
+				   stringify_objtype(node->objtype));
+	comment = new_objtree_VA(fmt, 0);
+
+	/* Add the comment clause; can be either NULL or a quoted literal.  */
+	append_literal_or_null(comment, "comment", node->comment);
+
+	/*
+	 * Add the object identity clause.  For zero argument aggregates we need to
+	 * add the (*) bit; in all other cases we can just use getObjectIdentity.
+	 *
+	 * XXX shouldn't we instead fix the object identities for zero-argument
+	 * aggregates?
+	 */
+	if (node->objtype == OBJECT_AGGREGATE)
+	{
+		HeapTuple		procTup;
+		Form_pg_proc	procForm;
+
+		procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(address.objectId));
+		if (!HeapTupleIsValid(procTup))
+			elog(ERROR, "cache lookup failed for procedure %u", address.objectId);
+		procForm = (Form_pg_proc) GETSTRUCT(procTup);
+		if (procForm->pronargs == 0)
+			identity = psprintf("%s(*)",
+								quote_qualified_identifier(get_namespace_name(procForm->pronamespace),
+														   NameStr(procForm->proname)));
+		else
+			identity = getObjectIdentity(&address);
+		ReleaseSysCache(procTup);
+	}
+	else
+		identity = getObjectIdentity(&address);
+
+	append_string_object(comment, "identity", identity);
+
+	return comment;
+}
+
+static ObjTree *
+deparse_SecLabelStmt(ObjectAddress address, Node *parsetree)
+{
+	SecLabelStmt *node = (SecLabelStmt *) parsetree;
+	ObjTree	   *label;
+	char	   *fmt;
+
+	Assert(node->provider);
+
+	fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}s",
+				   stringify_objtype(node->objtype));
+	label = new_objtree_VA(fmt, 0);
+
+	/* Add the label clause; can be either NULL or a quoted literal. */
+	append_literal_or_null(label, "label", node->label);
+
+	/* Add the security provider clause */
+	append_string_object(label, "provider", node->provider);
+
+	/* Add the object identity clause */
+	append_string_object(label, "identity",
+						 getObjectIdentity(&address));
+
+	return label;
+}
+
 static ObjTree *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
@@ -5512,7 +5663,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CommentStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CommentStmt(cmd->d.simple.address, parsetree);
 			break;
 
 		case T_GrantStmt:
@@ -5538,7 +5689,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_SecLabelStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_SecLabelStmt(cmd->d.simple.address, parsetree);
 			break;
 
 		default:
-- 
2.1.4

0001-add-secondaryOid-to-DefineTSConfiguration.patchtext/x-diff; charset=us-asciiDownload
>From 40f727d4a444d0395a7cca0ba17e70d53305391c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 16 Mar 2015 17:51:03 -0300
Subject: [PATCH 01/37] add secondaryOid to DefineTSConfiguration()

In the spirit of a2e35b53c39: we need an extra output argument to know
about a tsconfig referenced in the COPY clause of CREATE TS CONFIG.
---
 src/backend/commands/tsearchcmds.c | 10 +++++++++-
 src/include/commands/defrem.h      |  3 ++-
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 45bafd3..4c404e7 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -964,7 +964,7 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
  * CREATE TEXT SEARCH CONFIGURATION
  */
 ObjectAddress
-DefineTSConfiguration(List *names, List *parameters)
+DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
 {
 	Relation	cfgRel;
 	Relation	mapRel = NULL;
@@ -1013,6 +1013,14 @@ DefineTSConfiguration(List *names, List *parameters)
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("cannot specify both PARSER and COPY options")));
 
+	/* make copied tsconfig available to callers */
+	if (copied && OidIsValid(sourceOid))
+	{
+		ObjectAddressSet(*copied,
+						 TSConfigRelationId,
+						 sourceOid);
+	}
+
 	/*
 	 * Look up source config if given.
 	 */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index a9c6783..595f93f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -101,7 +101,8 @@ extern ObjectAddress AlterTSDictionary(AlterTSDictionaryStmt *stmt);
 extern ObjectAddress DefineTSTemplate(List *names, List *parameters);
 extern void RemoveTSTemplateById(Oid tmplId);
 
-extern ObjectAddress DefineTSConfiguration(List *names, List *parameters);
+extern ObjectAddress DefineTSConfiguration(List *names, List *parameters,
+					  ObjectAddress *copied);
 extern void RemoveTSConfigurationById(Oid cfgId);
 extern ObjectAddress AlterTSConfiguration(AlterTSConfigurationStmt *stmt);
 
-- 
2.1.4

0002-deparse-core-have-ALTER-TABLE-return-address-of-affe.patchtext/x-diff; charset=us-asciiDownload
>From 5008614739aa04c0d9163f576852656932cbcde1 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 02/37] deparse/core: have ALTER TABLE return address of
 affected object

Currently, most if not all CREATE commands return the ObjectAddress of
the created object.  This is useful for event triggers to try to figure
out exactly what happened.  This patch extends this idea a bit further
to cover ALTER TABLE as well: an auxiliary ObjectAddress is also
returned for several subcommands of ALTER TABLE.  This address can later
be used to determine, for instance, which constraint was added by ALTER
TABLE ADD CONSTRAINT, or which parent got dropped from the inherit-from
parents list by NO INHERIT.  This makes it possible to decode with
precision exactly what happened during execution of any ALTER TABLE
command.

There is no user-visible change here; this patch makes the info
available for later.
---
 src/backend/catalog/heap.c          |  40 ++--
 src/backend/catalog/index.c         |   7 +-
 src/backend/catalog/pg_constraint.c |   2 +
 src/backend/commands/tablecmds.c    | 413 ++++++++++++++++++++++++++----------
 src/include/catalog/heap.h          |   3 +-
 src/include/catalog/index.h         |   3 +-
 6 files changed, 342 insertions(+), 126 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e0dbcea..2db1cc3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -97,7 +97,7 @@ static ObjectAddress 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,
@@ -1853,7 +1853,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)
 {
@@ -1958,6 +1958,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	 */
 	InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
 								  RelationGetRelid(rel), attnum, is_internal);
+
+	return attrdefOid;
 }
 
 /*
@@ -1965,8 +1967,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)
@@ -1976,6 +1980,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
+	Oid			constrOid;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2027,7 +2032,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 */
@@ -2058,11 +2063,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
@@ -2074,7 +2083,7 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
 	int			numchecks = 0;
 	ListCell   *lc;
 
-	if (!cooked_constraints)
+	if (cooked_constraints == NIL)
 		return;					/* nothing to do */
 
 	/*
@@ -2091,12 +2100,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:
@@ -2184,6 +2195,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,
@@ -2204,10 +2216,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;
@@ -2227,6 +2240,7 @@ AddRelationNewConstraints(Relation rel,
 	{
 		Constraint *cdef = (Constraint *) lfirst(cell);
 		char	   *ccname;
+		Oid			constrOid;
 
 		if (cdef->contype != CONSTR_CHECK)
 			continue;
@@ -2329,13 +2343,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 f85ed93..322afc2 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1111,7 +1111,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
+ * address.
  *
  * heapRelation: table owning the index (must be suitably locked by caller)
  * indexRelationId: OID of the index
@@ -1128,7 +1129,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
+ObjectAddress
 index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
@@ -1316,6 +1317,8 @@ index_constraint_create(Relation heapRelation,
 		heap_freetuple(indexTuple);
 		heap_close(pg_index, RowExclusiveLock);
 	}
+
+	return referenced;
 }
 
 /*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 77fe918..3c756f8 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 32e19c5..e29b2c0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -282,9 +282,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 ObjectAddress ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 					  bool recurse, bool recursing, LOCKMODE lockmode);
-static void ATExecValidateConstraint(Relation rel, char *constrName,
+static ObjectAddress ATExecValidateConstraint(Relation rel, char *constrName,
 						 bool recurse, bool recursing, LOCKMODE lockmode);
 static int transformColumnNameList(Oid relId, List *colList,
 						int16 *attnums, Oid *atttypids);
@@ -326,47 +326,47 @@ static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
 							  DropBehavior behavior);
 static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 				bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
-				ColumnDef *colDef, bool isOid,
+static ObjectAddress 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 ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode);
-static void ATExecColumnDefault(Relation rel, const char *colName,
+static ObjectAddress 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 ObjectAddress ATExecSetStatistics(Relation rel, const char *colName,
 					Node *newValue, LOCKMODE lockmode);
-static void ATExecSetOptions(Relation rel, const char *colName,
+static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
 				 Node *options, bool isReset, LOCKMODE lockmode);
-static void ATExecSetStorage(Relation rel, const char *colName,
+static ObjectAddress 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);
-static void ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress 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 ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
-static void ATExecAddConstraint(List **wqueue,
+static ObjectAddress ATExecAddConstraint(List **wqueue,
 					AlteredTableInfo *tab, Relation rel,
 					Constraint *newConstraint, bool recurse, bool is_readd,
 					LOCKMODE lockmode);
-static void ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
+static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 						 IndexStmt *stmt, LOCKMODE lockmode);
-static void ATAddCheckConstraint(List **wqueue,
+static ObjectAddress 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 ObjectAddress ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 						  Constraint *fkconstraint, LOCKMODE lockmode);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
 					 DropBehavior behavior,
@@ -377,9 +377,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 ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
+static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
 								List *options, LOCKMODE lockmode);
 static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
 					   LOCKMODE lockmode);
@@ -392,7 +392,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,
+static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName,
 				LOCKMODE lockmode);
 static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
 static bool ATPrepChangePersistence(Relation rel, bool toLogged);
@@ -407,10 +407,10 @@ static void ATExecEnableDisableTrigger(Relation rel, char *trigname,
 static void ATExecEnableDisableRule(Relation rel, char *rulename,
 						char fires_when, LOCKMODE lockmode);
 static void ATPrepAddInherit(Relation child_rel);
-static void ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
-static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
+static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
+static ObjectAddress ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
-static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
+static ObjectAddress ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
 static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
 static void ATExecGenericOptions(Relation rel, List *options);
@@ -623,6 +623,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;
@@ -1742,6 +1743,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;
@@ -3429,78 +3431,90 @@ static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
+	ObjectAddress address = InvalidObjectAddress;
+
 	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);
+			address = 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);
+			address = 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);
+			address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
-			ATExecDropNotNull(rel, cmd->name, lockmode);
+			address = ATExecDropNotNull(rel, cmd->name, lockmode);
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
-			ATExecSetNotNull(tab, rel, cmd->name, lockmode);
+			address = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
 			break;
 		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
-			ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
+			address = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
-			ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
+			address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
 			break;
 		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
-			ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
+			address = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
 			break;
 		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
-			ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
+			address = 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);
+			address = ATExecDropColumn(wqueue, rel, cmd->name,
+									   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);
+			address = ATExecDropColumn(wqueue, rel, cmd->name,
+									   cmd->behavior, true, false,
+									   cmd->missing_ok, lockmode);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
-			ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false, lockmode);
+			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
+									 lockmode);
 			break;
 		case AT_ReAddIndex:		/* ADD INDEX */
-			ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, lockmode);
+			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
+									lockmode);
 			break;
 		case AT_AddConstraint:	/* ADD CONSTRAINT */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								false, false, lockmode);
+			address =
+				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);
+			address =
+				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);
+			address =
+				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);
+			address = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
+											   lockmode);
 			break;
 		case AT_AlterConstraint:		/* ALTER CONSTRAINT */
-			ATExecAlterConstraint(rel, cmd, false, false, lockmode);
+			address = ATExecAlterConstraint(rel, cmd, false, false, lockmode);
 			break;
 		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
-			ATExecValidateConstraint(rel, cmd->name, false, false, lockmode);
+			address = ATExecValidateConstraint(rel, cmd->name, false, false,
+											   lockmode);
 			break;
 		case AT_ValidateConstraintRecurse:		/* VALIDATE CONSTRAINT with
 												 * recursion */
-			ATExecValidateConstraint(rel, cmd->name, true, false, lockmode);
+			address = ATExecValidateConstraint(rel, cmd->name, true, false,
+											   lockmode);
 			break;
 		case AT_DropConstraint:	/* DROP CONSTRAINT */
 			ATExecDropConstraint(rel, cmd->name, cmd->behavior,
@@ -3513,10 +3527,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 								 cmd->missing_ok, lockmode);
 			break;
 		case AT_AlterColumnType:		/* ALTER COLUMN TYPE */
-			ATExecAlterColumnType(tab, rel, cmd, lockmode);
+			address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
 			break;
 		case AT_AlterColumnGenericOptions:		/* ALTER COLUMN OPTIONS */
-			ATExecAlterColumnGenericOptions(rel, cmd->name, (List *) cmd->def, lockmode);
+			address =
+				ATExecAlterColumnGenericOptions(rel, cmd->name,
+												(List *) cmd->def, lockmode);
 			break;
 		case AT_ChangeOwner:	/* ALTER OWNER */
 			ATExecChangeOwner(RelationGetRelid(rel),
@@ -3524,7 +3540,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							  false, lockmode);
 			break;
 		case AT_ClusterOn:		/* CLUSTER ON */
-			ATExecClusterOn(rel, cmd->name, lockmode);
+			address = ATExecClusterOn(rel, cmd->name, lockmode);
 			break;
 		case AT_DropCluster:	/* SET WITHOUT CLUSTER */
 			ATExecDropCluster(rel, lockmode);
@@ -3535,14 +3551,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_AddOids:		/* SET WITH OIDS */
 			/* Use the ADD COLUMN code, unless prep decided to do nothing */
 			if (cmd->def != NULL)
-				ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
-								true, false, false, lockmode);
+				address =
+					ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+									true, false, false, lockmode);
 			break;
 		case AT_AddOidsRecurse:	/* SET WITH OIDS */
 			/* Use the ADD COLUMN code, unless prep decided to do nothing */
 			if (cmd->def != NULL)
-				ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
-								true, true, false, lockmode);
+				address =
+					ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+									true, true, false, lockmode);
 			break;
 		case AT_DropOids:		/* SET WITHOUT OIDS */
 
@@ -3613,13 +3631,13 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 
 		case AT_AddInherit:
-			ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
+			address = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
 			break;
 		case AT_DropInherit:
-			ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
+			address = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
 			break;
 		case AT_AddOf:
-			ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
+			address = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
 			break;
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
@@ -4626,7 +4644,11 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 		cmd->subtype = AT_AddColumnRecurse;
 }
 
-static void
+/*
+ * Add a column to a table; this handles the AT_AddOids cases as well.  The
+ * return value is the address of the new column in the parent relation.
+ */
+static ObjectAddress
 ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				ColumnDef *colDef, bool isOid,
 				bool recurse, bool recursing, LOCKMODE lockmode)
@@ -4647,6 +4669,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	List	   *children;
 	ListCell   *child;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -4711,7 +4734,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					  colDef->colname, RelationGetRelationName(rel))));
 
 			heap_close(attrdesc, RowExclusiveLock);
-			return;
+			return InvalidObjectAddress;
 		}
 	}
 
@@ -4951,12 +4974,15 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		/* Find or create work queue entry for this table */
 		childtab = ATGetQueueEntry(wqueue, childrel);
 
-		/* Recurse to child */
+		/* Recurse to child; return value is ignored */
 		ATExecAddColumn(wqueue, childtab, childrel,
 						colDef, isOid, recurse, true, lockmode);
 
 		heap_close(childrel, NoLock);
 	}
+
+	ObjectAddressSubSet(address, RelationRelationId, myrelid, newattnum);
+	return address;
 }
 
 /*
@@ -5070,8 +5096,11 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
 
 /*
  * ALTER TABLE ALTER COLUMN DROP NOT NULL
+ *
+ * Return the address of the modified column.  If the column was already
+ * nullable, InvalidObjectAddress is returned.
  */
-static void
+static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
 	HeapTuple	tuple;
@@ -5079,6 +5108,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	Relation	attr_rel;
 	List	   *indexoidlist;
 	ListCell   *indexoidscan;
+	ObjectAddress address;
 
 	/*
 	 * lookup the attribute
@@ -5156,24 +5186,35 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 		/* keep the system catalog indexes current */
 		CatalogUpdateIndexes(attr_rel, tuple);
+
+		ObjectAddressSubSet(address, RelationRelationId,
+							RelationGetRelid(rel), attnum);
 	}
+	else
+		address = InvalidObjectAddress;
 
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel), attnum);
 
 	heap_close(attr_rel, RowExclusiveLock);
+
+	return address;
 }
 
 /*
  * ALTER TABLE ALTER COLUMN SET NOT NULL
+ *
+ * Return the address of the modified column.  If the column was already NOT
+ * NULL, InvalidObjectAddress is returned.
  */
-static void
+static ObjectAddress
 ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode)
 {
 	HeapTuple	tuple;
 	AttrNumber	attnum;
 	Relation	attr_rel;
+	ObjectAddress address;
 
 	/*
 	 * lookup the attribute
@@ -5211,22 +5252,32 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 
 		/* Tell Phase 3 it needs to test the constraint */
 		tab->new_notnull = true;
+
+		ObjectAddressSubSet(address, RelationRelationId,
+							RelationGetRelid(rel), attnum);
 	}
+	else
+		address = InvalidObjectAddress;
 
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel), attnum);
 
 	heap_close(attr_rel, RowExclusiveLock);
+
+	return address;
 }
 
 /*
  * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
+ *
+ * Return the address of the affected column.
  */
-static void
+static ObjectAddress
 ATExecColumnDefault(Relation rel, const char *colName,
 					Node *newDefault, LOCKMODE lockmode)
 {
 	AttrNumber	attnum;
+	ObjectAddress address;
 
 	/*
 	 * get the number of the attribute
@@ -5273,6 +5324,10 @@ ATExecColumnDefault(Relation rel, const char *colName,
 		AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
 								  false, true, false);
 	}
+
+	ObjectAddressSubSet(address, RelationRelationId,
+						RelationGetRelid(rel), attnum);
+	return address;
 }
 
 /*
@@ -5302,13 +5357,15 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 					   RelationGetRelationName(rel));
 }
 
-static void
+static ObjectAddress
 ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
 {
 	int			newtarget;
 	Relation	attrelation;
 	HeapTuple	tuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
+	ObjectAddress address;
 
 	Assert(IsA(newValue, Integer));
 	newtarget = intVal(newValue);
@@ -5343,7 +5400,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\"",
@@ -5359,12 +5417,16 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel),
 							  attrtuple->attnum);
+	ObjectAddressSubSet(address, RelationRelationId,
+						RelationGetRelid(rel), attnum);
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return address;
 }
 
-static void
+static ObjectAddress
 ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 bool isReset, LOCKMODE lockmode)
 {
@@ -5372,9 +5434,11 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	HeapTuple	tuple,
 				newtuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 	Datum		datum,
 				newOptions;
 	bool		isnull;
+	ObjectAddress address;
 	Datum		repl_val[Natts_pg_attribute];
 	bool		repl_null[Natts_pg_attribute];
 	bool		repl_repl[Natts_pg_attribute];
@@ -5390,7 +5454,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\"",
@@ -5424,17 +5489,22 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel),
 							  attrtuple->attnum);
+	ObjectAddressSubSet(address, RelationRelationId,
+						RelationGetRelid(rel), attnum);
+
 	heap_freetuple(newtuple);
 
 	ReleaseSysCache(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return address;
 }
 
 /*
  * ALTER TABLE ALTER COLUMN SET STORAGE
  */
-static void
+static ObjectAddress
 ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
 {
 	char	   *storagemode;
@@ -5442,6 +5512,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	Relation	attrelation;
 	HeapTuple	tuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
+	ObjectAddress address;
 
 	Assert(IsA(newValue, String));
 	storagemode = strVal(newValue);
@@ -5474,7 +5546,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\"",
@@ -5504,6 +5577,10 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	ObjectAddressSubSet(address, RelationRelationId,
+						RelationGetRelid(rel), attnum);
+	return address;
 }
 
 
@@ -5532,7 +5609,7 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 		cmd->subtype = AT_DropColumnRecurse;
 }
 
-static void
+static ObjectAddress
 ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
@@ -5566,7 +5643,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			ereport(NOTICE,
 					(errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
 							colName, RelationGetRelationName(rel))));
-			return;
+			return InvalidObjectAddress;
 		}
 	}
 	targetatt = (Form_pg_attribute) GETSTRUCT(tuple);
@@ -5720,6 +5797,8 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		/* Tell Phase 3 to physically remove the OID column */
 		tab->rewrite |= AT_REWRITE_ALTER_OID;
 	}
+
+	return object;
 }
 
 /*
@@ -5728,8 +5807,10 @@ 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 address of the new index.
  */
-static void
+static ObjectAddress
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
@@ -5772,12 +5853,16 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 		RelationPreserveStorage(irel->rd_node, true);
 		index_close(irel, NoLock);
 	}
+
+	return address;
 }
 
 /*
  * ALTER TABLE ADD CONSTRAINT USING INDEX
+ *
+ * Returns the address of the new constraint.
  */
-static void
+static ObjectAddress
 ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 						 IndexStmt *stmt, LOCKMODE lockmode)
 {
@@ -5787,6 +5872,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	IndexInfo  *indexInfo;
 	char	   *constraintName;
 	char		constraintType;
+	ObjectAddress address;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(OidIsValid(index_oid));
@@ -5831,32 +5917,43 @@ 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 */
+	address = 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 address;
 }
 
 /*
  * ALTER TABLE ADD CONSTRAINT
+ *
+ * Return value is the address of the new constraint; if no constraint was
+ * added, InvalidObjectAddress is returned.
  */
-static void
+static ObjectAddress
 ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					Constraint *newConstraint, bool recurse, bool is_readd,
 					LOCKMODE lockmode)
 {
+	ObjectAddress address = InvalidObjectAddress;
+
 	Assert(IsA(newConstraint, Constraint));
 
+	ObjectAddressSet(address,
+					 ConstraintRelationId,
+					 InvalidOid);
+
 	/*
 	 * Currently, we only expect to see CONSTR_CHECK and CONSTR_FOREIGN nodes
 	 * arriving here (see the preprocessing done in parse_utilcmd.c).  Use a
@@ -5865,9 +5962,10 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	switch (newConstraint->contype)
 	{
 		case CONSTR_CHECK:
-			ATAddCheckConstraint(wqueue, tab, rel,
-								 newConstraint, recurse, false, is_readd,
-								 lockmode);
+			address =
+				ATAddCheckConstraint(wqueue, tab, rel,
+									 newConstraint, recurse, false, is_readd,
+									 lockmode);
 			break;
 
 		case CONSTR_FOREIGN:
@@ -5898,17 +5996,22 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 										 RelationGetNamespace(rel),
 										 NIL);
 
-			ATAddForeignKeyConstraint(tab, rel, newConstraint, lockmode);
+			address = ATAddForeignKeyConstraint(tab, rel, newConstraint,
+												lockmode);
 			break;
 
 		default:
 			elog(ERROR, "unrecognized constraint type: %d",
 				 (int) newConstraint->contype);
 	}
+
+	return address;
 }
 
 /*
- * Add a check constraint to a single table and its children
+ * Add a check constraint to a single table and its children.  Returns the
+ * address of the constraint added to the parent relation, if one gets added,
+ * or InvalidObjectAddress otherwise.
  *
  * Subroutine for ATExecAddConstraint.
  *
@@ -5927,7 +6030,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 ObjectAddress
 ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					 Constraint *constr, bool recurse, bool recursing,
 					 bool is_readd, LOCKMODE lockmode)
@@ -5936,6 +6039,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	ListCell   *lcon;
 	List	   *children;
 	ListCell   *child;
+	ObjectAddress address = InvalidObjectAddress;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5957,6 +6061,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 										!recursing,		/* is_local */
 										is_readd);		/* is_internal */
 
+	/* we don't expect more than one constraint here */
+	Assert(list_length(newcons) <= 1);
+
 	/* Add each to-be-validated constraint to Phase 3's queue */
 	foreach(lcon, newcons)
 	{
@@ -5978,6 +6085,8 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		/* Save the actually assigned name if it was defaulted */
 		if (constr->conname == NULL)
 			constr->conname = ccon->name;
+
+		ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
 	}
 
 	/* At this point we must have a locked-down name to use */
@@ -5993,7 +6102,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * incorrect value for coninhcount.
 	 */
 	if (newcons == NIL)
-		return;
+		return address;
 
 	/*
 	 * If adding a NO INHERIT constraint, no need to find our children.
@@ -6001,7 +6110,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * handled at higher levels).
 	 */
 	if (constr->is_no_inherit || is_readd)
-		return;
+		return address;
 
 	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
@@ -6039,16 +6148,19 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 		heap_close(childrel, NoLock);
 	}
+
+	return address;
 }
 
 /*
- * Add a foreign-key constraint to a single table
+ * Add a foreign-key constraint to a single table; return the new constraint's
+ * address.
  *
  * 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 ObjectAddress
 ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 						  Constraint *fkconstraint, LOCKMODE lockmode)
 {
@@ -6067,6 +6179,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	Oid			indexOid;
 	Oid			constrOid;
 	bool		old_check_ok;
+	ObjectAddress address;
 	ListCell   *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
 
 	/*
@@ -6414,6 +6527,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 									  0,		/* inhcount */
 									  true,		/* isnoinherit */
 									  false);	/* is_internal */
+	ObjectAddressSet(address, ConstraintRelationId, constrOid);
 
 	/*
 	 * Create the triggers that will enforce the constraint.
@@ -6447,6 +6561,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Close pk table, but keep lock until we've committed.
 	 */
 	heap_close(pkrel, NoLock);
+
+	return address;
 }
 
 /*
@@ -6458,8 +6574,11 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  * Foreign keys do not inherit, so we purposely ignore the
  * recursion bit here, but we keep the API the same for when
  * other constraint types are supported.
+ *
+ * If the constraint is modified, returns its address; otherwise, return
+ * InvalidObjectAddress.
  */
-static void
+static ObjectAddress
 ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 					  bool recurse, bool recursing, LOCKMODE lockmode)
 {
@@ -6470,6 +6589,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 	Form_pg_constraint currcon = NULL;
 	Constraint *cmdcon = NULL;
 	bool		found = false;
+	ObjectAddress address;
 
 	Assert(IsA(cmd->def, Constraint));
 	cmdcon = (Constraint *) cmd->def;
@@ -6571,11 +6691,18 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 		 * Invalidate relcache so that others see the new attributes.
 		 */
 		CacheInvalidateRelcache(rel);
+
+		ObjectAddressSet(address, ConstraintRelationId,
+						 HeapTupleGetOid(contuple));
 	}
+	else
+		address = InvalidObjectAddress;
 
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return address;
 }
 
 /*
@@ -6585,8 +6712,11 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
  * there's no good way to skip recursing when handling foreign keys: there is
  * no need to lock children in that case, yet we wouldn't be able to avoid
  * doing so at that level.
+ *
+ * Return value is the address of the validated constraint.  If the constraint
+ * was already validated, InvalidObjectAddress is returned.
  */
-static void
+static ObjectAddress
 ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 						 bool recursing, LOCKMODE lockmode)
 {
@@ -6596,6 +6726,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	HeapTuple	tuple;
 	Form_pg_constraint con = NULL;
 	bool		found = false;
+	ObjectAddress address;
 
 	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -6639,7 +6770,6 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 		if (con->contype == CONSTRAINT_FOREIGN)
 		{
-			Oid			conid = HeapTupleGetOid(tuple);
 			Relation	refrel;
 
 			/*
@@ -6654,7 +6784,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 conid);
+										 HeapTupleGetOid(tuple));
 			heap_close(refrel, NoLock);
 
 			/*
@@ -6730,11 +6860,18 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 								  HeapTupleGetOid(tuple), 0);
 
 		heap_freetuple(copyTuple);
+
+		ObjectAddressSet(address, ConstraintRelationId,
+						 HeapTupleGetOid(tuple));
 	}
+	else
+		address = InvalidObjectAddress;
 
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return address;
 }
 
 
@@ -7424,6 +7561,10 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
  * ALTER TABLE DROP CONSTRAINT
  *
  * Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism.
+ *
+ * In constrast to most other ALTER TABLE subcommand executions, this one does
+ * not return the address of the affected constraint; that would be pointless,
+ * because the catalog object no longer exists.
  */
 static void
 ATExecDropConstraint(Relation rel, const char *constrName,
@@ -7825,7 +7966,12 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
 	}
 }
 
-static void
+/*
+ * ALTER COLUMN .. SET DATA TYPE
+ *
+ * Return the address of the modified column.
+ */
+static ObjectAddress
 ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
@@ -7846,6 +7992,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 	ScanKeyData key[3];
 	SysScanDesc scan;
 	HeapTuple	depTup;
+	ObjectAddress address;
 
 	attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
 
@@ -8217,11 +8364,16 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 		StoreAttrDefault(rel, attnum, defaultexpr, true);
 	}
 
+	ObjectAddressSubSet(address, RelationRelationId,
+						RelationGetRelid(rel), attnum);
+
 	/* Cleanup */
 	heap_freetuple(heapTup);
+
+	return address;
 }
 
-static void
+static ObjectAddress
 ATExecAlterColumnGenericOptions(Relation rel,
 								const char *colName,
 								List *options,
@@ -8240,9 +8392,11 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	Datum		datum;
 	Form_pg_foreign_table fttableform;
 	Form_pg_attribute atttableform;
+	AttrNumber	attnum;
+	ObjectAddress address;
 
 	if (options == NIL)
-		return;
+		return InvalidObjectAddress;
 
 	/* First, determine FDW validator associated to the foreign table. */
 	ftrel = heap_open(ForeignTableRelationId, AccessShareLock);
@@ -8269,7 +8423,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)));
@@ -8312,12 +8467,16 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel),
 							  atttableform->attnum);
+	ObjectAddressSubSet(address, RelationRelationId,
+						RelationGetRelid(rel), attnum);
 
 	ReleaseSysCache(tuple);
 
 	heap_close(attrel, RowExclusiveLock);
 
 	heap_freetuple(newtuple);
+
+	return address;
 }
 
 /*
@@ -8958,11 +9117,14 @@ change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lock
  * ALTER TABLE CLUSTER ON
  *
  * The only thing we have to do is to change the indisclustered bits.
+ *
+ * Return the address of the new clustering index.
  */
-static void
+static ObjectAddress
 ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 {
 	Oid			indexOid;
+	ObjectAddress address;
 
 	indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace);
 
@@ -8977,6 +9139,11 @@ ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 
 	/* And do the work */
 	mark_index_clustered(rel, indexOid, false);
+
+	ObjectAddressSet(address,
+					 RelationRelationId, indexOid);
+
+	return address;
 }
 
 /*
@@ -9677,16 +9844,21 @@ ATPrepAddInherit(Relation child_rel)
 				 errmsg("cannot change inheritance of typed table")));
 }
 
-static void
+/*
+ * Return the address of the new parent
+ */
+static ObjectAddress
 ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 {
 	Relation	parent_rel,
 				catalogRelation;
+	Oid			parent_oid;
 	SysScanDesc scan;
 	ScanKeyData key;
 	HeapTuple	inheritsTuple;
 	int32		inhseqno;
 	List	   *children;
+	ObjectAddress address;
 
 	/*
 	 * A self-exclusive lock is needed here.  See the similar case in
@@ -9801,11 +9973,18 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 							 inhseqno + 1,
 							 catalogRelation);
 
+	parent_oid = RelationGetRelid(parent_rel);
+
 	/* Now we're done with pg_inherits */
 	heap_close(catalogRelation, RowExclusiveLock);
 
 	/* keep our lock on the parent relation until commit */
 	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address,
+					 RelationRelationId, parent_oid);
+
+	return address;
 }
 
 /*
@@ -10073,11 +10252,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
  *
  * coninhcount and conislocal for inherited constraints are adjusted in
  * exactly the same way.
+ *
+ * Return value is the OID of the relation that is no longer parent.
  */
-static void
+static ObjectAddress
 ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 {
 	Relation	parent_rel;
+	Oid			parent_oid;
 	Relation	catalogRelation;
 	SysScanDesc scan;
 	ScanKeyData key[3];
@@ -10086,6 +10268,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
+	ObjectAddress address;
 
 	/*
 	 * AccessShareLock on the parent is probably enough, seeing that DROP
@@ -10246,6 +10429,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 		}
 	}
 
+	parent_oid = RelationGetRelid(parent_rel);
+
 	systable_endscan(scan);
 	heap_close(catalogRelation, RowExclusiveLock);
 
@@ -10264,6 +10449,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 
 	/* keep our lock on the parent relation until commit */
 	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId, parent_oid);
+
+	return address;
 }
 
 /*
@@ -10321,8 +10510,10 @@ drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid)
  * TABLE OF.  All attname, atttypid, atttypmod and attcollation must match.  The
  * subject table must not have inheritance parents.  These restrictions ensure
  * that you cannot create a configuration impossible with CREATE TABLE OF alone.
+ *
+ * The address of the type is returned.
  */
-static void
+static ObjectAddress
 ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 {
 	Oid			relid = RelationGetRelid(rel);
@@ -10451,6 +10642,8 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	heap_close(relationRelation, RowExclusiveLock);
 
 	ReleaseSysCache(typetuple);
+
+	return typeobj;
 }
 
 /*
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 573b2de..9a8fa1e 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -28,6 +28,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 */
@@ -101,7 +102,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 e7cc7a0..a04def9 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -14,6 +14,7 @@
 #ifndef INDEX_H
 #define INDEX_H
 
+#include "catalog/objectaddress.h"
 #include "nodes/execnodes.h"
 
 
@@ -63,7 +64,7 @@ extern Oid index_create(Relation heapRelation,
 			 bool is_internal,
 			 bool if_not_exists);
 
-extern void index_constraint_create(Relation heapRelation,
+extern ObjectAddress index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
 						const char *constraintName,
-- 
2.1.4

0003-deparse-core-transform-expr-during-parse-analysis-in.patchtext/x-diff; charset=us-asciiDownload
>From 511977144dfe5db1208d2e57ac03b95b7e9a1e6e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 24 Mar 2015 10:24:11 -0300
Subject: [PATCH 03/37] deparse/core: transform expr during parse analysis in
 ALTER COLUMN TYPE

... instead of doing it during the execution phase.
---
 src/backend/commands/tablecmds.c   | 39 +++++++++----------------------------
 src/backend/parser/parse_utilcmd.c | 40 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+), 30 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e29b2c0..47d5d3b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -7770,7 +7770,7 @@ ATPrepAlterColumnType(List **wqueue,
 	char	   *colName = cmd->name;
 	ColumnDef  *def = (ColumnDef *) cmd->def;
 	TypeName   *typeName = def->typeName;
-	Node	   *transform = def->raw_default;
+	Node	   *transform;
 	HeapTuple	tuple;
 	Form_pg_attribute attTup;
 	AttrNumber	attnum;
@@ -7829,32 +7829,16 @@ ATPrepAlterColumnType(List **wqueue,
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
-		 * type. If a USING option was given, transform and use that
-		 * expression, else just take the old value and try to coerce it.  We
-		 * do this first so that type incompatibility can be detected before
-		 * we waste effort, and because we need the expression to be parsed
-		 * against the original table row type.
+		 * type. If a USING option was given, use the expression as transformed
+		 * by transformAlterTableStmt, else just take the old value and try to
+		 * coerce it.  We do this first so that type incompatibility can be
+		 * detected before we waste effort, and because we need the expression
+		 * to be parsed against the original table row type.
 		 */
-		if (transform)
+		if (def->cooked_default)
 		{
-			RangeTblEntry *rte;
-
-			/* Expression must be able to access vars of old table */
-			rte = addRangeTableEntryForRelation(pstate,
-												rel,
-												NULL,
-												false,
-												true);
-			addRTEtoQuery(pstate, rte, false, true, true);
-
-			transform = transformExpr(pstate, transform,
-									  EXPR_KIND_ALTER_COL_TRANSFORM);
-
-			/* It can't return a set */
-			if (expression_returns_set(transform))
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-					  errmsg("transform expression must not return a set")));
+			/* transformAlterTableStmt already did the work */
+			transform = def->cooked_default;
 		}
 		else
 		{
@@ -7895,11 +7879,6 @@ ATPrepAlterColumnType(List **wqueue,
 		if (ATColumnChangeRequiresRewrite(transform, attnum))
 			tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
 	}
-	else if (transform)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
-						RelationGetRelationName(rel))));
 
 	if (tab->relkind == RELKIND_COMPOSITE_TYPE ||
 		tab->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1bbed95..0fa6285 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2372,6 +2372,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	List	   *newcmds = NIL;
 	bool		skipValidation = true;
 	AlterTableCmd *newcmd;
+	RangeTblEntry *rte;
 
 	/*
 	 * We must not scribble on the passed-in AlterTableStmt, so copy it. (This
@@ -2411,6 +2412,13 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 
+	rte = addRangeTableEntryForRelation(pstate,
+										rel,
+										NULL,
+										false,
+										true);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
 	/*
 	 * The only subtypes that currently require parse transformation handling
 	 * are ADD COLUMN and ADD CONSTRAINT.  These largely re-use code from
@@ -2446,6 +2454,38 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_AlterColumnType:
+				{
+					ColumnDef *def = (ColumnDef *) cmd->def;
+
+					/*
+					 * For ALTER COLUMN TYPE, transform the USING clause if
+					 * one was specified.
+					 */
+					if (def->raw_default)
+					{
+						/* USING is only valid for plain tables */
+						if (rel->rd_rel->relkind != RELKIND_RELATION)
+							ereport(ERROR,
+									(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+									 errmsg("\"%s\" is not a table",
+											RelationGetRelationName(rel))));
+
+						def->cooked_default =
+							transformExpr(pstate, def->raw_default,
+										  EXPR_KIND_ALTER_COL_TRANSFORM);
+
+						/* it can't return a set */
+						if (expression_returns_set(def->cooked_default))
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("transform expression must not return a set")));
+					}
+
+					newcmds = lappend(newcmds, cmd);
+					break;
+				}
+
 			case AT_AddConstraint:
 
 				/*
-- 
2.1.4

0004-deparse-core-report-temp-objs-in-pg_event_trigger_dr.patchtext/x-diff; charset=us-asciiDownload
>From 294ecc1dc63f781c4e2925841d2bd7cfdd067ddb Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 20 Mar 2015 15:35:49 -0300
Subject: [PATCH 04/37] deparse/core: report temp objs in
 pg_event_trigger_dropped_objects

Not reporting them means that a temp object dropped and recreated in the
middle of a test cannot properly be duplicated.

XXX this needs docs
---
 src/backend/catalog/objectaddress.c  | 22 +++++++++++-----------
 src/backend/commands/event_trigger.c | 35 +++++++++++++++++++++++++++--------
 src/backend/utils/adt/regproc.c      |  2 +-
 src/backend/utils/cache/lsyscache.c  | 16 ++++++++++++++++
 src/include/catalog/pg_proc.h        |  3 +--
 src/include/utils/lsyscache.h        |  1 +
 6 files changed, 57 insertions(+), 22 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index e82a448..9d63bd0 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3687,7 +3687,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
-				schema = get_namespace_name(coll->collnamespace);
+				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
 									   quote_qualified_identifier(schema,
 												   NameStr(coll->collname)));
@@ -3751,7 +3751,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					elog(ERROR, "cache lookup failed for conversion %u",
 						 object->objectId);
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
-				schema = get_namespace_name(conForm->connamespace);
+				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
 								quote_qualified_identifier(schema,
 														   NameStr(conForm->conname)));
@@ -3849,7 +3849,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					elog(ERROR, "cache lookup failed for opclass %u",
 						 object->objectId);
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
-				schema = get_namespace_name(opcForm->opcnamespace);
+				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
 				amTup = SearchSysCache1(AMOID,
 										ObjectIdGetDatum(opcForm->opcmethod));
@@ -4066,7 +4066,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *nspname;
 
-				nspname = get_namespace_name(object->objectId);
+				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
 					elog(ERROR, "cache lookup failed for namespace %u",
 						 object->objectId);
@@ -4089,7 +4089,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					elog(ERROR, "cache lookup failed for text search parser %u",
 						 object->objectId);
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
-				schema = get_namespace_name(formParser->prsnamespace);
+				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
 									   quote_qualified_identifier(schema,
 											  NameStr(formParser->prsname)));
@@ -4112,7 +4112,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					elog(ERROR, "cache lookup failed for text search dictionary %u",
 						 object->objectId);
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
-				schema = get_namespace_name(formDict->dictnamespace);
+				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
 									   quote_qualified_identifier(schema,
 											   NameStr(formDict->dictname)));
@@ -4135,7 +4135,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					elog(ERROR, "cache lookup failed for text search template %u",
 						 object->objectId);
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
-				schema = get_namespace_name(formTmpl->tmplnamespace);
+				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
 									   quote_qualified_identifier(schema,
 											   NameStr(formTmpl->tmplname)));
@@ -4158,7 +4158,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					elog(ERROR, "cache lookup failed for text search configuration %u",
 						 object->objectId);
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
-				schema = get_namespace_name(formCfg->cfgnamespace);
+				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
 									   quote_qualified_identifier(schema,
 												 NameStr(formCfg->cfgname)));
@@ -4305,7 +4305,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (OidIsValid(defacl->defaclnamespace))
 				{
-					schema = get_namespace_name(defacl->defaclnamespace);
+					schema = get_namespace_name_or_temp(defacl->defaclnamespace);
 					appendStringInfo(&buffer,
 									 " in schema %s",
 									 quote_identifier(schema));
@@ -4421,7 +4421,7 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **objname)
 			 opfForm->opfmethod);
 	amForm = (Form_pg_am) GETSTRUCT(amTup);
 
-	schema = get_namespace_name(opfForm->opfnamespace);
+	schema = get_namespace_name_or_temp(opfForm->opfnamespace);
 	appendStringInfo(buffer, "%s USING %s",
 					 quote_qualified_identifier(schema,
 												NameStr(opfForm->opfname)),
@@ -4453,7 +4453,7 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **objname)
 		elog(ERROR, "cache lookup failed for relation %u", relid);
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
-	schema = get_namespace_name(relForm->relnamespace);
+	schema = get_namespace_name_or_temp(relForm->relnamespace);
 	appendStringInfoString(buffer,
 						   quote_qualified_identifier(schema,
 												 NameStr(relForm->relname)));
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 4bcc327..de6f52f 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -121,6 +121,7 @@ typedef struct SQLDropObject
 	List	   *addrargs;
 	bool		original;
 	bool		normal;
+	bool		istemp;
 	slist_node	next;
 } SQLDropObject;
 
@@ -1294,9 +1295,10 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	Assert(EventTriggerSupportsObjectClass(getObjectClass(object)));
 
-	/* don't report temp schemas */
+	/* don't report temp schemas except my own */
 	if (object->classId == NamespaceRelationId &&
-		isAnyTempNamespace(object->objectId))
+		(isAnyTempNamespace(object->objectId) &&
+		 !isTempNamespace(object->objectId)))
 		return;
 
 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
@@ -1336,16 +1338,24 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 					Oid			namespaceId;
 
 					namespaceId = DatumGetObjectId(datum);
-					/* Don't report objects in temp namespaces */
-					if (isAnyTempNamespace(namespaceId))
+					/* temp objects are only reported if they are my own */
+					if (isTempNamespace(namespaceId))
+					{
+						obj->schemaname = "pg_temp";
+						obj->istemp = true;
+					}
+					else if (isAnyTempNamespace(namespaceId))
 					{
 						pfree(obj);
 						heap_close(catalog, AccessShareLock);
 						MemoryContextSwitchTo(oldcxt);
 						return;
 					}
-
-					obj->schemaname = get_namespace_name(namespaceId);
+					else
+					{
+						obj->schemaname = get_namespace_name(namespaceId);
+						obj->istemp = false;
+					}
 				}
 			}
 
@@ -1365,6 +1375,12 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 		heap_close(catalog, AccessShareLock);
 	}
+	else
+	{
+		if (object->classId == NamespaceRelationId &&
+			isTempNamespace(object->objectId))
+			obj->istemp = true;
+	}
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
@@ -1433,8 +1449,8 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
 	{
 		SQLDropObject *obj;
 		int			i = 0;
-		Datum		values[11];
-		bool		nulls[11];
+		Datum		values[12];
+		bool		nulls[12];
 
 		obj = slist_container(SQLDropObject, next, iter.cur);
 
@@ -1456,6 +1472,9 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
 		/* normal */
 		values[i++] = BoolGetDatum(obj->normal);
 
+		/* is_temp */
+		values[i++] = BoolGetDatum(obj->istemp);
+
 		/* object_type */
 		values[i++] = CStringGetTextDatum(obj->objecttype);
 
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 3d1bb32..ebb36e9 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -460,7 +460,7 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
 
-	*objnames = list_make2(get_namespace_name(procform->pronamespace),
+	*objnames = list_make2(get_namespace_name_or_temp(procform->pronamespace),
 						   pstrdup(NameStr(procform->proname)));
 	*objargs = NIL;
 	for (i = 0; i < nargs; i++)
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 818c2f6..1b0db40 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -19,6 +19,7 @@
 #include "access/htup_details.h"
 #include "access/nbtree.h"
 #include "bootstrap/bootstrap.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_collation.h"
@@ -2884,6 +2885,21 @@ get_namespace_name(Oid nspid)
 		return NULL;
 }
 
+/*
+ * get_namespace_name_or_temp
+ * 		As above, but if its a temporary schema, return "pg_temp"
+ *
+ * XXX should this throw an error when given another backend's temp namespace?
+ */
+char *
+get_namespace_name_or_temp(Oid nspid)
+{
+	if (isAnyTempNamespace(nspid))
+		return "pg_temp";
+	else
+		return get_namespace_name(nspid);
+}
+
 /*				---------- PG_RANGE CACHE ----------				 */
 
 /*
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 3c218a3..3ed5cf0 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5108,8 +5108,7 @@ DATA(insert OID = 3785 (  pg_logical_slot_peek_binary_changes PGNSP PGUID 12 100
 DESCR("peek at binary changes from replication slot");
 
 /* event triggers */
-DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,16,16,25,25,25,25,1009,1009}" "{o,o,o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, original, normal, object_type, schema_name, object_name, object_identity, address_names, address_args}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
-
+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,16,16,16,25,25,25,25,1009,1009}" "{o,o,o,o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, original, normal, is_temp, object_type, schema_name, object_name, object_identity, address_names, address_args}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
 DESCR("list objects dropped by the current command");
 DATA(insert OID = 4566 (  pg_event_trigger_table_rewrite_oid	PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 26 "" "{26}" "{o}" "{oid}" _null_ pg_event_trigger_table_rewrite_oid _null_ _null_ _null_ ));
 DESCR("return Oid of the table getting rewritten");
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 2f5ede1..15bb6d9 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -151,6 +151,7 @@ extern void free_attstatsslot(Oid atttype,
 				  Datum *values, int nvalues,
 				  float4 *numbers, int nnumbers);
 extern char *get_namespace_name(Oid nspid);
+extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid	get_range_subtype(Oid rangeOid);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
-- 
2.1.4

0005-deparse-infrastructure-needed-for-command-deparsing.patchtext/x-diff; charset=us-asciiDownload
>From e654bb7aff6256bf251933d7666550774836f61f Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 24 Sep 2014 15:53:04 -0300
Subject: [PATCH 05/37] deparse: infrastructure needed for command deparsing

This patch introduces unused infrastructure for command deparsing.
There are three main parts:

1. A list (stash) of executed commands in Node format, stored in the
event trigger state.  At ddl_command_end, the stored items can be
extracted.  For now this only support "basic" commands (in particular
not ALTER TABLE or GRANT).  It's useful enough to cover all CREATE
command as well as many simple ALTER forms.

2. Support code to enable writing routines that convert the Node format
into a JSON blob.  This JSON representation allows extracting and
modifying individual parts of the command.

3. Code to convert the JSON blobs back into plain strings.

No actual routines to convert Node into JSON is provided by this patch;
that is left to later patches.  This split is only presented as is for
ease of review.
---
 src/backend/commands/event_trigger.c |  264 ++++++++-
 src/backend/tcop/Makefile            |    2 +-
 src/backend/tcop/deparse_utility.c   | 1033 ++++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           |    2 +
 src/backend/utils/adt/Makefile       |    2 +-
 src/backend/utils/adt/ddl_json.c     |  714 +++++++++++++++++++++++
 src/backend/utils/adt/format_type.c  |  139 ++++-
 src/include/catalog/pg_proc.h        |    4 +
 src/include/commands/event_trigger.h |    6 +
 src/include/commands/extension.h     |    2 +-
 src/include/nodes/parsenodes.h       |    1 +
 src/include/tcop/deparse_utility.h   |   60 ++
 src/include/utils/builtins.h         |    7 +
 13 files changed, 2222 insertions(+), 14 deletions(-)
 create mode 100644 src/backend/tcop/deparse_utility.c
 create mode 100644 src/backend/utils/adt/ddl_json.c
 create mode 100644 src/include/tcop/deparse_utility.h

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index de6f52f..d80ffa5 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -25,16 +25,20 @@
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/trigger.h"
 #include "funcapi.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -52,7 +56,9 @@ typedef struct EventTriggerQueryState
 	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite event */
 	int			table_rewrite_reason;	/* AT_REWRITE reason */
 
+	bool		commandCollectionInhibited;
 	MemoryContext cxt;
+	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -71,6 +77,7 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
 	{"AGGREGATE", true},
 	{"CAST", true},
@@ -1067,6 +1074,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CAST:
 		case OBJECT_COLUMN:
 		case OBJECT_COLLATION:
+		case OBJECT_COMPOSITE:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFACL:
 		case OBJECT_DEFAULT:
@@ -1202,14 +1210,6 @@ EventTriggerBeginCompleteQuery(void)
 	EventTriggerQueryState *state;
 	MemoryContext cxt;
 
-	/*
-	 * Currently, sql_drop and table_rewrite events are the only reason to
-	 * have event trigger state at all; so if there are none, don't install
-	 * one.
-	 */
-	if (!trackDroppedObjectsNeeded())
-		return false;
-
 	cxt = AllocSetContextCreate(TopMemoryContext,
 								"event trigger state",
 								ALLOCSET_DEFAULT_MINSIZE,
@@ -1220,7 +1220,9 @@ EventTriggerBeginCompleteQuery(void)
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
-
+	state->commandCollectionInhibited = currentEventTriggerState ?
+		currentEventTriggerState->commandCollectionInhibited : false;
+	state->stash = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
 
@@ -1563,3 +1565,247 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
 }
+
+/*-------------------------------------------------------------------------
+ * Support for DDL command deparsing
+ *
+ * The routines below enable an event trigger function to obtain a list of
+ * DDL commands as they are executed, in a "normalized" format.  There are
+ * four main pieces to this feature:
+ *
+ * 1) Within ProcessUtilitySlow, or some sub-routine thereof, each DDL
+ * command must add an internal representation to the "commands stash",
+ * using the routines below.
+ *
+ * 2) Some time after that, the ddl_command_end event trigger occurs, and the
+ * command stash is made available to the event trigger function by way of
+ * a set-returning function called pg_event_trigger_get_creation_commands().
+ * XXX since this feature also captures ALTER and other commands, this
+ * function should probably be renamed.
+ *
+ * 3) deparse_utility.c knows how to interpret those internal formats and
+ * convert them into a JSON representation.  That representation has been
+ * designed to allow the event trigger function to inspect or modify the
+ * tree.
+ *
+ * 4) ddl_json.c knows how to convert the JSON representation back into
+ * a string corresponding to a valid command.  (This step is optional; the
+ * JSON itself could be stored directly into a table, for example for audit
+ * purposes.)
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * EventTriggerStashCommand
+ * 		Save data about a simple DDL command that was just executed
+ *
+ * address identifies the object being operated on.  secondaryObject is an
+ * object address that was related in some way to the executed command; its
+ * meaning is command-specific.
+ *
+ * For instance, for an ALTER obj SET SCHEMA command, objtype is the type of
+ * object being moved, objectId is its OID, and secondaryOid is the OID of the
+ * old schema.  (The destination schema OID can be obtained by catalog lookup
+ * of the object.)
+ */
+void
+EventTriggerStashCommand(ObjectAddress address, ObjectAddress secondaryObject,
+						 Node *parsetree)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_Simple;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.simple.address = address;
+	stashed->d.simple.secondaryObject = secondaryObject;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+void
+EventTriggerInhibitCommandCollection(void)
+{
+	currentEventTriggerState->commandCollectionInhibited = true;
+}
+
+void
+EventTriggerUndoInhibitCommandCollection(void)
+{
+	currentEventTriggerState->commandCollectionInhibited = false;
+}
+
+Datum
+pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_get_creation_commands()")));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->stash)
+	{
+		StashedCommand *cmd = lfirst(lc);
+		char	   *command;
+		Datum		values[9];
+		bool		nulls[9];
+		ObjectAddress addr;
+		int			i = 0;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid.  Don't return anything.
+		 *
+		 * One might think that a viable alternative would be to look up the
+		 * Oid of the existing object and run the deparse with that.  But since
+		 * the parse tree might be different from the one that created the
+		 * object in the first place, we might not end up in a consistent state
+		 * anyway.
+		 */
+		if (cmd->type == SCT_Simple &&
+			!OidIsValid(cmd->d.simple.address.objectId))
+			continue;
+
+		command = deparse_utility_command(cmd);
+
+		/*
+		 * Some parse trees return NULL when deparse is attempted; we don't
+		 * emit anything for them.
+		 */
+		if (command == NULL)
+			continue;
+
+		MemSet(nulls, 0, sizeof(nulls));
+
+		if (cmd->type == SCT_Simple)
+		{
+			const char *tag;
+			char	   *identity;
+			char	   *type;
+			char	   *schema = NULL;
+
+			if (cmd->type == SCT_Simple)
+				addr = cmd->d.simple.address;
+
+			tag = CreateCommandTag(cmd->parsetree);
+
+			type = getObjectTypeDescription(&addr);
+			identity = getObjectIdentity(&addr);
+
+			/*
+			 * Obtain schema name, if any ("pg_temp" if a temp object).  If
+			 * the object class is not in the supported list here, we
+			 * assume it's a schema-less object type, and thus "schema"
+			 * remains set to NULL.
+			 */
+			if (is_objectclass_supported(addr.classId))
+			{
+				AttrNumber	nspAttnum;
+
+				nspAttnum = get_object_attnum_namespace(addr.classId);
+				if (nspAttnum != InvalidAttrNumber)
+				{
+					Relation	catalog;
+					HeapTuple	objtup;
+					Oid			schema_oid;
+					bool		isnull;
+
+					catalog = heap_open(addr.classId, AccessShareLock);
+					objtup = get_catalog_object_by_oid(catalog,
+													   addr.objectId);
+					if (!HeapTupleIsValid(objtup))
+						elog(ERROR, "cache lookup failed for object %u/%u",
+							 addr.classId, addr.objectId);
+					schema_oid = heap_getattr(objtup, nspAttnum,
+											  RelationGetDescr(catalog), &isnull);
+					if (isnull)
+						elog(ERROR, "invalid null namespace in object %u/%u/%d",
+							 addr.classId, addr.objectId, addr.objectSubId);
+					if (isAnyTempNamespace(schema_oid))
+						schema = pstrdup("pg_temp");
+					else
+						schema = get_namespace_name(schema_oid);
+
+					heap_close(catalog, AccessShareLock);
+				}
+			}
+
+			/* classid */
+			values[i++] = ObjectIdGetDatum(addr.classId);
+			/* objid */
+			values[i++] = ObjectIdGetDatum(addr.objectId);
+			/* objsubid */
+			values[i++] = Int32GetDatum(addr.objectSubId);
+			/* command tag */
+			values[i++] = CStringGetTextDatum(tag);
+			/* object_type */
+			values[i++] = CStringGetTextDatum(type);
+			/* schema */
+			if (schema == NULL)
+				nulls[i++] = true;
+			else
+				values[i++] = CStringGetTextDatum(schema);
+			/* identity */
+			values[i++] = CStringGetTextDatum(identity);
+			/* in_extension */
+			values[i++] = BoolGetDatum(cmd->in_extension);
+			/* command */
+			values[i++] = CStringGetTextDatum(command);
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
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..de25f32
--- /dev/null
+++ b/src/backend/tcop/deparse_utility.c
@@ -0,0 +1,1033 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.c
+ *	  Functions to convert utility commands to machine-parseable
+ *	  representation
+ *
+ * Portions Copyright (c) 1996-2015, 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 "access/sysattr.h"
+#include "catalog/heap.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_cast.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_opclass.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_policy.h"
+#include "catalog/pg_proc.h"
+#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"
+#include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "foreign/foreign.h"
+#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"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/jsonb.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Before they are turned into JSONB representation, each command is
+ * represented as an object tree, using the structs below.
+ */
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeInteger,
+	ObjTypeFloat,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjTree
+{
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+
+	union
+	{
+		bool		boolean;
+		char	   *string;
+		int64		integer;
+		float8		flt;
+		ObjTree	   *object;
+		List	   *array;
+	} value;
+	slist_node	node;
+} ObjElem;
+
+static ObjElem *new_null_object(void);
+static ObjElem *new_bool_object(bool value);
+static ObjElem *new_string_object(char *value);
+static ObjElem *new_object_object(ObjTree *value);
+static ObjElem *new_array_object(List *array);
+static ObjElem *new_integer_object(int64 value);
+static ObjElem *new_float_object(float8 value);
+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);
+#ifdef NOT_USED
+static void append_integer_object(ObjTree *tree, char *name, int64 value);
+#endif
+static void append_float_object(ObjTree *tree, char *name, float8 value);
+static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
+static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+
+/*
+ * Allocate a new object tree to store parameter values.
+ */
+static ObjTree *
+new_objtree(void)
+{
+	ObjTree    *params;
+
+	params = palloc(sizeof(ObjTree));
+	params->numParams = 0;
+	slist_init(&params->params);
+
+	return params;
+}
+
+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one, a
+ * name (string), type (from the ObjType enum) and value must be supplied.  The
+ * value must match the type given; for instance, ObjTypeInteger requires an
+ * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of
+ * ObjElem), ObjTypeObject requires an ObjTree, and so on.  Each element type *
+ * must match the conversion specifier given in the format string, as described
+ * in pg_event_trigger_expand_command, q.v.
+ *
+ * Note we don't have the luxury of sprintf-like compiler warnings for
+ * malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree();
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		char	   *name;
+		ObjType		type;
+		ObjElem	   *elem;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/*
+		 * 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:
+				elem = new_bool_object(va_arg(args, int));
+				break;
+			case ObjTypeString:
+				elem = new_string_object(va_arg(args, char *));
+				break;
+			case ObjTypeObject:
+				elem = new_object_object(va_arg(args, ObjTree *));
+				break;
+			case ObjTypeArray:
+				elem = new_array_object(va_arg(args, List *));
+				break;
+			case ObjTypeInteger:
+				elem = new_integer_object(va_arg(args, int64));
+				break;
+			case ObjTypeFloat:
+				elem = new_float_object(va_arg(args, double));
+				break;
+			case ObjTypeNull:
+				/* Null params don't have a value (obviously) */
+				elem = new_null_object();
+				break;
+			default:
+				elog(ERROR, "invalid ObjTree element type %d", type);
+		}
+
+		elem->name = name;
+		append_premade_object(tree, elem);
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/* Allocate a new parameter with a NULL value */
+static ObjElem *
+new_null_object(void)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = NULL;
+	param->objtype = ObjTypeNull;
+
+	return param;
+}
+
+/* Append a NULL object to a tree */
+static void
+append_null_object(ObjTree *tree, char *name)
+{
+	ObjElem    *param;
+
+	param = new_null_object();
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new boolean parameter */
+static ObjElem *
+new_bool_object(bool value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeBool;
+	param->value.boolean = value;
+
+	return param;
+}
+
+/* Append a boolean parameter to a tree */
+static void
+append_bool_object(ObjTree *tree, char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = new_bool_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new string object */
+static ObjElem *
+new_string_object(char *value)
+{
+	ObjElem    *param;
+
+	Assert(value);
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeString;
+	param->value.string = value;
+
+	return param;
+}
+
+/*
+ * Append a string parameter to a tree.
+ */
+static void
+append_string_object(ObjTree *tree, char *name, char *value)
+{
+	ObjElem	   *param;
+
+	Assert(name);
+	param = new_string_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+static ObjElem *
+new_integer_object(int64 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeInteger;
+	param->value.integer = value;
+
+	return param;
+}
+
+static ObjElem *
+new_float_object(float8 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeFloat;
+	param->value.flt = value;
+
+	return param;
+}
+
+#ifdef NOT_USED
+/*
+ * Append an int64 parameter to a tree.
+ */
+static void
+append_integer_object(ObjTree *tree, char *name, int64 value)
+{
+	ObjElem	   *param;
+
+	param = new_integer_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+#endif
+
+/*
+ * Append a float8 parameter to a tree.
+ */
+static void
+append_float_object(ObjTree *tree, char *name, float8 value)
+{
+	ObjElem	   *param;
+
+	param = new_float_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new object parameter */
+static ObjElem *
+new_object_object(ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeObject;
+	param->value.object = value;
+
+	return param;
+}
+
+/* Append an object parameter to a tree */
+static void
+append_object_object(ObjTree *tree, char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	Assert(name);
+	param = new_object_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new array parameter */
+static ObjElem *
+new_array_object(List *array)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeArray;
+	param->value.array = array;
+
+	return param;
+}
+
+/* Append an array parameter to a tree */
+static void
+append_array_object(ObjTree *tree, char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = new_array_object(array);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Append a preallocated parameter to a tree */
+static inline void
+append_premade_object(ObjTree *tree, ObjElem *elem)
+{
+	slist_push_head(&tree->params, &elem->node);
+	tree->numParams++;
+}
+
+/*
+ * Helper for objtree_to_jsonb: process an individual element from an object or
+ * an array into the output parse state.
+ */
+static void
+objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object,
+						 JsonbIteratorToken elem_token)
+{
+	ListCell   *cell;
+	JsonbValue	val;
+
+	switch (object->objtype)
+	{
+		case ObjTypeNull:
+			val.type = jbvNull;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeString:
+			val.type = jbvString;
+			val.val.string.len = strlen(object->value.string);
+			val.val.string.val = object->value.string;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeInteger:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeFloat:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(float8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeBool:
+			val.type = jbvBool;
+			val.val.boolean = object->value.boolean;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeObject:
+			/* recursively add the object into the existing parse state */
+			objtree_to_jsonb_rec(object->value.object, state);
+			break;
+
+		case ObjTypeArray:
+			pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+			foreach(cell, object->value.array)
+			{
+				ObjElem   *elem = lfirst(cell);
+
+				objtree_to_jsonb_element(state, elem, WJB_ELEM);
+			}
+			pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+			break;
+
+		default:
+			elog(ERROR, "unrecognized object type %d", object->objtype);
+			break;
+	}
+}
+
+/*
+ * Recursive helper for objtree_to_jsonb
+ */
+static JsonbValue *
+objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state)
+{
+	slist_iter	iter;
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	slist_foreach(iter, &tree->params)
+	{
+		ObjElem    *object = slist_container(ObjElem, node, iter.cur);
+		JsonbValue	key;
+
+		/* Push the key first */
+		key.type = jbvString;
+		key.val.string.len = strlen(object->name);
+		key.val.string.val = object->name;
+		pushJsonbValue(&state, WJB_KEY, &key);
+
+		/* Then process the value according to its type */
+		objtree_to_jsonb_element(state, object, WJB_VALUE);
+	}
+
+	return pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Create a JSONB representation from an ObjTree.
+ */
+static Jsonb *
+objtree_to_jsonb(ObjTree *tree)
+{
+	JsonbValue *value;
+
+	value = objtree_to_jsonb_rec(tree, NULL);
+	return JsonbValueToJsonb(value);
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{
+	ObjTree    *typeParam;
+	Oid			typnspid;
+	char	   *typnsp;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		typarray;
+
+	format_type_detailed(typeId, typmod,
+						 &typnspid, &typename, &typmodstr, &typarray);
+
+	if (!OidIsValid(typnspid))
+		typnsp = pstrdup("");
+	else if (isAnyTempNamespace(typnspid))
+		typnsp = pstrdup("pg_temp");
+	else
+		typnsp = get_namespace_name(typnspid);
+
+	/* We don't use new_objtree_VA here because types don't have a "fmt" */
+	typeParam = new_objtree();
+	append_string_object(typeParam, "schemaname", typnsp);
+	append_string_object(typeParam, "typename", typename);
+	append_string_object(typeParam, "typmod", typmodstr);
+	append_bool_object(typeParam, "typarray", typarray);
+
+	return typeParam;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements
+ *
+ * Elements "schemaname" and "objname" are set.  If the namespace OID
+ * corresponds to a temp schema, that's set to "pg_temp".
+ *
+ * The difference between those two element types is whether the objname will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static ObjTree *
+new_objtree_for_qualname(Oid nspid, char *name)
+{
+	ObjTree    *qualified;
+	char	   *namespace;
+
+	/*
+	 * We don't use new_objtree_VA here because these names don't have a "fmt"
+	 */
+	qualified = new_objtree();
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+	append_string_object(qualified, "schemaname", namespace);
+	append_string_object(qualified, "objname", pstrdup(name));
+
+	return qualified;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements, with the object specified
+ * by classId/objId
+ *
+ * Elements "schemaname" and "objname" are set.  If the object is a temporary
+ * object, the schema name is set to "pg_temp".
+ */
+static ObjTree *
+new_objtree_for_qualname_id(Oid classId, Oid objectId)
+{
+	ObjTree    *qualified;
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		objnsp;
+	Datum		objname;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	bool		isnull;
+
+	catalog = heap_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL namespace");
+	objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL name");
+
+	qualified = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+										 NameStr(*DatumGetName(objname)));
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+/*
+ * Helper routine for %{}R objects, with role specified by OID.  (ACL_ID_PUBLIC
+ * means to use "public").
+ */
+static ObjTree *
+new_objtree_for_role_id(Oid roleoid)
+{
+	ObjTree    *role;
+
+	role = new_objtree();
+	append_bool_object(role, "is_public", roleoid == ACL_ID_PUBLIC);
+
+	if (roleoid != ACL_ID_PUBLIC)
+	{
+		HeapTuple	roltup;
+		char	   *rolename;
+
+		roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleoid));
+		if (!HeapTupleIsValid(roltup))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("role with OID %u does not exist", roleoid)));
+
+		rolename = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname);
+		append_string_object(role, "rolename", pstrdup(rolename));
+		ReleaseSysCache(roltup);
+	}
+
+	return role;
+}
+
+/*
+ * Helper routine for %{}R objects, with role specified by name.
+ */
+static ObjTree *
+new_objtree_for_role(char *rolename)
+{
+	ObjTree	   *role;
+	bool		is_public;
+
+	role = new_objtree();
+	is_public = strcmp(rolename, "public") == 0;
+	append_bool_object(role, "is_public", is_public);
+	if (!is_public)
+		append_string_object(role, "rolename", rolename);
+
+	return role;
+}
+
+/*
+ * Helper routine for %{}R objects, with role specified by RoleSpec node.
+ * Special values such as ROLESPEC_CURRENT_USER are expanded to their final
+ * names.
+ */
+static ObjTree *
+new_objtree_for_rolespec(RoleSpec *spec)
+{
+	ObjTree	   *role;
+
+	role = new_objtree();
+	append_bool_object(role, "is_public",
+					   spec->roletype == ROLESPEC_PUBLIC);
+	if (spec->roletype != ROLESPEC_PUBLIC)
+		append_string_object(role, "rolename", get_rolespec_name((Node *) spec));
+
+	return role;
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function contains a large switch that mirrors that in
+ * ProcessUtilitySlow.  All cases covered there should also be covered here.
+ */
+static ObjTree *
+deparse_simple_command(StashedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+	ObjTree	   *command;
+
+	Assert(cmd->type == SCT_Simple);
+
+	parsetree = cmd->parsetree;
+	objectId = cmd->d.simple.address.objectId;
+
+	/* This switch needs to handle everything that ProcessUtilitySlow does */
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateForeignTableStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTableStmt:
+		case T_AlterTableMoveAllStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterDomainStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+			/* other local objects */
+		case T_DefineStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_IndexStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateExtensionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterExtensionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterExtensionContentsStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropUserMappingStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_ImportForeignSchemaStmt:
+			/* generated commands are stashed individually */
+			elog(ERROR, "unexpected command %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterEnumStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_ViewStmt:		/* CREATE VIEW */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateFunctionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterFunctionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_RuleStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateSeqStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterSeqStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateTableAsStmt:
+			/* XXX handle at least the CREATE MATVIEW case? */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_RefreshMatViewStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateTrigStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePLangStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateDomainStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateConversionStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateCastStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpClassStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpFamilyStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterOpFamilyStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSDictionaryStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSConfigurationStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_RenameStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterObjectSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterOwnerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CommentStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_GrantStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropOwnedStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_AlterDefaultPrivilegesStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePolicyStmt:	/* CREATE POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterPolicyStmt:		/* ALTER POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_SecLabelStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		default:
+			command = NULL;
+			elog(LOG, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return command;
+}
+
+/*
+ * Given a StashedCommand, return a JSON representation of the command.
+ *
+ * The command is expanded fully, so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+char *
+deparse_utility_command(StashedCommand *cmd)
+{
+	OverrideSearchPath *overridePath;
+	MemoryContext	oldcxt;
+	MemoryContext	tmpcxt;
+	ObjTree		   *tree;
+	char		   *command;
+	StringInfoData  str;
+
+	/*
+	 * Allocate everything done by the deparsing routines into a temp context,
+	 * to avoid having to sprinkle them with memory handling code; but allocate
+	 * the output StringInfo before switching.
+	 */
+	initStringInfo(&str);
+	tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "deparse ctx",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * in order to obtain deparsed versions of expressions.  In such results,
+	 * we want all object names to be qualified, so that results are "portable"
+	 * to environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = true;
+	PushOverrideSearchPath(overridePath);
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			tree = deparse_simple_command(cmd);
+			break;
+		default:
+			elog(ERROR, "unexpected deparse node type %d", cmd->type);
+	}
+
+	PopOverrideSearchPath();
+
+	if (tree)
+	{
+		Jsonb *jsonb;
+
+		jsonb = objtree_to_jsonb(tree);
+		command = JsonbToCString(&str, &jsonb->root, 128);
+	}
+	else
+		command = NULL;
+
+	/*
+	 * Clean up.  Note that since we created the StringInfo in the caller's
+	 * context, the output string is not deleted here.
+	 */
+	MemoryContextSwitchTo(oldcxt);
+	MemoryContextDelete(tmpcxt);
+
+	return command;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d9443b1..9d0037b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -875,6 +875,8 @@ standard_ProcessUtility(Node *parsetree,
  * The "Slow" variant of ProcessUtility should only receive statements
  * supported by the event triggers facility.  Therefore, we always
  * perform the trigger support calls if the context allows it.
+ *
+ * See deparse_utility_command, which must be kept in sync with this.
  */
 static void
 ProcessUtilitySlow(Node *parsetree,
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 20e5ff1..110c6ee 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -18,7 +18,7 @@ endif
 # keep this list arranged alphabetically or it gets to be a mess
 OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
 	array_userfuncs.o arrayutils.o ascii.o bool.o \
-	cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \
+	cash.o char.o date.o datetime.o datum.o dbsize.o ddl_json.o domains.o \
 	encode.o enum.o float.o format_type.o formatting.o genfile.o \
 	geo_ops.o geo_selfuncs.o inet_cidr_ntop.o inet_net_pton.o int.o \
 	int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
diff --git a/src/backend/utils/adt/ddl_json.c b/src/backend/utils/adt/ddl_json.c
new file mode 100644
index 0000000..045bc29
--- /dev/null
+++ b/src/backend/utils/adt/ddl_json.c
@@ -0,0 +1,714 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_json.c
+ *	  JSON code related to DDL command deparsing
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/ddl_json.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "lib/stringinfo.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+
+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	SpecNumber,
+	SpecStringLiteral,
+	SpecIdentifier,
+	SpecRole
+} convSpecifier;
+
+typedef enum
+{
+	tv_absent,
+	tv_true,
+	tv_false
+} trivalue;
+
+static void expand_one_jsonb_element(StringInfo out, char *param,
+						 JsonbValue *jsonval, convSpecifier specifier,
+						 const char *fmt);
+static void expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep,
+				   convSpecifier specifier, const char *fmt);
+static void fmtstr_error_callback(void *arg);
+
+static trivalue
+find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	bool		result;
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+		return tv_absent;
+	if (value->type != jbvBool)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not of type boolean",
+						keyname)));
+	result = value->val.boolean ? tv_true : tv_false;
+	pfree(value);
+
+	return result;
+}
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvString, an error is raised.  If it doesn't
+ * exist, an error is raised if missing_ok; otherwise return NULL.
+ *
+ * If it exists and is a string, a freshly palloc'ed copy is returned.
+ *
+ * If *length is not NULL, it is set to the length of the string.
+ */
+static char *
+find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname,
+							  bool missing_ok, int *length)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	char	   *str;
+
+	/* XXX verify that this is an object, not an array */
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+	{
+		if (missing_ok)
+			return NULL;
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing element \"%s\" in json object", keyname)));
+	}
+
+	str = pnstrdup(value->val.string.val, value->val.string.len);
+	if (length)
+		*length = value->val.string.len;
+	pfree(value);
+	return str;
+}
+
+#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)
+
+/*
+ * Recursive helper for pg_event_trigger_expand_command
+ *
+ * Find the "fmt" element in the given container, and expand it into the
+ * provided StringInfo.
+ */
+static void
+expand_fmt_recursive(JsonbContainer *container, StringInfo out)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	int			len;
+
+	start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len);
+	end_ptr = start_ptr + len;
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{
+		convSpecifier specifier;
+		bool		is_array;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		is_array = false;
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			for (; cp < end_ptr;)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing conversion name in conversion specifier")));
+
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'L':
+				specifier = SpecStringLiteral;
+				break;
+			case 'T':
+				specifier = SpecTypename;
+				break;
+			case 'O':
+				specifier = SpecOperatorname;
+				break;
+			case 'n':
+				specifier = SpecNumber;
+				break;
+			case 'R':
+				specifier = SpecRole;
+				break;
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid conversion specifier \"%c\"", *cp)));
+		}
+
+		/*
+		 * Obtain the element to be expanded.
+		 */
+		key.type = jbvString;
+		key.val.string.val = param;
+		key.val.string.len = strlen(param);
+
+		value = findJsonbValueFromContainer(container, JB_FOBJECT, &key);
+
+		/* Validate that we got an array if the format string specified one. */
+
+		/* And finally print out the data */
+		if (is_array)
+			expand_jsonb_array(out, param, value, arraysep, specifier, start_ptr);
+		else
+			expand_one_jsonb_element(out, param, value, specifier, start_ptr);
+	}
+}
+
+/*
+ * Expand a json value as an identifier.  The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	Assert(jsonval->type == jbvString);
+
+	str = pnstrdup(jsonval->val.string.val,
+				   jsonval->val.string.len);
+	appendStringInfoString(buf, quote_identifier(str));
+	pfree(str);
+}
+
+/*
+ * Expand a json value as a dot-separated-name.  The value must be of type
+ * object and must contain elements "schemaname" (optional), "objname"
+ * (mandatory), "attrname" (optional).  Double quotes are added to each element
+ * as necessary, and dot separators where needed.
+ *
+ * One day we might need a "catalog" element as well, but no current use case
+ * needs that.
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+		pfree(str);
+	}
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfo(buf, "%s", quote_identifier(str));
+	pfree(str);
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"attrname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, ".%s", quote_identifier(str));
+		pfree(str);
+	}
+}
+
+/*
+ * expand a json value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename;
+	char	   *typmodstr;
+	trivalue	is_array;
+	char	   *array_decor;
+
+	/*
+	 * We omit schema-qualifying the output name if the schema element is
+	 * either the empty string or NULL; the difference between those two cases
+	 * is that in the latter we quote the type name, in the former we don't.
+	 * This allows for types with special typmod needs, such as interval and
+	 * timestamp (see format_type_detailed), while at the same time allowing
+	 * for the schema name to be omitted from type names that require quotes
+	 * but are to be obtained from a user schema.
+	 */
+
+	schema = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										   "schemaname", true, NULL);
+	typename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											 "typename", false, NULL);
+	typmodstr = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											  "typmod", true, NULL);
+	is_array = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+										   "typarray");
+	switch (is_array)
+	{
+		default:
+		case tv_absent:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("missing typarray element")));
+			break;
+		case tv_true:
+			array_decor = "[]";
+			break;
+		case tv_false:
+			array_decor = "";
+			break;
+	}
+
+	if (schema == NULL)
+		appendStringInfo(buf, "%s%s%s",
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else if (schema[0] == '\0')
+		appendStringInfo(buf, "%s%s%s",
+						 typename,
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else
+		appendStringInfo(buf, "%s.%s%s%s",
+						 quote_identifier(schema),
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+}
+
+/*
+ * Expand a json value as an operator name
+ */
+static void
+expand_jsonval_operator(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	/* schema might be NULL or empty */
+	if (str != NULL && str[0] != '\0')
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfoString(buf, str);
+}
+
+/*
+ * Expand a json value as a string.  The value must be of type string or of
+ * type object.  In the latter 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, JsonbValue *jsonval)
+{
+	if (jsonval->type == jbvString)
+	{
+		appendBinaryStringInfo(buf, jsonval->val.string.val,
+							   jsonval->val.string.len);
+	}
+	else if (jsonval->type == jbvBinary)
+	{
+		trivalue	present;
+
+		present = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											  "present");
+		/*
+		 * If "present" is set to false, this element expands to empty;
+		 * otherwise (either true or absent), fall through to expand "fmt".
+		 */
+		if (present == tv_false)
+			return;
+
+		expand_fmt_recursive(jsonval->val.binary.data, buf);
+	}
+}
+
+/*
+ * Expand a json value as a string literal
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval)
+{
+	char   *str;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int         dqnextchar = 0;
+
+	str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+
+	/* easy case: if there are no ' and no \, just use a single quote */
+	if (strchr(str, '\'') == NULL &&
+		strchr(str, '\\') == NULL)
+	{
+		appendStringInfo(buf, "'%s'", str);
+		pfree(str);
+		return;
+	}
+
+	/* Otherwise need to find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(str, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar %= sizeof(dqsuffixes) - 1;
+	}
+	/* add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data);
+	pfree(dqdelim.data);
+	pfree(str);
+}
+
+/*
+ * Expand a json value as an integer quantity
+ */
+static void
+expand_jsonval_number(StringInfo buf, JsonbValue *jsonval)
+{
+	char *strdatum;
+
+	strdatum = DatumGetCString(DirectFunctionCall1(numeric_out,
+												   NumericGetDatum(jsonval->val.numeric)));
+	appendStringInfoString(buf, strdatum);
+}
+
+/*
+ * Expand a json value as a role name.  If the is_public element is set to
+ * true, PUBLIC is expanded (no quotes); otherwise, expand the given role name,
+ * quoting as an identifier.
+ */
+static void
+expand_jsonval_role(StringInfo buf, JsonbValue *jsonval)
+{
+	trivalue	is_public;
+
+	is_public = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											"is_public");
+	if (is_public == tv_true)
+		appendStringInfoString(buf, "PUBLIC");
+	else
+	{
+		char *rolename;
+
+		rolename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+												 "rolename", false, NULL);
+		appendStringInfoString(buf, quote_identifier(rolename));
+	}
+}
+
+/*
+ * Expand one json element into the output StringInfo according to the
+ * conversion specifier.  The element type is validated, and an error is raised
+ * if it doesn't match what we expect for the conversion specifier.
+ */
+static void
+expand_one_jsonb_element(StringInfo out, char *param, JsonbValue *jsonval,
+						 convSpecifier specifier, const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (!jsonval)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" not found", param)));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%I element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_identifier(out, jsonval);
+			break;
+
+		case SpecDottedName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%D element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_dottedname(out, jsonval);
+			break;
+
+		case SpecString:
+			if (jsonval->type != jbvString &&
+				jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string or object for %%s element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_string(out, jsonval);
+			break;
+
+		case SpecStringLiteral:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%L element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_strlit(out, jsonval);
+			break;
+
+		case SpecTypename:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%T element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_typename(out, jsonval);
+			break;
+
+		case SpecOperatorname:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%O element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_operator(out, jsonval);
+			break;
+
+		case SpecNumber:
+			if (jsonval->type != jbvNumeric)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON numeric for %%n element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_number(out, jsonval);
+			break;
+
+		case SpecRole:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%R element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_role(out, jsonval);
+			break;
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*
+ * Iterate on the elements of a JSON array, expanding each one into the output
+ * StringInfo per the given conversion specifier, separated by the given
+ * separator.
+ */
+static void
+expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep, convSpecifier specifier,
+				   const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+	JsonbContainer *container;
+	JsonbIterator  *it;
+	JsonbValue	v;
+	int			type;
+	bool		first = true;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (jsonarr->type != jbvBinary)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	container = jsonarr->val.binary.data;
+	if ((container->header & JB_FARRAY) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	it = JsonbIteratorInit(container);
+	while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+	{
+		switch (type)
+		{
+			case WJB_ELEM:
+				if (!first)
+					appendStringInfoString(out, arraysep);
+				first = false;
+				expand_one_jsonb_element(out, param, &v, specifier, NULL);
+				break;
+		}
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ * n		expand as a simple number (no quoting)
+ * R		expand as a role name (possibly quoted name, or PUBLIC)
+ *
+ * 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.
+ *------
+ */
+Datum
+pg_event_trigger_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	Datum		d;
+	Jsonb	   *jsonb;
+	StringInfoData out;
+
+	initStringInfo(&out);
+	d = DirectFunctionCall1(jsonb_in,
+							PointerGetDatum(TextDatumGetCString(json)));
+	jsonb = (Jsonb *) DatumGetPointer(d);
+
+	expand_fmt_recursive(&jsonb->root, &out);
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(out.data));
+}
+
+/*
+ * Error context callback for JSON format string expansion.
+ *
+ * Possible improvement: indicate which element we're expanding, if applicable
+ */
+static void
+fmtstr_error_callback(void *arg)
+{
+	errcontext("while expanding format string \"%s\"", (char *) arg);
+
+}
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index fc816ce..fee71e5 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,132 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
+/*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name in this case.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *typarray)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID ||
+		type_oid == TIMEOID ||
+		type_oid == TIMETZOID)
+	{
+		*typarray = false;
+
+peculiar_typmod:
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPTZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmod, so fall through */
+			case TIMESTAMPOID:
+				*typname = pstrdup("TIMESTAMP");
+				break;
+			case TIMETZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIME WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmode, so fall through */
+			case TIMEOID:
+				*typname = pstrdup("TIME");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		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;
+		*typarray = true;
+
+		/*
+		 * If it's an array of one of the types with special typmod rules,
+		 * have the element type be processed as above, but now with typarray
+		 * set to true.
+		 */
+		if (type_oid == INTERVALOID ||
+			type_oid == TIMESTAMPTZOID ||
+			type_oid == TIMESTAMPOID ||
+			type_oid == TIMETZOID ||
+			type_oid == TIMEOID)
+			goto peculiar_typmod;
+	}
+	else
+		*typarray = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
 
 /*
  * Add typmod decoration to the basic type name
@@ -338,7 +467,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 +479,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 3ed5cf0..a11bdd6 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5114,6 +5114,10 @@ DATA(insert OID = 4566 (  pg_event_trigger_table_rewrite_oid	PGNSP PGUID 12 1 0
 DESCR("return Oid of the table getting rewritten");
 DATA(insert OID = 4567 (  pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ ));
 DESCR("return reason code for table getting rewritten");
+DATA(insert OID = 3590 (  pg_event_trigger_get_creation_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,114}" "{o,o,o,o,o,o,o,o,o}" "{classid,objid,objsubid,command_tag,object_type,schema,identity,in_extension,command}" _null_ pg_event_trigger_get_creation_commands _null_ _null_ _null_ ));
+DESCR("list JSON-formatted commands executed by the current command");
+DATA(insert OID = 3591 (  pg_event_trigger_expand_command PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 25 "114" _null_ _null_ _null_ _null_ pg_event_trigger_expand_command _null_ _null_ _null_ ));
+DESCR("format JSON command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 7eb2156..a38d5aa 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -60,4 +60,10 @@ extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 							 bool original, bool normal);
 
+extern void EventTriggerInhibitCommandCollection(void);
+extern void EventTriggerUndoInhibitCommandCollection(void);
+
+extern void EventTriggerStashCommand(ObjectAddress address,
+						 ObjectAddress secondaryObject, Node *parsetree);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 40ecea2..0423350 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -24,7 +24,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ec0d0ea..3ecfbac 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1229,6 +1229,7 @@ typedef enum ObjectType
 	OBJECT_CAST,
 	OBJECT_COLUMN,
 	OBJECT_COLLATION,
+	OBJECT_COMPOSITE,
 	OBJECT_CONVERSION,
 	OBJECT_DATABASE,
 	OBJECT_DEFAULT,
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..7f355cb
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,60 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+#include "access/attnum.h"
+#include "nodes/nodes.h"
+
+/*
+ * Support for keeping track of a command to deparse.
+ *
+ * When a command is run, we collect some information about it for later
+ * deparsing; deparse_utility_command can later be used to obtain a usable
+ * representation of it.
+ */
+
+typedef enum StashedCommandType
+{
+	SCT_Simple,
+} StashedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct StashedATSubcmd
+{
+	AttrNumber		attnum;	/* affected column number */
+	Oid				oid;	/* affected constraint, default value or index */
+	Node		   *parsetree;
+} StashedATSubcmd;
+
+typedef struct StashedCommand
+{
+	StashedCommandType type;
+	bool		in_extension;
+	Node	   *parsetree;
+
+	union
+	{
+		/* most commands */
+		struct
+		{
+			ObjectAddress address;
+			ObjectAddress secondaryObject;
+		} simple;
+	} d;
+} StashedCommand;
+
+extern char *deparse_utility_command(StashedCommand *cmd);
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 6310641..a4fd958 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1086,6 +1086,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);
@@ -1217,6 +1220,10 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS);
+
+/* utils/adt/ddl_json.c */
+extern Datum pg_event_trigger_expand_command(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
-- 
2.1.4

0006-deparse-add-EventTriggerStashCommand-calls-to-Proces.patchtext/x-diff; charset=us-asciiDownload
>From 5baba19dbc5824a8002a1665522780a852f11bc6 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:50:37 -0300
Subject: [PATCH 06/37] deparse: add EventTriggerStashCommand() calls to
 ProcessUtilitySlow

These calls add each executed command to the event trigger command
stash.
---
 src/backend/commands/schemacmds.c |  14 +++
 src/backend/tcop/utility.c        | 246 ++++++++++++++++++++++++++------------
 2 files changed, 182 insertions(+), 78 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index c090ed2..89ac973 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -25,6 +25,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/schemacmds.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
@@ -52,6 +53,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	Oid			saved_uid;
 	int			save_sec_context;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
 
@@ -142,6 +144,18 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	/* XXX should we clear overridePath->useTemp? */
 	PushOverrideSearchPath(overridePath);
 
+	address.classId = NamespaceRelationId;
+	address.objectId = namespaceId;
+	address.objectSubId = 0;
+
+	/*
+	 * 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(address, InvalidObjectAddress, (Node *) stmt);
+
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
 	 * reorganize them into a sequentially executable order with no forward
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 9d0037b..fde1ddb 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -889,7 +889,9 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	bool		commandStashed = false;
 	ObjectAddress address;
+	ObjectAddress secondaryObject = InvalidObjectAddress;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -908,6 +910,12 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * CreateSchemaCommand calls EventTriggerStashCommand
+				 * internally, for reasons explained there, so turn off
+				 * command collection below.
+				 */
+				commandStashed = true;
 				break;
 
 			case T_CreateStmt:
@@ -934,6 +942,8 @@ ProcessUtilitySlow(Node *parsetree,
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
 													 InvalidOid, NULL);
+							EventTriggerStashCommand(address, secondaryObject,
+													 stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -966,10 +976,16 @@ ProcessUtilitySlow(Node *parsetree,
 													 InvalidOid, NULL);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
+							EventTriggerStashCommand(address, secondaryObject,
+													 stmt);
 						}
 						else
 						{
-							/* Recurse for anything else */
+							/*
+							 * Recurse for anything else.  Note the recursive
+							 * call will stash the objects so created into our
+							 * event trigger context.
+							 */
 							ProcessUtility(stmt,
 										   queryString,
 										   PROCESS_UTILITY_SUBCOMMAND,
@@ -982,6 +998,12 @@ ProcessUtilitySlow(Node *parsetree,
 						if (lnext(l) != NULL)
 							CommandCounterIncrement();
 					}
+
+					/*
+					 * The multiple commands generated here are stashed
+					 * individually, so disable collection below.
+					 */
+					commandStashed = true;
 				}
 				break;
 
@@ -1040,6 +1062,9 @@ ProcessUtilitySlow(Node *parsetree,
 						  (errmsg("relation \"%s\" does not exist, skipping",
 								  atstmt->relation->relname)));
 				}
+
+				/* ALTER TABLE stashes commands internally */
+				commandStashed = true;
 				break;
 
 			case T_AlterDomainStmt:
@@ -1058,31 +1083,37 @@ ProcessUtilitySlow(Node *parsetree,
 							 * Recursively alter column default for table and,
 							 * if requested, for descendants
 							 */
-							AlterDomainDefault(stmt->typeName,
-											   stmt->def);
+							address =
+								AlterDomainDefault(stmt->typeName,
+												   stmt->def);
 							break;
 						case 'N':		/* ALTER DOMAIN DROP NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   false);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   false);
 							break;
 						case 'O':		/* ALTER DOMAIN SET NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   true);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   true);
 							break;
 						case 'C':		/* ADD CONSTRAINT */
-							AlterDomainAddConstraint(stmt->typeName,
-													 stmt->def,
-													 NULL);
+							address =
+								AlterDomainAddConstraint(stmt->typeName,
+														 stmt->def,
+														 &secondaryObject);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
-							AlterDomainDropConstraint(stmt->typeName,
-													  stmt->name,
-													  stmt->behavior,
-													  stmt->missing_ok);
+							address =
+								AlterDomainDropConstraint(stmt->typeName,
+														  stmt->name,
+														  stmt->behavior,
+														  stmt->missing_ok);
 							break;
 						case 'V':		/* VALIDATE CONSTRAINT */
-							AlterDomainValidateConstraint(stmt->typeName,
-														  stmt->name);
+							address =
+								AlterDomainValidateConstraint(stmt->typeName,
+															  stmt->name);
 							break;
 						default:		/* oops */
 							elog(ERROR, "unrecognized alter domain type: %d",
@@ -1102,40 +1133,46 @@ ProcessUtilitySlow(Node *parsetree,
 					switch (stmt->kind)
 					{
 						case OBJECT_AGGREGATE:
-							DefineAggregate(stmt->defnames, stmt->args,
-											stmt->oldstyle, stmt->definition,
-											queryString);
+							address =
+								DefineAggregate(stmt->defnames, stmt->args,
+												stmt->oldstyle,
+												stmt->definition, queryString);
 							break;
 						case OBJECT_OPERATOR:
 							Assert(stmt->args == NIL);
-							DefineOperator(stmt->defnames, stmt->definition);
+							address = DefineOperator(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TYPE:
 							Assert(stmt->args == NIL);
-							DefineType(stmt->defnames, stmt->definition);
+							address = DefineType(stmt->defnames,
+												  stmt->definition);
 							break;
 						case OBJECT_TSPARSER:
 							Assert(stmt->args == NIL);
-							DefineTSParser(stmt->defnames, stmt->definition);
+							address = DefineTSParser(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TSDICTIONARY:
 							Assert(stmt->args == NIL);
-							DefineTSDictionary(stmt->defnames,
-											   stmt->definition);
+							address = DefineTSDictionary(stmt->defnames,
+														  stmt->definition);
 							break;
 						case OBJECT_TSTEMPLATE:
 							Assert(stmt->args == NIL);
-							DefineTSTemplate(stmt->defnames,
-											 stmt->definition);
+							address = DefineTSTemplate(stmt->defnames,
+														stmt->definition);
 							break;
 						case OBJECT_TSCONFIGURATION:
 							Assert(stmt->args == NIL);
-							DefineTSConfiguration(stmt->defnames,
-												  stmt->definition);
+							address = DefineTSConfiguration(stmt->defnames,
+															 stmt->definition,
+															 &secondaryObject);
 							break;
 						case OBJECT_COLLATION:
 							Assert(stmt->args == NIL);
-							DefineCollation(stmt->defnames, stmt->definition);
+							address = DefineCollation(stmt->defnames,
+													   stmt->definition);
 							break;
 						default:
 							elog(ERROR, "unrecognized define stmt type: %d",
@@ -1176,204 +1213,250 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
-					DefineIndex(relid,	/* OID of heap relation */
-								stmt,
-								InvalidOid,		/* no predefined OID */
-								false,	/* is_alter_table */
-								true,	/* check_rights */
-								false,	/* skip_build */
-								false); /* quiet */
+					address =
+						DefineIndex(relid,	/* OID of heap relation */
+									stmt,
+									InvalidOid,		/* no predefined OID */
+									false,	/* is_alter_table */
+									true,	/* check_rights */
+									false,	/* skip_build */
+									false); /* quiet */
+					/*
+					 * Add the CREATE INDEX node itself to stash right away; if
+					 * there were any commands stashed in the ALTER TABLE code,
+					 * we need them to appear after this one.
+					 */
+					EventTriggerStashCommand(address, secondaryObject,
+											 parsetree);
+					commandStashed = true;
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				address = CreateExtension((CreateExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				address = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
-											   NULL);
+				address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+														  &secondaryObject);
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				address = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
 				break;
 
 			case T_AlterFdwStmt:
-				AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				address = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				address = CreateForeignServer((CreateForeignServerStmt *) parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
-				AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				address = AlterForeignServer((AlterForeignServerStmt *) parsetree);
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				address = CreateUserMapping((CreateUserMappingStmt *) parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
-				AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				address = AlterUserMapping((AlterUserMappingStmt *) parsetree);
 				break;
 
 			case T_DropUserMappingStmt:
 				RemoveUserMapping((DropUserMappingStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandStashed = true;
 				break;
 
 			case T_ImportForeignSchemaStmt:
 				ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+				/* commands are stashed inside ImportForeignSchema */
+				commandStashed = true;
 				break;
 
 			case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					address = DefineCompositeType(stmt->typevar,
+												  stmt->coldeflist);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				address = DefineEnum((CreateEnumStmt *) parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				address = DefineRange((CreateRangeStmt *) parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				address = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				address = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerStashCommand(address, secondaryObject, parsetree);
+				/* stashed internally */
+				commandStashed = true;
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				address = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				address = AlterFunction((AlterFunctionStmt *) parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				address = DefineRule((RuleStmt *) parsetree, queryString);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				address = DefineSequence((CreateSeqStmt *) parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				address = AlterSequence((AlterSeqStmt *) parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
-								   queryString, params, completionTag);
+				/*
+				 * REFRSH CONCURRENTLY executes some DDL commands internally.
+				 * Inhibit DDL command collection here to avoid those commands
+				 * from showing up in the deparsed command queue.  The refresh
+				 * command itself is queued, which is enough.
+				 */
+				EventTriggerInhibitCommandCollection();
+				PG_TRY();
+				{
+					address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+												 queryString, params, completionTag);
+				}
+				PG_CATCH();
+				{
+					EventTriggerUndoInhibitCommandCollection();
+					PG_RE_THROW();
+				}
+				PG_END_TRY();
+				EventTriggerUndoInhibitCommandCollection();
 				break;
 
 			case T_CreateTrigStmt:
-				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
-									 InvalidOid, InvalidOid, InvalidOid,
-									 InvalidOid, false);
+				address = CreateTrigger((CreateTrigStmt *) parsetree,
+										 queryString, InvalidOid, InvalidOid,
+										 InvalidOid, InvalidOid, false);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				address = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				address = DefineDomain((CreateDomainStmt *) parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				address = CreateConversionCommand((CreateConversionStmt *) parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				address = CreateCast((CreateCastStmt *) parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				address = DefineOpClass((CreateOpClassStmt *) parsetree);
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				address = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
 				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				/* commands are stashed in AlterOpFamily */
+				commandStashed = true;
 				break;
 
 			case T_AlterTSDictionaryStmt:
-				AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				address = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
 				break;
 
 			case T_AlterTableMoveAllStmt:
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
+				/* commands are stashed in AlterTableMoveAll */
+				commandStashed = true;
 				break;
 
 			case T_DropStmt:
 				ExecDropStmt((DropStmt *) parsetree, isTopLevel);
+				/* no commands stashed for DROP */
+				commandStashed = true;
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree);
+				address = ExecRenameStmt((RenameStmt *) parsetree);
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
-										  NULL);
+				address =
+					ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
+											  &secondaryObject);
 				break;
 
 			case T_AlterOwnerStmt:
-				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
 			case T_CommentStmt:
-				CommentObject((CommentStmt *) parsetree);
+				address = CommentObject((CommentStmt *) parsetree);
 				break;
 
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
+				/* commands are stashed in ExecGrantStmt_oids */
+				commandStashed = true;
 				break;
 
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandStashed = true;
 				break;
 
 			case T_AlterDefaultPrivilegesStmt:
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
+				/* XXX FIXME WTF? */
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
-				CreatePolicy((CreatePolicyStmt *) parsetree);
+				address = CreatePolicy((CreatePolicyStmt *) parsetree);
 				break;
 
 			case T_AlterPolicyStmt:		/* ALTER POLICY */
-				AlterPolicy((AlterPolicyStmt *) parsetree);
+				address = AlterPolicy((AlterPolicyStmt *) parsetree);
 				break;
 
 			case T_SecLabelStmt:
-				ExecSecLabelStmt((SecLabelStmt *) parsetree);
+				address = ExecSecLabelStmt((SecLabelStmt *) parsetree);
 				break;
 
 			default:
@@ -1382,6 +1465,13 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 		}
 
+		/*
+		 * Remember the object so that ddl_command_end event triggers have
+		 * access to it.
+		 */
+		if (!commandStashed)
+			EventTriggerStashCommand(address, secondaryObject, parsetree);
+
 		if (isCompleteQuery)
 		{
 			EventTriggerSQLDrop(parsetree);
-- 
2.1.4

0007-deparse-Support-CREATE-SCHEMA-TABLE-SEQUENCE-INDEX-T.patchtext/x-diff; charset=us-asciiDownload
>From fefff86b61bba13cd3f3735d3494cbb2f57aa942 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:54:00 -0300
Subject: [PATCH 07/37] deparse: Support CREATE
 SCHEMA/TABLE/SEQUENCE/INDEX/TRIGGER/TYPE AS

---
 src/backend/commands/sequence.c    |   34 +
 src/backend/commands/tablecmds.c   |    3 +-
 src/backend/tcop/deparse_utility.c | 1264 +++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  373 +++++++++--
 src/include/commands/sequence.h    |    1 +
 src/include/utils/ruleutils.h      |   14 +-
 6 files changed, 1628 insertions(+), 61 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6d316d6..73cf4da 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1503,6 +1503,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/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 47d5d3b..eaac0b6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8116,7 +8116,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/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index de25f32..5fc7fbd 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -716,6 +716,1256 @@ new_objtree_for_rolespec(RoleSpec *spec)
 }
 
 /*
+ * 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_CreateTrigStmt
+ *		Deparse a CreateTrigStmt (CREATE TRIGGER)
+ *
+ * Given a trigger OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
+{
+	CreateTrigStmt *node = (CreateTrigStmt *) parsetree;
+	Relation	pg_trigger;
+	HeapTuple	trigTup;
+	Form_pg_trigger trigForm;
+	ObjTree	   *trigger;
+	ObjTree	   *tmp;
+	int			tgnargs;
+	List	   *list;
+	List	   *events;
+
+	pg_trigger = heap_open(TriggerRelationId, AccessShareLock);
+
+	trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+	trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+	/*
+	 * Some of the elements only make sense for CONSTRAINT TRIGGERs, but it
+	 * seems simpler to use a single fmt string for both kinds of triggers.
+	 */
+	trigger =
+		new_objtree_VA("CREATE %{constraint}s TRIGGER %{name}I %{time}s %{events: OR }s "
+					   "ON %{relation}D %{from_table}s %{constraint_attrs: }s "
+					   "FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE %{function}s",
+					   2,
+					   "name", ObjTypeString, node->trigname,
+					   "constraint", ObjTypeString,
+					   node->isconstraint ? "CONSTRAINT" : "");
+
+	if (node->timing == TRIGGER_TYPE_BEFORE)
+		append_string_object(trigger, "time", "BEFORE");
+	else if (node->timing == TRIGGER_TYPE_AFTER)
+		append_string_object(trigger, "time", "AFTER");
+	else if (node->timing == TRIGGER_TYPE_INSTEAD)
+		append_string_object(trigger, "time", "INSTEAD OF");
+	else
+		elog(ERROR, "unrecognized trigger timing value %d", node->timing);
+
+	/*
+	 * Decode the events that the trigger fires for.  The output is a list;
+	 * in most cases it will just be a string with the even name, but when
+	 * there's an UPDATE with a list of columns, we return a JSON object.
+	 */
+	events = NIL;
+	if (node->events & TRIGGER_TYPE_INSERT)
+		events = lappend(events, new_string_object("INSERT"));
+	if (node->events & TRIGGER_TYPE_DELETE)
+		events = lappend(events, new_string_object("DELETE"));
+	if (node->events & TRIGGER_TYPE_TRUNCATE)
+		events = lappend(events, new_string_object("TRUNCATE"));
+	if (node->events & TRIGGER_TYPE_UPDATE)
+	{
+		if (node->columns == NIL)
+		{
+			events = lappend(events, new_string_object("UPDATE"));
+		}
+		else
+		{
+			ObjTree	   *update;
+			ListCell   *cell;
+			List	   *cols = NIL;
+
+			/*
+			 * Currently only UPDATE OF can be objects in the output JSON, but
+			 * we add a "kind" element so that user code can distinguish
+			 * possible future new event types.
+			 */
+			update = new_objtree_VA("UPDATE OF %{columns:, }I",
+									1, "kind", ObjTypeString, "update_of");
+
+			foreach(cell, node->columns)
+			{
+				char   *colname = strVal(lfirst(cell));
+
+				cols = lappend(cols,
+							   new_string_object(colname));
+			}
+
+			append_array_object(update, "columns", cols);
+
+			events = lappend(events,
+							 new_object_object(update));
+		}
+	}
+	append_array_object(trigger, "events", events);
+
+	tmp = new_objtree_for_qualname_id(RelationRelationId,
+									  trigForm->tgrelid);
+	append_object_object(trigger, "relation", tmp);
+
+	tmp = new_objtree_VA("FROM %{relation}D", 0);
+	if (trigForm->tgconstrrelid)
+	{
+		ObjTree	   *rel;
+
+		rel = new_objtree_for_qualname_id(RelationRelationId,
+										  trigForm->tgconstrrelid);
+		append_object_object(tmp, "relation", rel);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "from_table", tmp);
+
+	list = NIL;
+	if (node->deferrable)
+		list = lappend(list,
+					   new_string_object("DEFERRABLE"));
+	if (node->initdeferred)
+		list = lappend(list,
+					   new_string_object("INITIALLY DEFERRED"));
+	append_array_object(trigger, "constraint_attrs", list);
+
+	append_string_object(trigger, "for_each",
+						 node->row ? "ROW" : "STATEMENT");
+
+	tmp = new_objtree_VA("WHEN (%{clause}s)", 0);
+	if (node->whenClause)
+	{
+		Node	   *whenClause;
+		Datum		value;
+		bool		isnull;
+
+		value = fastgetattr(trigTup, Anum_pg_trigger_tgqual,
+							RelationGetDescr(pg_trigger), &isnull);
+		if (isnull)
+			elog(ERROR, "bogus NULL tgqual");
+
+		whenClause = stringToNode(TextDatumGetCString(value));
+		append_string_object(tmp, "clause",
+							 pg_get_trigger_whenclause(trigForm,
+													   whenClause,
+													   false));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "when", tmp);
+
+	tmp = new_objtree_VA("%{funcname}D(%{args:, }L)",
+						 1, "funcname", ObjTypeObject,
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 trigForm->tgfoid));
+	list = NIL;
+	tgnargs = trigForm->tgnargs;
+	if (tgnargs > 0)
+	{
+		bytea  *tgargs;
+		char   *argstr;
+		bool	isnull;
+		int		findx;
+		int		lentgargs;
+		char   *p;
+
+		tgargs = DatumGetByteaP(fastgetattr(trigTup,
+											Anum_pg_trigger_tgargs,
+											RelationGetDescr(pg_trigger),
+											&isnull));
+		if (isnull)
+			elog(ERROR, "invalid NULL tgargs");
+		argstr = (char *) VARDATA(tgargs);
+		lentgargs = VARSIZE_ANY_EXHDR(tgargs);
+
+		p = argstr;
+		for (findx = 0; findx < tgnargs; findx++)
+		{
+			size_t	tlen;
+
+			/* verify that the argument encoding is correct */
+			tlen = strlen(p);
+			if (p + tlen >= argstr + lentgargs)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid argument string (%s) for trigger \"%s\"",
+								argstr, NameStr(trigForm->tgname))));
+
+			list = lappend(list, new_string_object(p));
+
+			p += tlen + 1;
+		}
+	}
+
+	append_array_object(tmp, "args", list);		/* might be NIL */
+
+	append_object_object(trigger, "function", tmp);
+
+	heap_close(pg_trigger, AccessShareLock);
+
+	return trigger;
+}
+
+/*
+ * 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(Relation relation, List *dpcontext, bool composite,
+				  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);
+
+	/* Composite types use a slightly simpler format string */
+	if (composite)
+		column = new_objtree_VA("%{name}I %{coltype}T %{collation}s",
+								3,
+								"type", ObjTypeString, "column",
+								"name", ObjTypeString, coldef->colname,
+								"coltype", ObjTypeObject,
+								new_objtree_for_type(typid, typmod));
+	else
+		column = new_objtree_VA("%{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(typid, typmod));
+
+	tmp = new_objtree_VA("COLLATE %{name}D", 0);
+	if (OidIsValid(typcollation))
+	{
+		ObjTree *collname;
+
+		collname = new_objtree_for_qualname_id(CollationRelationId,
+											   typcollation);
+		append_object_object(tmp, "name", collname);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(column, "collation", tmp);
+
+	if (!composite)
+	{
+		/*
+		 * 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.  Therefore,
+		 * we scan the list of constraints attached to this column to determine
+		 * whether we need to emit anything.
+		 * (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("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(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("%{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(Relation relation, List *tableElements, List *dpcontext,
+					 bool typed, bool composite)
+{
+	List	   *elements = NIL;
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					ObjTree	   *tree;
+
+					tree = typed ?
+						deparse_ColumnDef_typed(relation, dpcontext,
+												(ColumnDef *) elt) :
+						deparse_ColumnDef(relation, dpcontext,
+										  composite, (ColumnDef *) elt);
+					if (tree != NULL)
+					{
+						ObjElem    *column;
+
+						column = new_object_object(tree);
+						elements = lappend(elements, column);
+					}
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+
+	return elements;
+}
+
+/*
+ * obtainConstraints
+ *		Subroutine for CREATE TABLE/CREATE DOMAIN deparsing
+ *
+ * Given a table OID or domain OID, obtain its constraints and append them to
+ * the given elements list.  The updated list is returned.
+ *
+ * This works for typed tables, regular tables, and domains.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static List *
+obtainConstraints(List *elements, Oid relationId, Oid domainId)
+{
+	Relation	conRel;
+	ScanKeyData key;
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	ObjTree    *tmp;
+
+	/* only one may be valid */
+	Assert(OidIsValid(relationId) ^ OidIsValid(domainId));
+
+	/*
+	 * scan pg_constraint to fetch all constraints linked to the given
+	 * relation.
+	 */
+	conRel = heap_open(ConstraintRelationId, AccessShareLock);
+	if (OidIsValid(relationId))
+	{
+		ScanKeyInit(&key,
+					Anum_pg_constraint_conrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relationId));
+		scan = systable_beginscan(conRel, ConstraintRelidIndexId,
+								  true, NULL, 1, &key);
+	}
+	else
+	{
+		Assert(OidIsValid(domainId));
+		ScanKeyInit(&key,
+					Anum_pg_constraint_contypid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(domainId));
+		scan = systable_beginscan(conRel, ConstraintTypidIndexId,
+								  true, NULL, 1, &key);
+	}
+
+	/*
+	 * 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:
+				continue;	/* not here */
+			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("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, new_object_object(tmp));
+	}
+
+	systable_endscan(scan);
+	heap_close(conRel, AccessShareLock);
+
+	return elements;
+}
+
+/*
+ * deparse the ON COMMMIT ... clause for CREATE ... TEMPORARY ...
+ */
+static ObjTree *
+deparse_OnCommitClause(OnCommitAction option)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0);
+	switch (option)
+	{
+		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;
+	}
+
+	return tmp;
+}
+
+/*
+ * Deparse DefElems, as used e.g. by ALTER COLUMN ... SET, into a list of SET
+ * (...)  or RESET (...) contents.
+ */
+static ObjTree *
+deparse_DefElem(DefElem *elem, bool is_reset)
+{
+	ObjTree	   *set;
+	ObjTree	   *optname;
+
+	if (elem->defnamespace != NULL)
+		optname = new_objtree_VA("%{schema}I.%{label}I", 1,
+								 "schema", ObjTypeString, elem->defnamespace);
+	else
+		optname = new_objtree_VA("%{label}I", 0);
+
+	append_string_object(optname, "label", elem->defname);
+
+	if (is_reset)
+		set = new_objtree_VA("%{label}s", 0);
+	else
+		set = new_objtree_VA("%{label}s = %{value}L", 1,
+							 "value", ObjTypeString,
+							 elem->arg ? defGetString(elem) :
+							 defGetBoolean(elem) ? "TRUE" : "FALSE");
+
+	append_object_object(set, "label", optname);
+	return set;
+}
+
+/*
+ * deparse_CreateStmt
+ *		Deparse a CreateStmt (CREATE TABLE)
+ *
+ * Given a table OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *list;
+	ListCell   *cell;
+	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 %{if_not_exists}s %{identity}D "
+			"OF %{of_type}T %{table_elements}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) %{inherits}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+
+	createStmt =
+		new_objtree_VA(fmtstr, 1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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(relation->rd_rel->reloftype, -1);
+		append_object_object(createStmt, "of_type", tmp);
+
+		tableelts = deparseTableElements(relation, node->tableElts, dpcontext,
+										 true,		/* typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+		if (tableelts == NIL)
+			tmp = new_objtree_VA("", 1,
+								 "present", ObjTypeBool, false);
+		else
+			tmp = new_objtree_VA("(%{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(relation, node->tableElts, dpcontext,
+										 false,		/* not typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+		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("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(RelationRelationId,
+													 formInh->inhparent);
+				parents = lappend(parents, new_object_object(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("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);
+
+	append_object_object(createStmt, "on_commit",
+						 deparse_OnCommitClause(node->oncommit));
+
+	/*
+	 * WITH clause.  We always emit one, containing at least the OIDS option.
+	 * That way we don't depend on the default value for default_with_oids.
+	 * We can skip emitting other options if there don't appear in the parse
+	 * node.
+	 */
+	tmp = new_objtree_VA("oids=%{value}s", 2,
+						 "option", ObjTypeString, "oids",
+						 "value", ObjTypeString,
+						 relation->rd_rel->relhasoids ? "ON" : "OFF");
+	list = list_make1(new_object_object(tmp));
+
+	foreach(cell, node->options)
+	{
+		DefElem	*opt = (DefElem *) lfirst(cell);
+
+		/* already handled above */
+		if (strcmp(opt->defname, "oids") == 0)
+			continue;
+
+		tmp = deparse_DefElem(opt, false);
+		list = lappend(list, new_object_object(tmp));
+	}
+	append_array_object(createStmt, "with", list);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
+/*
+ * deparse_CompositeTypeStmt
+ *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
+{
+	CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree;
+	ObjTree	   *composite;
+	HeapTuple	typtup;
+	Form_pg_type typform;
+	Relation	typerel;
+	List	   *dpcontext;
+	List	   *tableelts = NIL;
+
+	/* Find the pg_type entry and open the corresponding relation */
+	typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(typtup))
+		elog(ERROR, "cache lookup failed for type %u", objectId);
+	typform = (Form_pg_type) GETSTRUCT(typtup);
+	typerel = relation_open(typform->typrelid, AccessShareLock);
+
+	dpcontext = deparse_context_for(RelationGetRelationName(typerel),
+									RelationGetRelid(typerel));
+
+	composite = new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)",
+							   0);
+	append_object_object(composite, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+
+	tableelts = deparseTableElements(typerel, node->coldeflist, dpcontext,
+									 false,		/* not typed */
+									 true);		/* composite type */
+
+	append_array_object(composite, "columns", tableelts);
+
+	heap_close(typerel, AccessShareLock);
+	ReleaseSysCache(typtup);
+
+	return composite;
+}
+
+static inline ObjElem *
+deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->cache_value);
+	tmp = new_objtree_VA("CACHE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "cache",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Cycle(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA("%{no}s CYCLE",
+						 2,
+						 "clause", ObjTypeString, "cycle",
+						 "no", ObjTypeString,
+						 seqdata->is_cycled ? "" : "NO");
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_IncrementBy(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->increment_by);
+	tmp = new_objtree_VA("INCREMENT BY %{value}s",
+						 2,
+						 "clause", ObjTypeString, "increment_by",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Minvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->min_value);
+	tmp = new_objtree_VA("MINVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "minvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Maxvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->max_value);
+	tmp = new_objtree_VA("MAXVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "maxvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Startwith(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->start_value);
+	tmp = new_objtree_VA("START WITH %{value}s",
+						 2,
+						 "clause", ObjTypeString, "start",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Restart(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->last_value);
+	tmp = new_objtree_VA("RESTART %{value}s",
+						 2,
+						 "clause", ObjTypeString, "restart",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static ObjElem *
+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(RelationRelationId, ownerId);
+		append_string_object(tmp, "attrname", colname);
+		ownedby = new_objtree_VA("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("OWNED BY %{owner}D",
+								 3,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeNull,
+								 "present", ObjTypeBool, false);
+	return new_object_object(ownedby);
+}
+
+/*
+ * deparse_CreateSeqStmt
+ *		deparse a CreateSeqStmt
+ *
+ * Given a sequence OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *createSeq;
+	ObjTree    *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+
+	seqdata = get_sequence_values(objectId);
+
+	createSeq =
+		new_objtree_VA("CREATE %{persistence}s SEQUENCE %{identity}D "
+					   "%{definition: }s",
+					   1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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);
+
+	relation_close(relation, AccessShareLock);
+
+	return createSeq;
+}
+
+/*
+ * deparse_AlterSeqStmt
+ *		deparse an AlterSeqStmt
+ *
+ * Given a sequence OID and a parsetree that modified it, return an ObjTree
+ * representing the alter command.
+ */
+static ObjTree *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *alterSeq;
+	ObjTree	   *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+	ListCell   *cell;
+
+	seqdata = get_sequence_values(objectId);
+
+	alterSeq =
+		new_objtree_VA("ALTER SEQUENCE %{identity}D %{definition: }s", 0);
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(alterSeq, "identity", tmp);
+
+	foreach(cell, ((AlterSeqStmt *) parsetree)->options)
+	{
+		DefElem *elem = (DefElem *) lfirst(cell);
+		ObjElem *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") == 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);
+
+	relation_close(relation, AccessShareLock);
+
+	return alterSeq;
+}
+
+/*
+ * deparse_IndexStmt
+ *		deparse an IndexStmt
+ *
+ * Given an index OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ */
+static ObjTree *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+	IndexStmt  *node = (IndexStmt *) parsetree;
+	ObjTree    *indexStmt;
+	ObjTree    *tmp;
+	Relation	idxrel;
+	Relation	heaprel;
+	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);
+
+	pg_get_indexdef_detailed(objectId,
+							 &index_am, &definition, &reloptions,
+							 &tablespace, &whereClause);
+
+	indexStmt =
+		new_objtree_VA("CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   6,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "if_not_exists", ObjTypeString, node->if_not_exists ? "IF NOT EXISTS" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(heaprel->rd_rel->relnamespace,
+								   RelationGetRelationName(heaprel));
+	append_object_object(indexStmt, "table", tmp);
+
+	/* reloptions */
+	tmp = new_objtree_VA("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("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("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);
+
+	heap_close(idxrel, AccessShareLock);
+	heap_close(heaprel, AccessShareLock);
+
+	return indexStmt;
+}
+
+/*
+ * deparse_CreateSchemaStmt
+ *		deparse a CreateSchemaStmt
+ *
+ * Given a schema OID and the parsetree that created it, return an ObjTree
+ * 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 ObjTree *
+deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
+{
+	CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
+	ObjTree    *createSchema;
+	ObjTree    *auth;
+
+	createSchema =
+		new_objtree_VA("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("AUTHORIZATION %{authorization_role}I", 0);
+	if (node->authrole)
+		append_string_object(auth, "authorization_role",
+							 get_rolespec_name(node->authrole));
+	else
+	{
+		append_null_object(auth, "authorization_role");
+		append_bool_object(auth, "present", false);
+	}
+	append_object_object(createSchema, "authorization", auth);
+
+	return createSchema;
+}
+
+/*
  * Handle deparsing of simple commands.
  *
  * This function contains a large switch that mirrors that in
@@ -737,11 +1987,11 @@ deparse_simple_command(StashedCommand *cmd)
 	switch (nodeTag(parsetree))
 	{
 		case T_CreateSchemaStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateSchemaStmt(objectId, parsetree);
 			break;
 
 		case T_CreateStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateStmt(objectId, parsetree);
 			break;
 
 		case T_CreateForeignTableStmt:
@@ -764,7 +2014,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_IndexStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_IndexStmt(objectId, parsetree);
 			break;
 
 		case T_CreateExtensionStmt:
@@ -814,7 +2064,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CompositeTypeStmt(objectId, parsetree);
 			break;
 
 		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
@@ -846,11 +2096,11 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateSeqStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateSeqStmt(objectId, parsetree);
 			break;
 
 		case T_AlterSeqStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterSeqStmt(objectId, parsetree);
 			break;
 
 		case T_CreateTableAsStmt:
@@ -863,7 +2113,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateTrigStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateTrigStmt(objectId, parsetree);
 			break;
 
 		case T_CreatePLangStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b7a81ec..d9aab2d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -821,59 +821,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char	   *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.ctes = NIL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
@@ -914,6 +867,63 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char		relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+	initStringInfo(&buf);
+
+	relkind = get_rel_relkind(trigrec->tgrelid);
+
+	/* Build minimal OLD and NEW RTEs for the rel */
+	oldrte = makeNode(RangeTblEntry);
+	oldrte->rtekind = RTE_RELATION;
+	oldrte->relid = trigrec->tgrelid;
+	oldrte->relkind = relkind;
+	oldrte->alias = makeAlias("old", NIL);
+	oldrte->eref = oldrte->alias;
+	oldrte->lateral = false;
+	oldrte->inh = false;
+	oldrte->inFromCl = true;
+
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_RELATION;
+	newrte->relid = trigrec->tgrelid;
+	newrte->relkind = relkind;
+	newrte->alias = makeAlias("new", NIL);
+	newrte->eref = newrte->alias;
+	newrte->lateral = false;
+	newrte->inh = false;
+	newrte->inFromCl = true;
+
+	/* Build two-element rtable */
+	memset(&dpns, 0, sizeof(dpns));
+	dpns.rtable = list_make2(oldrte, newrte);
+	dpns.ctes = NIL;
+	set_rtable_names(&dpns, NIL, NULL);
+	set_simple_column_names(&dpns);
+
+	/* Set up context with one-deep namespace stack */
+	context.buf = &buf;
+	context.namespaces = list_make1(&dpns);
+	context.windowClause = NIL;
+	context.windowTList = NIL;
+	context.varprefix = true;
+	context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+	context.wrapColumn = WRAP_COLUMN_DEFAULT;
+	context.indentLevel = PRETTYINDENT_STD;
+
+	get_rule_expr(whenClause, &context, false);
+
+	return buf.data;
+}
+
 /* ----------
  * get_indexdef			- Get the definition of an index
  *
@@ -977,6 +987,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,
@@ -1256,6 +1268,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
@@ -1290,9 +1541,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);
 }
 
 /*
@@ -9388,3 +9639,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/commands/sequence.h b/src/include/commands/sequence.h
index 44862bb..8d8e556 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -72,6 +72,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 ObjectAddress DefineSequence(CreateSeqStmt *stmt);
 extern ObjectAddress AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index fed9c7b..4447af9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,6 +13,7 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
@@ -20,8 +21,16 @@
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+						  Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 
-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);
@@ -32,4 +41,7 @@ extern List *select_rtable_names_for_explain(List *rtable,
 								Bitmapset *rels_used);
 extern char *generate_collation_name(Oid collid);
 
+extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
+						 List *dpcontext);
+
 #endif	/* RULEUTILS_H */
-- 
2.1.4

0008-deparse-Support-CREATE-TYPE-AS-ENUM-RANGE.patchtext/x-diff; charset=us-asciiDownload
>From e13c8cd7e8d7c4adfbd42e7c32f7e109fed336dc Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 25 Sep 2014 15:55:43 -0300
Subject: [PATCH 08/37] deparse: Support CREATE TYPE AS ENUM / RANGE

---
 src/backend/tcop/deparse_utility.c | 134 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 132 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 5fc7fbd..4495007 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1565,6 +1565,136 @@ deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
 	return composite;
 }
 
+/*
+ * deparse_CreateEnumStmt
+ *		Deparse a CreateEnumStmt (CREATE TYPE AS ENUM)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
+{
+	CreateEnumStmt *node = (CreateEnumStmt *) parsetree;
+	ObjTree	   *enumtype;
+	List	   *values;
+	ListCell   *cell;
+
+	enumtype = new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)",
+							  0);
+	append_object_object(enumtype, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	values = NIL;
+	foreach(cell, node->vals)
+	{
+		Value   *val = (Value *) lfirst(cell);
+
+		values = lappend(values, new_string_object(strVal(val)));
+	}
+	append_array_object(enumtype, "values", values);
+
+	return enumtype;
+}
+
+static ObjTree *
+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;
+
+	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(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(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(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(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(tmp));
+	}
+
+	append_array_object(range, "definition", definition);
+
+	systable_endscan(scan);
+	heap_close(pg_range, RowExclusiveLock);
+
+	return range;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -2068,11 +2198,11 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateEnumStmt(objectId, parsetree);
 			break;
 
 		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateRangeStmt(objectId, parsetree);
 			break;
 
 		case T_AlterEnumStmt:
-- 
2.1.4

0009-deparse-Support-EXTENSION-commands.patchtext/x-diff; charset=us-asciiDownload
>From 616e020966e0cd4bf1d531071b37b08fe5b96635 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 09/37] deparse: Support EXTENSION commands

CREATE EXTENSION
ALTER EXTENSION / UPDATE TO
ALTER EXTENSION ADD/DROP
---
 src/backend/tcop/deparse_utility.c | 148 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 145 insertions(+), 3 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 4495007..8fe8ae1 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -735,6 +735,147 @@ 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 ObjTree *
+deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
+{
+	CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *extStmt;
+	ObjTree	   *tmp;
+	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(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(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(tmp));
+
+	append_array_object(extStmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return extStmt;
+}
+
+static ObjTree *
+deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
+{
+	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list = NIL;
+	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 %{options: }s", 1,
+						  "identity", ObjTypeString,
+						  NameStr(extForm->extname));
+
+	foreach(cell, node->options)
+	{
+		DefElem *opt = (DefElem *) lfirst(cell);
+
+		if (strcmp(opt->defname, "new_version") == 0)
+		{
+			tmp = new_objtree_VA("TO %{version}L", 2,
+								 "type", ObjTypeString, "version",
+								 "version", ObjTypeString, defGetString(opt));
+			list = lappend(list, new_object_object(tmp));
+		}
+		else
+			elog(ERROR, "unsupported option %s", opt->defname);
+	}
+
+	append_array_object(stmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree,
+								   ObjectAddress objectAddress)
+{
+	AlterExtensionContentsStmt *node = (AlterExtensionContentsStmt *) parsetree;
+	ObjTree	   *stmt;
+	char	   *fmt;
+
+	Assert(node->action == +1 || node->action == -1);
+
+	fmt = psprintf("ALTER EXTENSION %%{extidentity}I %s %s %%{objidentity}s",
+				   node->action == +1 ? "ADD" : "DROP",
+				   stringify_objtype(node->objtype));
+
+	stmt = new_objtree_VA(fmt, 2, "extidentity", ObjTypeString, node->extname,
+						  "objidentity", ObjTypeString,
+						  getObjectIdentity(&objectAddress));
+
+	return stmt;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -2148,15 +2289,16 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateExtensionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionContentsStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterExtensionContentsStmt(objectId, parsetree,
+														 cmd->d.simple.secondaryObject);
 			break;
 
 		case T_CreateFdwStmt:
-- 
2.1.4

0010-deparse-Support-CREATE-RULE.patchtext/x-diff; charset=us-asciiDownload
>From 8b22bbbc1babb5619d70512a3843a551e8faf7fb 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 10/37] deparse: Support CREATE RULE

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 8fe8ae1..b896fa4 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2196,6 +2196,95 @@ deparse_IndexStmt(Oid objectId, Node *parsetree)
 	return indexStmt;
 }
 
+static ObjTree *
+deparse_RuleStmt(Oid objectId, Node *parsetree)
+{
+	RuleStmt *node = (RuleStmt *) parsetree;
+	ObjTree	   *ruleStmt;
+	ObjTree	   *tmp;
+	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(action));
+	}
+	append_array_object(ruleStmt, "actions", list);
+
+	systable_endscan(scan);
+	heap_close(pg_rewrite, AccessShareLock);
+
+	return ruleStmt;
+}
+
+
+
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -2364,7 +2453,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RuleStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RuleStmt(objectId, parsetree);
 			break;
 
 		case T_CreateSeqStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d9aab2d..169119c 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 4447af9..9989cdd 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -30,6 +30,8 @@ extern void pg_get_indexdef_detailed(Oid indexrelid,
 extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 						  Node *whenClause, bool pretty);
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+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);
-- 
2.1.4

0011-deparse-Support-ALTER-TYPE-ADD-VALUE-enums.patchtext/x-diff; charset=us-asciiDownload
>From 7281a1dcd6356b255d13383fd5d16cae47f195e1 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 11/37] deparse: Support ALTER TYPE / ADD VALUE (enums)

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index b896fa4..4828cb1 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2325,6 +2325,39 @@ deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
 	return createSchema;
 }
 
+static ObjTree *
+deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
+{
+	AlterEnumStmt *node = (AlterEnumStmt *) parsetree;
+	ObjTree	   *alterEnum;
+	ObjTree	   *tmp;
+
+	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);
+
+	return alterEnum;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -2437,7 +2470,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterEnumStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterEnumStmt(objectId, parsetree);
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-- 
2.1.4

0012-deparse-Support-ALTER-.-RENAME-OWNER-SCHEMA.patchtext/x-diff; charset=us-asciiDownload
>From c35b9b6f31fefc228274844bb7c9ded602d3de38 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 12/37] deparse: Support ALTER .. {RENAME/OWNER/SCHEMA}

---
 src/backend/commands/alter.c       |   1 -
 src/backend/tcop/deparse_utility.c | 483 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/regproc.c    | 101 +++++---
 src/include/utils/builtins.h       |   1 +
 4 files changed, 553 insertions(+), 33 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 3ddd7ec..d28758c 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -446,7 +446,6 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 				Relation	relation;
 				Oid			classId;
 				Oid			nspOid;
-				ObjectAddress address;
 
 				address = get_object_address(stmt->objectType,
 											 stmt->object,
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 4828cb1..ec36834 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -145,6 +145,7 @@ static void append_integer_object(ObjTree *tree, char *name, int64 value);
 static void append_float_object(ObjTree *tree, char *name, float8 value);
 static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
 static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+static const char *stringify_objtype(ObjectType objtype);
 
 /*
  * Allocate a new object tree to store parameter values.
@@ -1836,6 +1837,478 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return range;
 }
 
+/*
+ * Return the given object type as a string.
+ */
+static const char *
+stringify_objtype(ObjectType objtype)
+{
+	switch (objtype)
+	{
+		case OBJECT_AGGREGATE:
+			return "AGGREGATE";
+		case OBJECT_CAST:
+			return "CAST";
+		case OBJECT_COLUMN:
+			return "COLUMN";
+		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 "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_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_USER_MAPPING:
+			return "USER MAPPING";
+		case OBJECT_VIEW:
+			return "VIEW";
+
+		default:
+			elog(ERROR, "unsupported object type %d", objtype);
+	}
+}
+
+static ObjTree *
+deparse_RenameStmt(ObjectAddress address, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree	   *renameStmt;
+	char	   *fmtstr;
+	Relation	relation;
+	Oid			schemaId;
+
+	/*
+	 * 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(address.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_ATTRIBUTE:
+		case OBJECT_COLUMN:
+			relation = relation_open(address.objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			if (node->renameType == OBJECT_ATTRIBUTE)
+			{
+				fmtstr = "ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I TO %{newname}I %{cascade}s";
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_object_object(renameStmt, "cascade",
+									 new_objtree_VA("CASCADE", 1,
+													"present", ObjTypeBool,
+													node->behavior == DROP_CASCADE));
+			}
+			else
+			{
+				fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME COLUMN %%{colname}I TO %%{newname}I",
+								  stringify_objtype(node->relationType));
+				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);
+
+			/* composite types do not support IF EXISTS */
+			if (node->relationType != OBJECT_TYPE)
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+
+			break;
+
+		case OBJECT_SCHEMA:
+			{
+				renameStmt =
+					new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I",
+								   0);
+				append_string_object(renameStmt, "identity", node->subname);
+			}
+			break;
+
+		case OBJECT_FDW:
+		case OBJECT_LANGUAGE:
+		case OBJECT_FOREIGN_SERVER:
+			{
+				fmtstr = psprintf("ALTER %s %%{identity}I RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_string_object(renameStmt, "identity",
+									 strVal(linitial(node->object)));
+			}
+			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:
+			{
+				HeapTuple	objTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				AttrNumber	Anum_namespace;
+				ObjTree	   *ident;
+
+				/* obtain object tuple */
+				catalog = relation_open(address.classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, address.objectId);
+
+				/* obtain namespace */
+				Anum_namespace = get_object_attnum_namespace(address.classId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				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(DatumGetObjectId(objnsp),
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+
+				relation_close(catalog, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_OPCLASS:
+		case OBJECT_OPFAMILY:
+			{
+				HeapTuple	objTup;
+				HeapTuple	amTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				AttrNumber	Anum_namespace;
+				Oid			amoid;
+				ObjTree	   *ident;
+
+				/* obtain object tuple */
+				catalog = relation_open(address.classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, address.objectId);
+
+				/* obtain namespace */
+				Anum_namespace = get_object_attnum_namespace(address.classId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				/* obtain AM tuple */
+				if (node->renameType == OBJECT_OPCLASS)
+					amoid = ((Form_pg_opclass) GETSTRUCT(objTup))->opcmethod;
+				else
+					amoid = ((Form_pg_opfamily) GETSTRUCT(objTup))->opfmethod;
+				amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+				if (!HeapTupleIsValid(amTup))
+					elog(ERROR, "cache lookup failed for access method %u", amoid);
+
+
+				fmtstr = psprintf("ALTER %s %%{identity}D USING %%{amname}I RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+
+				/* add the identity clauses */
+				ident = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+				append_string_object(renameStmt, "amname",
+									 pstrdup(NameStr(((Form_pg_am) GETSTRUCT(amTup))->amname)));
+
+				ReleaseSysCache(amTup);
+				relation_close(catalog, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			{
+				char	*ident;
+				HeapTuple proctup;
+				Form_pg_proc procform;
+
+				proctup = SearchSysCache1(PROCOID,
+										  ObjectIdGetDatum(address.objectId));
+				if (!HeapTupleIsValid(proctup))
+					elog(ERROR, "cache lookup failed for procedure %u",
+						 address.objectId);
+				procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+				/* XXX does this work for ordered-set aggregates? */
+				ident = psprintf("%s%s",
+								 quote_qualified_identifier(get_namespace_name(procform->pronamespace),
+															strVal(llast(node->object))),
+								 format_procedure_args(address.objectId, true));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 1,
+											"identity", ObjTypeString, ident);
+
+				ReleaseSysCache(proctup);
+			}
+			break;
+
+		case OBJECT_TABCONSTRAINT:
+		case OBJECT_DOMCONSTRAINT:
+			{
+				HeapTuple		conTup;
+				Form_pg_constraint	constrForm;
+				ObjTree		   *ident;
+
+				conTup = SearchSysCache1(CONSTROID, address.objectId);
+				constrForm = (Form_pg_constraint) GETSTRUCT(conTup);
+
+				if (node->renameType == OBJECT_TABCONSTRAINT)
+				{
+					fmtstr = "ALTER TABLE %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(RelationRelationId,
+														constrForm->conrelid);
+				}
+				else
+				{
+					fmtstr = "ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(TypeRelationId,
+														constrForm->contypid);
+				}
+				renameStmt = new_objtree_VA(fmtstr, 2,
+											"conname", ObjTypeString, node->subname,
+											"identity", ObjTypeObject, ident);
+				ReleaseSysCache(conTup);
+			}
+			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, address.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, address.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;
+
+		case OBJECT_POLICY:
+			{
+				HeapTuple	polTup;
+				Form_pg_policy polForm;
+				Relation	pg_policy;
+				ScanKeyData	key;
+				SysScanDesc	scan;
+
+				pg_policy = relation_open(PolicyRelationId, AccessShareLock);
+				ScanKeyInit(&key, ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(address.objectId));
+				scan = systable_beginscan(pg_policy, PolicyOidIndexId, true,
+										  NULL, 1, &key);
+				polTup = systable_getnext(scan);
+				if (!HeapTupleIsValid(polTup))
+					elog(ERROR, "cache lookup failed for policy %u", address.objectId);
+				polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+				renameStmt = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I on %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "policyname", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 polForm->polrelid));
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+				systable_endscan(scan);
+				relation_close(pg_policy, AccessShareLock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	append_string_object(renameStmt, "newname", node->newname);
+
+	return renameStmt;
+}
+
+/*
+ * deparse an ALTER ... SET SCHEMA command.
+ */
+static ObjTree *
+deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree,
+							  ObjectAddress oldschema)
+{
+	AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree;
+	ObjTree	   *alterStmt;
+	char	   *fmt;
+	char	   *identity;
+	char	   *newschema;
+	char	   *oldschname;
+	char	   *ident;
+
+	newschema = node->newschema;
+
+	fmt = psprintf("ALTER %s %%{identity}s SET SCHEMA %%{newschema}I",
+				   stringify_objtype(node->objectType));
+	alterStmt = new_objtree_VA(fmt, 0);
+	append_string_object(alterStmt, "newschema", newschema);
+
+	/*
+	 * Since the command has already taken place from the point of view of
+	 * catalogs, getObjectIdentity returns the object name with the already
+	 * changed schema.  The output of our deparsing must return the original
+	 * schema name however, so we chop the schema name off the identity string
+	 * and then prepend the quoted schema name.
+	 *
+	 * XXX This is pretty clunky. Can we do better?
+	 */
+	identity = getObjectIdentity(&address);
+	oldschname = get_namespace_name(oldschema.objectId);
+	if (!oldschname)
+		elog(ERROR, "cache lookup failed for schema with OID %u", oldschema.objectId);
+	ident = psprintf("%s%s",
+					 quote_identifier(oldschname),
+					 identity + strlen(quote_identifier(newschema)));
+	append_string_object(alterStmt, "identity", ident);
+
+	return alterStmt;
+}
+
+static ObjTree *
+deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+	ObjTree	   *ownerStmt;
+	char	   *fmt;
+
+	fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newowner}I",
+				   stringify_objtype(node->objectType));
+	ownerStmt = new_objtree_VA(fmt, 0);
+	append_string_object(ownerStmt, "newowner",
+						 get_rolespec_name(node->newowner));
+
+	append_string_object(ownerStmt, "identity",
+						 getObjectIdentity(&address));
+
+	return ownerStmt;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -2283,8 +2756,6 @@ deparse_RuleStmt(Oid objectId, Node *parsetree)
 	return ruleStmt;
 }
 
-
-
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -2552,15 +3023,17 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RenameStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RenameStmt(cmd->d.simple.address, parsetree);
 			break;
 
 		case T_AlterObjectSchemaStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
+													parsetree,
+													cmd->d.simple.secondaryObject);
 			break;
 
 		case T_AlterOwnerStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree);
 			break;
 
 		case T_CommentStmt:
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index ebb36e9..6e2e1e2 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -42,7 +42,11 @@
 #include "utils/tqual.h"
 
 static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
+static char *format_procedure_internal(Oid procedure_oid, bool force_qualify,
+						  bool args_only);
+static void format_procedure_args_internal(Form_pg_proc procform,
+							   StringInfo buf, bool force_qualify);
+
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -363,13 +367,36 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_internal(procedure_oid, false, false);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_internal(procedure_oid, true, false);
+}
+
+/*
+ * format_procedure_args	- converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+	StringInfoData buf;
+	HeapTuple	proctup;
+	Form_pg_proc procform;
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+	initStringInfo(&buf);
+	format_procedure_args_internal(procform, &buf, force_qualify);
+
+	ReleaseSysCache(proctup);
+
+	return buf.data;
 }
 
 /*
@@ -380,7 +407,7 @@ format_procedure_qualified(Oid procedure_oid)
  * qualified if the function is not in path.
  */
 static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+format_procedure_internal(Oid procedure_oid, bool force_qualify, bool args_only)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -391,8 +418,6 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 	{
 		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
 		char	   *proname = NameStr(procform->proname);
-		int			nargs = procform->pronargs;
-		int			i;
 		char	   *nspname;
 		StringInfoData buf;
 
@@ -400,29 +425,24 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		initStringInfo(&buf);
 
-		/*
-		 * Would this proc be found (given the right args) by regprocedurein?
-		 * If not, or if caller requests it, we need to qualify it.
-		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
-			nspname = NULL;
-		else
-			nspname = get_namespace_name(procform->pronamespace);
-
-		appendStringInfo(&buf, "%s(",
-						 quote_qualified_identifier(nspname, proname));
-		for (i = 0; i < nargs; i++)
+		if (!args_only)
 		{
-			Oid			thisargtype = procform->proargtypes.values[i];
-
-			if (i > 0)
-				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf,
-								   force_qualify ?
-								   format_type_be_qualified(thisargtype) :
-								   format_type_be(thisargtype));
+			/*
+			 * Would this proc be found (given the right args) by
+			 * regprocedurein?  If not, or if caller requests it, we need to
+			 * qualify it.
+			 */
+			if (!force_qualify && FunctionIsVisible(procedure_oid))
+				nspname = NULL;
+			else
+				nspname = get_namespace_name(procform->pronamespace);
+
+			appendStringInfo(&buf, "%s",
+							 quote_qualified_identifier(nspname, proname));
 		}
-		appendStringInfoChar(&buf, ')');
+
+		/* add the attributes */
+		format_procedure_args_internal(procform, &buf, force_qualify);
 
 		result = buf.data;
 
@@ -439,6 +459,33 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 }
 
 /*
+ * Append the parenthised arguments of the given pg_proc row into the output
+ * buffer.  force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+							   bool force_qualify)
+{
+	int			i;
+	int			nargs = procform->pronargs;
+
+	appendStringInfoChar(buf, '(');
+	for (i = 0; i < nargs; i++)
+	{
+		Oid			thisargtype = procform->proargtypes.values[i];
+
+		if (i > 0)
+			appendStringInfoChar(buf, ',');
+		appendStringInfoString(buf,
+							   force_qualify ?
+							   format_type_be_qualified(thisargtype) :
+							   format_type_be(thisargtype));
+	}
+	appendStringInfoChar(buf, ')');
+}
+
+/*
  * Output a objname/objargs representation for the procedure with the
  * given OID.  If it doesn't exist, an error is thrown.
  *
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index a4fd958..8888394 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -644,6 +644,7 @@ extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
 					  List **objargs);
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-- 
2.1.4

0023-deparse-Handle-default-security-provider.patchtext/x-diff; charset=us-asciiDownload
>From 571e64592c7b9731bcc8e582d70cf21c40f7cd5a Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Sat, 20 Dec 2014 08:56:54 +0100
Subject: [PATCH 23/37] deparse: Handle default security provider.

---
 src/backend/commands/seclabel.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 1ef98ce..aa4de97 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -63,6 +63,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("must specify provider when multiple security label providers have been loaded")));
 		provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
 	}
 	else
 	{
-- 
2.1.4

0024-deparse-support-CREATE-TABLE-AS.patchtext/x-diff; charset=us-asciiDownload
>From 3e6cce52fa18d5b53687e3ce41575812e476745e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:30:04 -0300
Subject: [PATCH 24/37] deparse: support CREATE TABLE AS

---
 src/backend/tcop/deparse_utility.c | 132 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  |  15 +++++
 src/include/utils/ruleutils.h      |   1 +
 3 files changed, 146 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index c84ed08..9c0c684 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4333,6 +4333,135 @@ deparse_RuleStmt(Oid objectId, Node *parsetree)
 	return ruleStmt;
 }
 
+static ObjTree *
+deparse_CreateTableAsStmt(Oid objectId, Node *parsetree)
+{
+	CreateTableAsStmt *node = (CreateTableAsStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	ObjTree	   *createStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	char	   *fmt;
+	List	   *list;
+	ListCell   *cell;
+
+	/*
+	 * Reject unsupported case right away.
+	 */
+	if (((Query *) (node->query))->commandType == CMD_UTILITY)
+		elog(ERROR, "unimplemented deparse of CREATE TABLE AS EXECUTE");
+
+	/*
+	 * Note that INSERT INTO is deparsed as CREATE TABLE AS.  They are
+	 * functionally equivalent synonyms so there is no harm from this.
+	 */
+	if (node->relkind == OBJECT_MATVIEW)
+		fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with_clause}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+	else
+		fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with_clause}s %{on_commit}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+
+	createStmt =
+		new_objtree_VA(fmt, 2,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(node->into->rel->relpersistence),
+					   "if_not_exists", ObjTypeString,
+					   node->if_not_exists ? "IF NOT EXISTS" : "");
+	append_object_object(createStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+
+	/* Add an ON COMMIT clause.  CREATE MATERIALIZED VIEW doesn't have one */
+	if (node->relkind == OBJECT_TABLE)
+	{
+		append_object_object(createStmt, "on_commit",
+							 deparse_OnCommitClause(node->into->onCommit));
+	}
+
+	/* add a TABLESPACE clause */
+	tmp = new_objtree_VA("TABLESPACE %{tablespace}I", 0);
+	if (node->into->tableSpaceName)
+		append_string_object(tmp, "tablespace", node->into->tableSpaceName);
+	else
+	{
+		append_null_object(tmp, "tablespace");
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(createStmt, "tablespace", tmp);
+
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->into->skipData ? true : false);
+	append_object_object(createStmt, "with_no_data", tmp);
+
+	/* add the column list, if any */
+	if (node->into->colNames == NIL)
+		tmp = new_objtree_VA("", 1,
+							 "present", ObjTypeBool, false);
+	else
+	{
+		ListCell *cell;
+
+		list = NIL;
+		foreach (cell, node->into->colNames)
+			list = lappend(list, new_string_object(strVal(lfirst(cell))));
+
+		tmp = new_objtree_VA("(%{columns:, }I)", 1,
+							 "columns", ObjTypeArray, list);
+	}
+	append_object_object(createStmt, "columns", tmp);
+
+	/* add the query */
+	Assert(IsA(node->query, Query));
+	append_string_object(createStmt, "query",
+						 pg_get_createtableas_def((Query *) node->query));
+
+
+	/*
+	 * WITH clause.  In CREATE TABLE AS, like for CREATE TABLE, we always emit
+	 * one, containing at least the OIDS option; CREATE MATERIALIZED VIEW
+	 * doesn't support the oids option, so we have to make the clause reduce to
+	 * empty if no other options are specified.
+	 */
+	tmp = new_objtree_VA("WITH (%{with:, }s)", 0);
+	if (node->relkind == OBJECT_MATVIEW && node->into->options == NIL)
+		append_bool_object(tmp, "present", false);
+	else
+	{
+		if (node->relkind == OBJECT_TABLE)
+		{
+			tmp2 = new_objtree_VA("oids=%{value}s", 2,
+								  "option", ObjTypeString, "oids",
+								  "value", ObjTypeString,
+								  relation->rd_rel->relhasoids ? "ON" : "OFF");
+			list = list_make1(new_object_object(tmp2));
+		}
+		else
+			list = NIL;
+
+		foreach (cell, node->into->options)
+		{
+			DefElem	*opt = (DefElem *) lfirst(cell);
+
+			if (strcmp(opt->defname, "oids") == 0)
+				continue;
+
+			tmp2 = deparse_DefElem(opt, false);
+			list = lappend(list, new_object_object(tmp2));
+		}
+		append_array_object(tmp, "with", list);
+	}
+	append_object_object(createStmt, "with_clause", tmp);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -5595,8 +5724,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateTableAsStmt:
-			/* XXX handle at least the CREATE MATVIEW case? */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateTableAsStmt(objectId, parsetree);
 			break;
 
 		case T_RefreshMatViewStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1a2f03c..ce459b5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -750,6 +750,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def	- Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * get_triggerdef			- Get the definition of a trigger
  * ----------
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 7b22ea0..2c020e9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -33,6 +33,7 @@ extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
 					   char **whereClause, List **actions);
 extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
 
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
-- 
2.1.4

0025-deparse-support-CREATE-ALTER-POLICY.patchtext/x-diff; charset=us-asciiDownload
>From c2578e6e20b75675eb4f86a073304f5adf759402 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:30:49 -0300
Subject: [PATCH 25/37] deparse: support CREATE/ALTER POLICY

---
 src/backend/tcop/deparse_utility.c | 131 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 129 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 9c0c684..bd63217 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4660,6 +4660,133 @@ deparse_CommentStmt(ObjectAddress address, Node *parsetree)
 	return comment;
 }
 
+/*
+ * Add common clauses to CreatePolicy or AlterPolicy deparse objects
+ */
+static void
+add_policy_clauses(ObjTree *policyStmt, Oid policyOid, List *roles,
+				   bool do_qual, bool do_with_check)
+{
+	Relation	polRel = heap_open(PolicyRelationId, AccessShareLock);
+	HeapTuple	polTup = get_catalog_object_by_oid(polRel, policyOid);
+	ObjTree	   *tmp;
+	Form_pg_policy polForm;
+
+	if (!HeapTupleIsValid(polTup))
+		elog(ERROR, "cache lookup failed for policy %u", policyOid);
+
+	polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+	/* add the "ON table" clause */
+	append_object_object(policyStmt, "table",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 polForm->polrelid));
+
+	/*
+	 * Add the "TO role" clause, if any.  In the CREATE case, it always
+	 * contains at least PUBLIC, but in the ALTER case it might be empty.
+	 */
+	tmp = new_objtree_VA("TO %{role:, }R", 0);
+	if (roles)
+	{
+		List   *list = NIL;
+		ListCell *cell;
+
+		foreach (cell, roles)
+		{
+			RoleSpec   *spec = (RoleSpec *) lfirst(cell);
+
+			list = lappend(list,
+						   new_object_object(new_objtree_for_rolespec(spec)));
+		}
+		append_array_object(tmp, "role", list);
+	}
+	else
+	{
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(policyStmt, "to_role", tmp);
+
+	/* add the USING clause, if any */
+	tmp = new_objtree_VA("USING (%{expression}s)", 0);
+	if (do_qual)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polqual expression in policy %u", policyOid);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policyStmt, "using", tmp);
+
+	/* add the WITH CHECK clause, if any */
+	tmp = new_objtree_VA("WITH CHECK (%{expression}s)", 0);
+	if (do_with_check)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polwithcheck expression in policy %u", policyOid);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policyStmt, "with_check", tmp);
+
+	relation_close(polRel, AccessShareLock);
+}
+
+static ObjTree *
+deparse_CreatePolicyStmt(Oid objectId, Node *parsetree)
+{
+	CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree;
+	ObjTree	   *policy;
+
+	policy = new_objtree_VA("CREATE POLICY %{identity}I ON %{table}D %{for_command}s "
+							"%{to_role}s %{using}s %{with_check}s", 2,
+							"identity", ObjTypeString, node->policy_name,
+							"for_command", ObjTypeObject,
+							new_objtree_VA("FOR %{command}s", 1,
+										   "command", ObjTypeString, node->cmd));
+
+	/* add the rest of the stuff */
+	add_policy_clauses(policy, objectId, node->roles, !!node->qual,
+					   !!node->with_check);
+
+	return policy;
+}
+
+static ObjTree *
+deparse_AlterPolicyStmt(Oid objectId, Node *parsetree)
+{
+	AlterPolicyStmt *node = (AlterPolicyStmt *) parsetree;
+	ObjTree	   *policy;
+
+	policy = new_objtree_VA("ALTER POLICY %{identity}I ON %{table}D "
+							"%{to_role}s %{using}s %{with_check}s", 1,
+							"identity", ObjTypeString, node->policy_name);
+
+	/* add the rest of the stuff */
+	add_policy_clauses(policy, objectId, node->roles, !!node->qual,
+					   !!node->with_check);
+
+	return policy;
+}
+
 static ObjTree *
 deparse_SecLabelStmt(ObjectAddress address, Node *parsetree)
 {
@@ -5809,11 +5936,11 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreatePolicyStmt:	/* CREATE POLICY */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreatePolicyStmt(objectId, parsetree);
 			break;
 
 		case T_AlterPolicyStmt:		/* ALTER POLICY */
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterPolicyStmt(objectId, parsetree);
 			break;
 
 		case T_SecLabelStmt:
-- 
2.1.4

0026-deparse-support-REFRESH-MATERIALIZED-VIEW.patchtext/x-diff; charset=us-asciiDownload
>From 987f01b390872a7ff56a54c8ac5c9ee3d439ddf2 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:38:28 -0300
Subject: [PATCH 26/37] deparse: support REFRESH MATERIALIZED VIEW

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index bd63217..39ecd04 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2064,6 +2064,31 @@ deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
 	return trigger;
 }
 
+static ObjTree *
+deparse_RefreshMatViewStmt(Oid objectId, Node *parsetree)
+{
+	RefreshMatViewStmt *node = (RefreshMatViewStmt *) parsetree;
+	ObjTree	   *refreshStmt;
+	ObjTree	   *tmp;
+
+	refreshStmt = new_objtree_VA("REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D "
+								 "%{with_no_data}s", 0);
+	/* add a CONCURRENTLY clause */
+	append_string_object(refreshStmt, "concurrently",
+						 node->concurrent ? "CONCURRENTLY" : "");
+	/* add the matview name */
+	append_object_object(refreshStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->skipData ? true : false);
+	append_object_object(refreshStmt, "with_no_data", tmp);
+
+	return refreshStmt;
+}
+
 /*
  * deparse_ColumnDef
  *		Subroutine for CREATE TABLE deparsing
@@ -5855,7 +5880,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RefreshMatViewStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RefreshMatViewStmt(objectId, parsetree);
 			break;
 
 		case T_CreateTrigStmt:
-- 
2.1.4

0027-deparse-support-FDW-related-commands.patchtext/x-diff; charset=us-asciiDownload
>From cbdc19ccdf9de631295d12c29bd6263e8f1794b7 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:37:57 -0300
Subject: [PATCH 27/37] deparse: support FDW-related commands

CREATE SERVER
ALTER TABLE ... OPTIONS
ALTER COLUMN OPTIONS
CREATE FOREIGN TABLE
ALTER FOREIGN TABLE
CREATE USER MAPPING
ALTER USER MAPPING

Mostly from Andres Freund
---
 src/backend/tcop/deparse_utility.c | 405 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 396 insertions(+), 9 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 39ecd04..9f05d0e 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1831,6 +1831,347 @@ deparse_AlterDomainStmt(Oid objectId, Node *parsetree,
 }
 
 /*
+ * If a column name is specified, add an element for it; otherwise it's a
+ * table-level option.
+ */
+static ObjTree *
+deparse_FdwOptions(List *options, char *colname)
+{
+	ObjTree	   *tmp;
+
+	if (colname)
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I OPTIONS (%{option:, }s)",
+							 1, "column", ObjTypeString, colname);
+	else
+		tmp = new_objtree_VA("OPTIONS (%{option:, }s)", 0);
+
+	if (options != NIL)
+	{
+		List	   *optout = NIL;
+		ListCell   *cell;
+
+		foreach(cell, options)
+		{
+			DefElem	   *elem;
+			ObjTree	   *opt;
+
+			elem = (DefElem *) lfirst(cell);
+
+			switch (elem->defaction)
+			{
+				case DEFELEM_UNSPEC:
+					opt = new_objtree_VA("%{label}I %{value}L", 0);
+					break;
+				case DEFELEM_SET:
+					opt = new_objtree_VA("SET %{label}I %{value}L", 0);
+					break;
+				case DEFELEM_ADD:
+					opt = new_objtree_VA("ADD %{label}I %{value}L", 0);
+					break;
+				case DEFELEM_DROP:
+					opt = new_objtree_VA("DROP %{label}I", 0);
+					break;
+				default:
+					elog(ERROR, "invalid def action %d", elem->defaction);
+					opt = NULL;
+			}
+
+			append_string_object(opt, "label", elem->defname);
+			append_string_object(opt, "value",
+								 elem->arg ? defGetString(elem) :
+								 defGetBoolean(elem) ? "TRUE" : "FALSE");
+
+			optout = lappend(optout, new_object_object(opt));
+		}
+
+		append_array_object(tmp, "option", optout);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+
+	return tmp;
+}
+
+static ObjTree *
+deparse_CreateFdwStmt(Oid objectId, Node *parsetree)
+{
+	CreateFdwStmt *node = (CreateFdwStmt *) parsetree;
+	HeapTuple		fdwTup;
+	Form_pg_foreign_data_wrapper fdwForm;
+	Relation	rel;
+
+	ObjTree	   *createStmt;
+	ObjTree	   *tmp;
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+							 ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(fdwTup))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", objectId);
+
+	fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup);
+
+	createStmt = new_objtree_VA("CREATE FOREIGN DATA WRAPPER %{identity}I"
+								" %{handler}s %{validator}s %{generic_options}s", 1,
+								"identity", ObjTypeString, NameStr(fdwForm->fdwname));
+
+	/* add HANDLER clause */
+	if (fdwForm->fdwhandler == InvalidOid)
+		tmp = new_objtree_VA("NO HANDLER", 0);
+	else
+	{
+		tmp = new_objtree_VA("HANDLER %{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 fdwForm->fdwhandler));
+	}
+	append_object_object(createStmt, "handler", tmp);
+
+	/* add VALIDATOR clause */
+	if (fdwForm->fdwvalidator == InvalidOid)
+		tmp = new_objtree_VA("NO VALIDATOR", 0);
+	else
+	{
+		tmp = new_objtree_VA("VALIDATOR %{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 fdwForm->fdwvalidator));
+	}
+	append_object_object(createStmt, "validator", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(fdwTup);
+	heap_close(rel, RowExclusiveLock);
+
+	return createStmt;
+}
+
+static ObjTree *
+deparse_AlterFdwStmt(Oid objectId, Node *parsetree)
+{
+	AlterFdwStmt *node = (AlterFdwStmt *) parsetree;
+	HeapTuple		fdwTup;
+	Form_pg_foreign_data_wrapper fdwForm;
+	Relation	rel;
+	ObjTree	   *alterStmt;
+	List	   *fdw_options = NIL;
+	ListCell   *cell;
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+							 ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(fdwTup))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", objectId);
+
+	fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup);
+
+	alterStmt = new_objtree_VA("ALTER FOREIGN DATA WRAPPER %{identity}I"
+							   " %{fdw_options: }s %{generic_options}s", 1,
+							   "identity", ObjTypeString, NameStr(fdwForm->fdwname));
+
+	/*
+	 * Iterate through options, to see what changed, but use catalog as basis
+	 * for new values.
+	 */
+	foreach(cell, node->func_options)
+	{
+		DefElem	   *elem;
+		ObjTree	   *tmp;
+
+		elem = lfirst(cell);
+
+		if (pg_strcasecmp(elem->defname, "handler") == 0)
+		{
+			/* add HANDLER clause */
+			if (fdwForm->fdwhandler == InvalidOid)
+				tmp = new_objtree_VA("NO HANDLER", 0);
+			else
+			{
+				tmp = new_objtree_VA("HANDLER %{procedure}D", 0);
+				append_object_object(tmp, "procedure",
+									 new_objtree_for_qualname_id(ProcedureRelationId,
+																 fdwForm->fdwhandler));
+			}
+			fdw_options = lappend(fdw_options, new_object_object(tmp));
+		}
+		else if (pg_strcasecmp(elem->defname, "validator") == 0)
+		{
+			/* add VALIDATOR clause */
+			if (fdwForm->fdwvalidator == InvalidOid)
+				tmp = new_objtree_VA("NO VALIDATOR", 0);
+			else
+			{
+				tmp = new_objtree_VA("VALIDATOR %{procedure}D", 0);
+				append_object_object(tmp, "procedure",
+									 new_objtree_for_qualname_id(ProcedureRelationId,
+																 fdwForm->fdwvalidator));
+			}
+			fdw_options = lappend(fdw_options, new_object_object(tmp));
+		}
+	}
+
+	/* Add HANDLER/VALIDATOR if specified */
+	append_array_object(alterStmt, "fdw_options", fdw_options);
+
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(fdwTup);
+	heap_close(rel, RowExclusiveLock);
+
+	return alterStmt;
+}
+
+static ObjTree *
+deparse_CreateForeignServerStmt(Oid objectId, Node *parsetree)
+{
+	CreateForeignServerStmt *node = (CreateForeignServerStmt *) parsetree;
+	ObjTree	   *createServer;
+	ObjTree	   *tmp;
+
+	createServer = new_objtree_VA("CREATE SERVER %{identity}I %{type}s %{version}s "
+								  "FOREIGN DATA WRAPPER %{fdw}I %{generic_options}s", 2,
+								  "identity", ObjTypeString, node->servername,
+								  "fdw",  ObjTypeString, node->fdwname);
+
+	/* add a TYPE clause, if any */
+	tmp = new_objtree_VA("TYPE %{type}L", 0);
+	if (node->servertype)
+		append_string_object(tmp, "type", node->servertype);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "type", tmp);
+
+	/* add a VERSION clause, if any */
+	tmp = new_objtree_VA("VERSION %{version}L", 0);
+	if (node->version)
+		append_string_object(tmp, "version", node->version);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "version", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createServer, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	return createServer;
+}
+
+static ObjTree *
+deparse_AlterForeignServerStmt(Oid objectId, Node *parsetree)
+{
+	AlterForeignServerStmt *node = (AlterForeignServerStmt *) parsetree;
+	ObjTree	   *alterServer;
+	ObjTree	   *tmp;
+
+	alterServer = new_objtree_VA("ALTER SERVER %{identity}I %{version}s "
+								  "%{generic_options}s", 1,
+								  "identity", ObjTypeString, node->servername);
+
+	/* add a VERSION clause, if any */
+	tmp = new_objtree_VA("VERSION %{version}s", 0);
+	if (node->has_version && node->version)
+		append_string_object(tmp, "version", quote_literal_cstr(node->version));
+	else if (node->has_version)
+		append_string_object(tmp, "version", "NULL");
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterServer, "version", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterServer, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	return alterServer;
+}
+
+static ObjTree *
+deparse_CreateUserMappingStmt(Oid objectId, Node *parsetree)
+{
+	CreateUserMappingStmt *node = (CreateUserMappingStmt *) parsetree;
+	ObjTree	   *createStmt;
+	Relation	rel;
+	HeapTuple	tp;
+	Form_pg_user_mapping form;
+	ForeignServer *server;
+
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	/*
+	 * Lookup up object in the catalog, so we don't have to deal with
+	 * current_user and such.
+	 */
+	tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for user mapping %u", objectId);
+
+	form = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+	server = GetForeignServer(form->umserver);
+
+	createStmt = new_objtree_VA("CREATE USER MAPPING FOR %{role}R SERVER %{server}I "
+								"%{generic_options}s", 1,
+								"server", ObjTypeString, server->servername);
+
+	append_object_object(createStmt, "role", new_objtree_for_role_id(form->umuser));
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(tp);
+	heap_close(rel, RowExclusiveLock);
+	return createStmt;
+}
+
+static ObjTree *
+deparse_AlterUserMappingStmt(Oid objectId, Node *parsetree)
+{
+	AlterUserMappingStmt *node = (AlterUserMappingStmt *) parsetree;
+	ObjTree	   *alterStmt;
+	Relation	rel;
+	HeapTuple	tp;
+	Form_pg_user_mapping form;
+	ForeignServer *server;
+
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	/*
+	 * Lookup up object in the catalog, so we don't have to deal with
+	 * current_user and such.
+	 */
+
+	tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for user mapping %u", objectId);
+
+	form = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+	server = GetForeignServer(form->umserver);
+
+	alterStmt = new_objtree_VA("ALTER USER MAPPING FOR %{role}R SERVER %{server}I "
+								"%{generic_options}s", 1,
+								"server", ObjTypeString, server->servername);
+
+	append_object_object(alterStmt, "role", new_objtree_for_role_id(form->umuser));
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(tp);
+	heap_close(rel, RowExclusiveLock);
+	return alterStmt;
+}
+
+/*
  * deparse_ViewStmt
  *		deparse a ViewStmt
  *
@@ -2747,6 +3088,49 @@ deparse_CreateStmt(Oid objectId, Node *parsetree)
 	return createStmt;
 }
 
+static ObjTree *
+deparse_CreateForeignTableStmt(Oid objectId, Node *parsetree)
+{
+	CreateForeignTableStmt *stmt = (CreateForeignTableStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *tableelts = NIL;
+
+	createStmt = new_objtree_VA(
+			"CREATE FOREIGN TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) SERVER %{server}I "
+			"%{generic_options}s", 0);
+
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(createStmt, "identity", tmp);
+
+	append_string_object(createStmt, "if_not_exists",
+						 stmt->base.if_not_exists ? "IF NOT EXISTS" : "");
+
+	append_string_object(createStmt, "server", stmt->servername);
+
+	dpcontext = deparse_context_for(RelationGetRelationName(relation),
+									objectId);
+
+	tableelts = deparseTableElements(relation, stmt->base.tableElts, dpcontext,
+									 false,		/* not typed table */
+									 false);	/* not composite */
+	tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+	append_array_object(createStmt, "table_elements", tableelts);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(stmt->options, NULL));
+
+	relation_close(relation, AccessShareLock);
+	return createStmt;
+}
+
+
 /*
  * deparse_CompositeTypeStmt
  *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
@@ -5504,7 +5888,9 @@ deparse_AlterTableStmt(StashedCommand *cmd)
 				break;
 
 			case AT_AlterColumnGenericOptions:
-				elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS");
+				tmp = deparse_FdwOptions((List *) subcmd->def,
+										 subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
 				break;
 
 			case AT_ChangeOwner:
@@ -5715,7 +6101,8 @@ deparse_AlterTableStmt(StashedCommand *cmd)
 				break;
 
 			case AT_GenericOptions:
-				elog(ERROR, "unimplemented deparse of ALTER TABLE OPTIONS (...)");
+				tmp = deparse_FdwOptions((List *) subcmd->def, NULL);
+				subcmds = lappend(subcmds, new_object_object(tmp));
 				break;
 
 			default:
@@ -5764,7 +6151,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateForeignTableStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateForeignTableStmt(objectId, parsetree);
 			break;
 
 		case T_AlterTableStmt:
@@ -5802,27 +6189,27 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateFdwStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateFdwStmt(objectId, parsetree);
 			break;
 
 		case T_AlterFdwStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterFdwStmt(objectId, parsetree);
 			break;
 
 		case T_CreateForeignServerStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateForeignServerStmt(objectId, parsetree);
 			break;
 
 		case T_AlterForeignServerStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterForeignServerStmt(objectId, parsetree);
 			break;
 
 		case T_CreateUserMappingStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreateUserMappingStmt(objectId, parsetree);
 			break;
 
 		case T_AlterUserMappingStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_AlterUserMappingStmt(objectId, parsetree);
 			break;
 
 		case T_DropUserMappingStmt:
-- 
2.1.4

0028-infrastructure-for-ALTER-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From d1878729d70c8f9fd27df3ed73474a2b7dc6e6f8 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 13 Mar 2015 14:17:58 -0300
Subject: [PATCH 28/37] infrastructure for ALTER OPERATOR FAMILY

---
 src/backend/commands/event_trigger.c | 39 +++++++++++++++++++++++++++++++++++-
 src/backend/commands/opclasscmds.c   | 15 +-------------
 src/backend/tcop/deparse_utility.c   |  6 +++++-
 src/include/catalog/opfam_internal.h | 28 ++++++++++++++++++++++++++
 src/include/commands/event_trigger.h |  2 ++
 src/include/tcop/deparse_utility.h   | 11 +++++++++-
 6 files changed, 84 insertions(+), 17 deletions(-)
 create mode 100644 src/include/catalog/opfam_internal.h

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 3c57951..9b51bf3 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -20,6 +20,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -1855,6 +1856,37 @@ EventTriggerStashGrant(InternalGrant *istmt)
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * EventTriggerStashAlterOpFam
+ * 		Save data about an ALTER OPERATOR FAMILY ADD/DROP command being
+ * 		executed
+ */
+void
+EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
+							List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+	stashed->type = SCT_AlterOpFamily;
+	stashed->in_extension = creating_extension;
+	stashed->d.opfam.opfamOid = opfamoid;
+	stashed->d.opfam.operators = operators;		/* XXX prolly need to copy */
+	stashed->d.opfam.procedures = procedures;	/* XXX ditto */
+	stashed->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1934,7 +1966,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 		MemSet(nulls, 0, sizeof(nulls));
 
 		if (cmd->type == SCT_Simple ||
-			cmd->type == SCT_AlterTable)
+			cmd->type == SCT_AlterTable ||
+			cmd->type == SCT_AlterOpFamily)
 		{
 			const char *tag;
 			char	   *identity;
@@ -1947,6 +1980,10 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 				ObjectAddressSet(addr,
 								 cmd->d.alterTable.classId,
 								 cmd->d.alterTable.objectId);
+			else if (cmd->type == SCT_AlterOpFamily)
+				ObjectAddressSet(addr,
+								 OperatorFamilyRelationId,
+								 cmd->d.opfam.opfamOid);
 
 			tag = CreateCommandTag(cmd->parsetree);
 
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index c327cc0..d5ca502 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -25,6 +25,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
+#include "catalog/opfam_internal.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_namespace.h"
@@ -47,20 +48,6 @@
 #include "utils/tqual.h"
 
 
-/*
- * We use lists of this struct type to keep track of both operators and
- * procedures while building or adding to an opfamily.
- */
-typedef struct
-{
-	Oid			object;			/* operator or support proc's OID */
-	int			number;			/* strategy or support proc number */
-	Oid			lefttype;		/* lefttype */
-	Oid			righttype;		/* righttype */
-	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
-} OpFamilyMember;
-
-
 static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items);
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 9f05d0e..195b06f 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -6299,7 +6299,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterOpFamilyStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
 			break;
 
 		case T_AlterTSDictionaryStmt:
@@ -6422,6 +6423,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_Grant:
 			tree = deparse_GrantStmt(cmd);
 			break;
+		case SCT_AlterOpFamily:
+			tree = deparse_AlterOpFamily(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/include/catalog/opfam_internal.h b/src/include/catalog/opfam_internal.h
new file mode 100644
index 0000000..f01dcbe
--- /dev/null
+++ b/src/include/catalog/opfam_internal.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * opfam_internal.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/opfam_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OPFAM_INTERNAL_H
+#define OPFAM_INTERNAL_H
+
+/*
+ * We use lists of this struct type to keep track of both operators and
+ * procedures while building or adding to an opfamily.
+ */
+typedef struct
+{
+	Oid			object;			/* operator or support proc's OID */
+	int			number;			/* strategy or support proc number */
+	Oid			lefttype;		/* lefttype */
+	Oid			righttype;		/* righttype */
+	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
+} OpFamilyMember;
+
+#endif		/* OPFAM_INTERNAL_H */
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 932630f..a8435e3 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -74,5 +74,7 @@ extern void EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid,
 extern void EventTriggerAlterTableEnd(void);
 
 extern void EventTriggerStashGrant(InternalGrant *istmt);
+extern void EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
+							List *operators, List *procedures);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 7f70ede..6aa5fa5 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -29,7 +29,8 @@ typedef enum StashedCommandType
 {
 	SCT_Simple,
 	SCT_AlterTable,
-	SCT_Grant
+	SCT_Grant,
+	SCT_AlterOpFamily
 } StashedCommandType;
 
 /*
@@ -71,6 +72,14 @@ typedef struct StashedCommand
 			InternalGrant *istmt;
 			const char *type;
 		} grant;
+
+		/* ALTER OPERATOR FAMILY */
+		struct
+		{
+			Oid		opfamOid;
+			List   *operators;
+			List   *procedures;
+		} opfam;
 	} d;
 } StashedCommand;
 
-- 
2.1.4

0029-deparse-Support-ALTER-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From 3b47551025ad4378a1eb7c544797aa4497f883f4 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Sat, 14 Feb 2015 15:43:29 -0300
Subject: [PATCH 29/37] deparse: Support ALTER OPERATOR FAMILY

---
 src/backend/commands/opclasscmds.c |  25 +++++--
 src/backend/tcop/deparse_utility.c | 141 +++++++++++++++++++++++++++++++++++--
 2 files changed, 156 insertions(+), 10 deletions(-)

diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index d5ca502..68624c3 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
@@ -48,10 +49,12 @@
 #include "utils/tqual.h"
 
 
-static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+				 List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items);
-static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+				  List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				  int maxOpNumber, int maxProcNumber,
 				  List *items);
 static void processTypesSpec(List *args, Oid *lefttype, Oid *righttype);
@@ -809,11 +812,11 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
 	 * ADD and DROP cases need separate code from here on down.
 	 */
 	if (stmt->isDrop)
-		AlterOpFamilyDrop(stmt->opfamilyname, amoid, opfamilyoid,
+		AlterOpFamilyDrop(stmt, stmt->opfamilyname, amoid, opfamilyoid,
 						  maxOpNumber, maxProcNumber,
 						  stmt->items);
 	else
-		AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid,
+		AlterOpFamilyAdd(stmt, stmt->opfamilyname, amoid, opfamilyoid,
 						 maxOpNumber, maxProcNumber,
 						 stmt->items);
 
@@ -824,7 +827,8 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
  * ADD part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+				 List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items)
 {
@@ -949,13 +953,19 @@ AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				   InvalidOid, operators, true);
 	storeProcedures(opfamilyname, amoid, opfamilyoid,
 					InvalidOid, procedures, true);
+
+	/*
+	 * make information available to event triggers */
+	EventTriggerStashAlterOpFam(stmt, opfamilyoid,
+								operators, procedures);
 }
 
 /*
  * DROP part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+				  List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				  int maxOpNumber, int maxProcNumber,
 				  List *items)
 {
@@ -1022,6 +1032,9 @@ AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 	 */
 	dropOperators(opfamilyname, amoid, opfamilyoid, operators);
 	dropProcedures(opfamilyname, amoid, opfamilyoid, procedures);
+
+	EventTriggerStashAlterOpFam(stmt, opfamilyoid,
+								operators, procedures);
 }
 
 
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 195b06f..9d083db 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -31,6 +31,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/opfam_internal.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
@@ -139,9 +140,7 @@ 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);
-#ifdef NOT_USED
 static void append_integer_object(ObjTree *tree, char *name, int64 value);
-#endif
 static void append_float_object(ObjTree *tree, char *name, float8 value);
 static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
 static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
@@ -345,7 +344,6 @@ new_float_object(float8 value)
 	return param;
 }
 
-#ifdef NOT_USED
 /*
  * Append an int64 parameter to a tree.
  */
@@ -358,7 +356,6 @@ append_integer_object(ObjTree *tree, char *name, int64 value)
 	param->name = name;
 	append_premade_object(tree, param);
 }
-#endif
 
 /*
  * Append a float8 parameter to a tree.
@@ -5557,6 +5554,142 @@ deparse_GrantStmt(StashedCommand *cmd)
 	return grantStmt;
 }
 
+/*
+ * Deparse an ALTER OPERATOR FAMILY ADD/DROP command.
+ */
+static ObjTree *
+deparse_AlterOpFamily(StashedCommand *cmd)
+{
+	ObjTree	   *alterOpFam;
+	AlterOpFamilyStmt *stmt = (AlterOpFamilyStmt *) cmd->parsetree;
+	HeapTuple	ftp;
+	Form_pg_opfamily opfForm;
+	List	   *list;
+	ListCell   *cell;
+
+	ftp = SearchSysCache1(OPFAMILYOID,
+						  ObjectIdGetDatum(cmd->d.opfam.opfamOid));
+	if (!HeapTupleIsValid(ftp))
+		elog(ERROR, "cache lookup failed for operator family %u", cmd->d.opfam.opfamOid);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(ftp);
+
+	if (!stmt->isDrop)
+		alterOpFam = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D "
+									"USING %{amname}I ADD %{items:, }s", 0);
+	else
+		alterOpFam = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D "
+									"USING %{amname}I DROP %{items:, }s", 0);
+	append_object_object(alterOpFam, "identity",
+						 new_objtree_for_qualname(opfForm->opfnamespace,
+												  NameStr(opfForm->opfname)));
+	append_string_object(alterOpFam, "amname", stmt->amname);
+
+	list = NIL;
+	foreach(cell, cmd->d.opfam.operators)
+	{
+		OpFamilyMember *oper = lfirst(cell);
+		ObjTree	   *tmp;
+
+		if (!stmt->isDrop)
+			tmp = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s",
+								 0);
+		else
+			tmp = new_objtree_VA("OPERATOR %{num}n (%{ltype}T, %{rtype}T)",
+								 0);
+		append_integer_object(tmp, "num", oper->number);
+
+		/* Add the operator name; the DROP case doesn't have this */
+		if (!stmt->isDrop)
+		{
+			append_object_object(tmp, "operator",
+								 new_objtree_for_qualname_id(OperatorRelationId,
+															 oper->object));
+		}
+
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(oper->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(oper->righttype, -1));
+
+		/* Add the FOR SEARCH / FOR ORDER BY clause; not in the DROP case */
+		if (!stmt->isDrop)
+		{
+			if (oper->sortfamily == InvalidOid)
+				append_string_object(tmp, "purpose", "FOR SEARCH");
+			else
+			{
+				ObjTree	   *tmp2;
+
+				tmp2 = new_objtree_VA("FOR ORDER BY %{opfamily}D", 0);
+				append_object_object(tmp2, "opfamily",
+									 new_objtree_for_qualname_id(OperatorFamilyRelationId,
+																 oper->sortfamily));
+				append_object_object(tmp, "purpose", tmp2);
+			}
+		}
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	foreach(cell, cmd->d.opfam.procedures)
+	{
+		OpFamilyMember *proc = lfirst(cell);
+		ObjTree	   *tmp;
+
+		if (!stmt->isDrop)
+			tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D(%{argtypes:, }T)", 0);
+		else
+			tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T)", 0);
+		append_integer_object(tmp, "num", proc->number);
+
+		/* Add the function name and arg types; the DROP case doesn't have this */
+		if (!stmt->isDrop)
+		{
+			HeapTuple	procTup;
+			Form_pg_proc procForm;
+			Oid		   *proargtypes;
+			List	   *arglist;
+			int			i;
+
+			procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object));
+			if (!HeapTupleIsValid(procTup))
+				elog(ERROR, "cache lookup failed for procedure %u", proc->object);
+			procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+			append_object_object(tmp, "function",
+								 new_objtree_for_qualname(procForm->pronamespace,
+														  NameStr(procForm->proname)));
+			proargtypes = procForm->proargtypes.values;
+			arglist = NIL;
+			for (i = 0; i < procForm->pronargs; i++)
+			{
+				ObjTree	   *arg;
+
+				arg = new_objtree_for_type(proargtypes[i], -1);
+				arglist = lappend(arglist, new_object_object(arg));
+			}
+			append_array_object(tmp, "argtypes", arglist);
+
+			ReleaseSysCache(procTup);
+		}
+
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(proc->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(proc->righttype, -1));
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	append_array_object(alterOpFam, "items", list);
+
+	ReleaseSysCache(ftp);
+
+	return alterOpFam;
+}
+
 static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
-- 
2.1.4

0030-deparse-support-ALTER-DEFAULT-PRIVILEGES.patchtext/x-diff; charset=us-asciiDownload
>From e81824deb10dcee547ea4216842f050ec6bb0882 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 16 Mar 2015 14:41:13 -0300
Subject: [PATCH 30/37] deparse: support ALTER DEFAULT PRIVILEGES

---
 src/backend/commands/event_trigger.c |  67 +++++++++++++++++++
 src/backend/tcop/deparse_utility.c   | 123 ++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c           |   3 +-
 src/include/commands/event_trigger.h |   1 +
 src/include/tcop/deparse_utility.h   |   9 ++-
 5 files changed, 200 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9b51bf3..95491a1 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1887,6 +1887,52 @@ EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * EventTriggerStashAlterDefPrivs
+ * 		Save data about an ALTER DEFAULT PRIVILEGES command being
+ * 		executed
+ */
+void
+EventTriggerStashAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc0(sizeof(StashedCommand));
+	stashed->type = SCT_AlterDefaultPrivileges;
+
+	switch (stmt->action->objtype)
+	{
+		case ACL_OBJECT_RELATION:
+			stashed->d.defprivs.objtype = "TABLES";
+			break;
+		case ACL_OBJECT_FUNCTION:
+			stashed->d.defprivs.objtype = "FUNCTIONS";
+			break;
+		case ACL_OBJECT_SEQUENCE:
+			stashed->d.defprivs.objtype = "SEQUENCES";
+			break;
+		case ACL_OBJECT_TYPE:
+			stashed->d.defprivs.objtype = "TYPES";
+			break;
+		default:
+			elog(ERROR, "unexpected object type %d", stmt->action->objtype);
+	}
+
+
+	stashed->in_extension = creating_extension;
+	stashed->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+	MemoryContextSwitchTo(oldcxt);
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -2050,6 +2096,27 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 			/* command */
 			values[i++] = CStringGetTextDatum(command);
 		}
+		else if (cmd->type == SCT_AlterDefaultPrivileges)
+		{
+			/* classid */
+			nulls[i++] = true;
+			/* objid */
+			nulls[i++] = true;
+			/* objsubid */
+			nulls[i++] = true;
+			/* command tag */
+			values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree));
+			/* object_type */
+			values[i++] = CStringGetTextDatum(cmd->d.defprivs.objtype);
+			/* schema */
+			nulls[i++] = true;
+			/* identity */
+			nulls[i++] = true;
+			/* in_extension */
+			values[i++] = BoolGetDatum(cmd->in_extension);
+			/* command */
+			values[i++] = CStringGetTextDatum(command);
+		}
 		else
 		{
 			Assert(cmd->type == SCT_Grant);
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 9d083db..860eb0f 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -5691,6 +5691,123 @@ deparse_AlterOpFamily(StashedCommand *cmd)
 }
 
 static ObjTree *
+deparse_AlterDefaultPrivilegesStmt(StashedCommand *cmd)
+{
+	ObjTree	   *alterStmt;
+	AlterDefaultPrivilegesStmt *stmt = (AlterDefaultPrivilegesStmt *) cmd->parsetree;
+	List	   *roles = NIL;
+	List	   *schemas = NIL;
+	List	   *grantees;
+	List	   *privs;
+	ListCell   *cell;
+	ObjTree	   *tmp;
+	ObjTree	   *grant;
+
+	alterStmt = new_objtree_VA("ALTER DEFAULT PRIVILEGES %{in_schema}s "
+							   "%{for_roles}s %{grant}s", 0);
+
+	/* Scan the parse node to dig out the FOR ROLE and IN SCHEMA clauses */
+	foreach(cell, stmt->options)
+	{
+		DefElem	   *opt = (DefElem *) lfirst(cell);
+		ListCell   *cell2;
+
+		Assert(IsA(opt, DefElem));
+		Assert(IsA(opt->arg, List));
+		if (strcmp(opt->defname, "roles") == 0)
+		{
+			foreach(cell2, (List *) opt->arg)
+			{
+				Value  *val = lfirst(cell2);
+				ObjTree *obj = new_objtree_for_role(strVal(val));
+
+				roles = lappend(roles, new_object_object(obj));
+			}
+		}
+		else if (strcmp(opt->defname, "schemas") == 0)
+		{
+			foreach(cell2, (List *) opt->arg)
+			{
+				Value  *val = lfirst(cell2);
+
+				schemas = lappend(schemas,
+								  new_string_object(strVal(val)));
+			}
+		}
+	}
+
+	/* Add the FOR ROLE clause, if any */
+	tmp = new_objtree_VA("FOR ROLE %{roles:, }R", 0);
+	append_array_object(tmp, "roles", roles);
+	if (roles == NIL)
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterStmt, "for_roles", tmp);
+
+	/* Add the IN SCHEMA clause, if any */
+	tmp = new_objtree_VA("IN SCHEMA %{schemas:, }I", 0);
+	append_array_object(tmp, "schemas", schemas);
+	if (schemas == NIL)
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterStmt, "in_schema", tmp);
+
+	/* Add the GRANT subcommand */
+	if (stmt->action->is_grant)
+		grant = new_objtree_VA("GRANT %{privileges:, }s ON %{target}s "
+							   "TO %{grantees:, }R %{grant_option}s", 0);
+	else
+		grant = new_objtree_VA("REVOKE %{grant_option}s %{privileges:, }s "
+							   "ON %{target}s FROM %{grantees:, }R", 0);
+
+	/* add the GRANT OPTION clause */
+	tmp = new_objtree_VA(stmt->action->is_grant ?
+						   "WITH GRANT OPTION" : "GRANT OPTION FOR",
+						   1, "present", ObjTypeBool,
+						   stmt->action->grant_option);
+	append_object_object(grant, "grant_option", tmp);
+
+	/* add the target object type */
+	append_string_object(grant, "target", cmd->d.defprivs.objtype);
+
+	/* add the grantee list */
+	grantees = NIL;
+	foreach(cell, stmt->action->grantees)
+	{
+		RoleSpec   *spec = (RoleSpec *) lfirst(cell);
+		ObjTree	   *obj = new_objtree_for_rolespec(spec);
+
+		grantees = lappend(grantees, new_object_object(obj));
+	}
+	append_array_object(grant, "grantees", grantees);
+
+	/*
+	 * Add the privileges list.  This uses the parser struct, as opposed to the
+	 * InternalGrant format used by GRANT.  There are enough other differences
+	 * that this doesn't seem worth improving.
+	 */
+	if (stmt->action->privileges == NIL)
+		privs = list_make1(new_string_object("ALL PRIVILEGES"));
+	else
+	{
+		privs = NIL;
+
+		foreach(cell, stmt->action->privileges)
+		{
+			AccessPriv *priv = lfirst(cell);
+
+			Assert(priv->cols == NIL);
+			privs = lappend(privs,
+							new_string_object(priv->priv_name));
+		}
+	}
+
+	append_array_object(grant, "privileges", privs);
+
+	append_object_object(alterStmt, "grant", grant);
+
+	return alterStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -6478,7 +6595,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterDefaultPrivilegesStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
 			break;
 
 		case T_CreatePolicyStmt:	/* CREATE POLICY */
@@ -6559,6 +6677,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterOpFamily:
 			tree = deparse_AlterOpFamily(cmd);
 			break;
+		case SCT_AlterDefaultPrivileges:
+			tree = deparse_AlterDefaultPrivilegesStmt(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 0bd239c..eaa057f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1465,7 +1465,8 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_AlterDefaultPrivilegesStmt:
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
-				/* XXX FIXME WTF? */
+				EventTriggerStashAlterDefPrivs((AlterDefaultPrivilegesStmt *) parsetree);
+				commandStashed = true;
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index a8435e3..35fbfd1 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -76,5 +76,6 @@ extern void EventTriggerAlterTableEnd(void);
 extern void EventTriggerStashGrant(InternalGrant *istmt);
 extern void EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
 							List *operators, List *procedures);
+extern void EventTriggerStashAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 6aa5fa5..3a51b24 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -30,7 +30,8 @@ typedef enum StashedCommandType
 	SCT_Simple,
 	SCT_AlterTable,
 	SCT_Grant,
-	SCT_AlterOpFamily
+	SCT_AlterOpFamily,
+	SCT_AlterDefaultPrivileges
 } StashedCommandType;
 
 /*
@@ -80,6 +81,12 @@ typedef struct StashedCommand
 			List   *operators;
 			List   *procedures;
 		} opfam;
+
+		/* ALTER DEFAULT PRIVILEGES */
+		struct
+		{
+			char   *objtype;
+		} defprivs;
 	} d;
 } StashedCommand;
 
-- 
2.1.4

0031-deparse-support-CREATE-OPERATOR-CLASS.patchtext/x-diff; charset=us-asciiDownload
>From abe2ad87a4feec2f18de816a24ebc9b0be74783b Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 16 Mar 2015 15:54:52 -0300
Subject: [PATCH 31/37] deparse: support CREATE OPERATOR CLASS

---
 src/backend/commands/event_trigger.c |  35 +++++++-
 src/backend/commands/opclasscmds.c   |   5 +-
 src/backend/tcop/deparse_utility.c   | 158 ++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c           |   2 +
 src/include/commands/defrem.h        |   1 +
 src/include/commands/event_trigger.h |   2 +
 src/include/tcop/deparse_utility.h   |  11 ++-
 7 files changed, 210 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 95491a1..ba97550 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -20,6 +20,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
@@ -1887,6 +1888,33 @@ EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
 	MemoryContextSwitchTo(oldcxt);
 }
 
+void
+EventTriggerStashCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
+							   List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc0(sizeof(StashedCommand));
+	stashed->type = SCT_CreateOpClass;
+	stashed->in_extension = creating_extension;
+	stashed->d.createopc.opcOid = opcoid;
+	stashed->d.createopc.operators = operators;	/* XXX prolly need to copy */
+	stashed->d.createopc.procedures = procedures;
+	stashed->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+
 /*
  * EventTriggerStashAlterDefPrivs
  * 		Save data about an ALTER DEFAULT PRIVILEGES command being
@@ -2013,7 +2041,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
 		if (cmd->type == SCT_Simple ||
 			cmd->type == SCT_AlterTable ||
-			cmd->type == SCT_AlterOpFamily)
+			cmd->type == SCT_AlterOpFamily ||
+			cmd->type == SCT_CreateOpClass)
 		{
 			const char *tag;
 			char	   *identity;
@@ -2030,6 +2059,10 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 				ObjectAddressSet(addr,
 								 OperatorFamilyRelationId,
 								 cmd->d.opfam.opfamOid);
+			else if (cmd->type == SCT_CreateOpClass)
+				ObjectAddressSet(addr,
+								 OperatorClassRelationId,
+								 cmd->d.createopc.opcOid);
 
 			tag = CreateCommandTag(cmd->parsetree);
 
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 68624c3..38b2ff1 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -665,6 +665,9 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	storeProcedures(stmt->opfamilyname, amoid, opfamilyoid,
 					opclassoid, procedures, false);
 
+	/* let event triggers know what happened */
+	EventTriggerStashCreateOpClass(stmt, opclassoid, operators, procedures);
+
 	/*
 	 * Create dependencies for the opclass proper.  Note: we do not create a
 	 * dependency link to the AM, because we don't currently support DROP
@@ -1673,7 +1676,7 @@ RemoveAmProcEntryById(Oid entryOid)
 	heap_close(rel, RowExclusiveLock);
 }
 
-static char *
+char *
 get_am_name(Oid amOid)
 {
 	HeapTuple	tup;
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 860eb0f..19769fa 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -5330,6 +5330,158 @@ deparse_CreateCastStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateOpClassStmt(StashedCommand *cmd)
+{
+	Oid			opcoid = cmd->d.createopc.opcOid;
+	HeapTuple   opcTup;
+	HeapTuple   opfTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_opclass opcForm;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	ListCell   *cell;
+
+	opcTup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opcoid));
+	if (!HeapTupleIsValid(opcTup))
+		elog(ERROR, "cache lookup failed for opclass with OID %u", opcoid);
+	opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
+
+	opfTup = SearchSysCache1(OPFAMILYOID, opcForm->opcfamily);
+	if (!HeapTupleIsValid(opfTup))
+		elog(ERROR, "cache lookup failed for operator family with OID %u", opcForm->opcfamily);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+	stmt = new_objtree_VA("CREATE OPERATOR CLASS %{identity}D %{default}s "
+						  "FOR TYPE %{type}T USING %{amname}I %{opfamily}s "
+						  "AS %{items:, }s", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(opcForm->opcnamespace,
+												  NameStr(opcForm->opcname)));
+
+	/*
+	 * Add the FAMILY clause; but if it has the same name and namespace as the
+	 * opclass, then have it expand to empty, because it would cause a failure
+	 * if the opfamily was created internally.
+	 */
+	tmp = new_objtree_VA("FAMILY %{opfamily}D", 1,
+						 "opfamily", ObjTypeObject,
+						 new_objtree_for_qualname(opfForm->opfnamespace,
+												  NameStr(opfForm->opfname)));
+	if (strcmp(NameStr(opfForm->opfname), NameStr(opcForm->opcname)) == 0 &&
+		opfForm->opfnamespace == opcForm->opcnamespace)
+		append_bool_object(tmp, "present", false);
+	append_object_object(stmt, "opfamily",  tmp);
+
+	/* Add the DEFAULT clause */
+	append_string_object(stmt, "default",
+						 opcForm->opcdefault ? "DEFAULT" : "");
+
+	/* Add the FOR TYPE clause */
+	append_object_object(stmt, "type",
+						 new_objtree_for_type(opcForm->opcintype, -1));
+
+	/* Add the USING clause */
+	append_string_object(stmt, "amname", get_am_name(opcForm->opcmethod));
+
+	/*
+	 * Add the initial item list.  Note we always add the STORAGE clause, even
+	 * when it is implicit in the original command.
+	 */
+	tmp = new_objtree_VA("STORAGE %{type}T", 0);
+	append_object_object(tmp, "type",
+						 new_objtree_for_type(opcForm->opckeytype != InvalidOid ?
+											  opcForm->opckeytype : opcForm->opcintype,
+											  -1));
+	list = list_make1(new_object_object(tmp));
+
+	/* Add the declared operators */
+	/* XXX this duplicates code in deparse_AlterOpFamily */
+	foreach(cell, cmd->d.createopc.operators)
+	{
+		OpFamilyMember *oper = lfirst(cell);
+		ObjTree	   *tmp;
+
+		tmp = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s",
+							 0);
+		append_integer_object(tmp, "num", oper->number);
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oper->object));
+		/* add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(oper->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(oper->righttype, -1));
+		/* Add the FOR SEARCH / FOR ORDER BY clause */
+		if (oper->sortfamily == InvalidOid)
+			append_string_object(tmp, "purpose", "FOR SEARCH");
+		else
+		{
+			ObjTree	   *tmp2;
+
+			tmp2 = new_objtree_VA("FOR ORDER BY %{opfamily}D", 0);
+			append_object_object(tmp2, "opfamily",
+								 new_objtree_for_qualname_id(OperatorFamilyRelationId,
+															 oper->sortfamily));
+			append_object_object(tmp, "purpose", tmp2);
+		}
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	/* Add the declared support functions */
+	foreach(cell, cmd->d.createopc.procedures)
+	{
+		OpFamilyMember *proc = lfirst(cell);
+		ObjTree	   *tmp;
+		HeapTuple	procTup;
+		Form_pg_proc procForm;
+		Oid		   *proargtypes;
+		List	   *arglist;
+		int			i;
+
+		tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D(%{argtypes:, }T)", 0);
+		append_integer_object(tmp, "num", proc->number);
+		procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object));
+		if (!HeapTupleIsValid(procTup))
+			elog(ERROR, "cache lookup failed for procedure %u", proc->object);
+		procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+		append_object_object(tmp, "function",
+							 new_objtree_for_qualname(procForm->pronamespace,
+													  NameStr(procForm->proname)));
+		proargtypes = procForm->proargtypes.values;
+		arglist = NIL;
+		for (i = 0; i < procForm->pronargs; i++)
+		{
+			ObjTree	   *arg;
+
+			arg = new_objtree_for_type(proargtypes[i], -1);
+			arglist = lappend(arglist, new_object_object(arg));
+		}
+		append_array_object(tmp, "argtypes", arglist);
+
+		ReleaseSysCache(procTup);
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(proc->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(proc->righttype, -1));
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	append_array_object(stmt, "items", list);
+
+	ReleaseSysCache(opfTup);
+	ReleaseSysCache(opcTup);
+
+	return stmt;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -6541,7 +6693,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateOpClassStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
 			break;
 
 		case T_CreateOpFamilyStmt:
@@ -6677,6 +6830,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterOpFamily:
 			tree = deparse_AlterOpFamily(cmd);
 			break;
+		case SCT_CreateOpClass:
+			tree = deparse_CreateOpClassStmt(cmd);
+			break;
 		case SCT_AlterDefaultPrivileges:
 			tree = deparse_AlterDefaultPrivilegesStmt(cmd);
 			break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index eaa057f..4886a3f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1401,6 +1401,8 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_CreateOpClassStmt:
 				address = DefineOpClass((CreateOpClassStmt *) parsetree);
+				/* command is stashed in DefineOpClass */
+				commandStashed = true;
 				break;
 
 			case T_CreateOpFamilyStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 595f93f..89a7e49 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -87,6 +87,7 @@ extern void IsThereOpClassInNamespace(const char *opcname, Oid opcmethod,
 extern void IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod,
 						   Oid opfnamespace);
 extern Oid	get_am_oid(const char *amname, bool missing_ok);
+extern char *get_am_name(Oid amOid);
 extern Oid	get_opclass_oid(Oid amID, List *opclassname, bool missing_ok);
 extern Oid	get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok);
 
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 35fbfd1..8d53e8b 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -76,6 +76,8 @@ extern void EventTriggerAlterTableEnd(void);
 extern void EventTriggerStashGrant(InternalGrant *istmt);
 extern void EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
 							List *operators, List *procedures);
+extern void EventTriggerStashCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
+							   List *operators, List *procedures);
 extern void EventTriggerStashAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 3a51b24..6ee3ce2 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -31,7 +31,8 @@ typedef enum StashedCommandType
 	SCT_AlterTable,
 	SCT_Grant,
 	SCT_AlterOpFamily,
-	SCT_AlterDefaultPrivileges
+	SCT_AlterDefaultPrivileges,
+	SCT_CreateOpClass
 } StashedCommandType;
 
 /*
@@ -82,6 +83,14 @@ typedef struct StashedCommand
 			List   *procedures;
 		} opfam;
 
+		/* CREATE OPERATOR CLASS */
+		struct
+		{
+			Oid		opcOid;
+			List   *operators;
+			List   *procedures;
+		} createopc;
+
 		/* ALTER DEFAULT PRIVILEGES */
 		struct
 		{
-- 
2.1.4

0032-deparse-support-CREATE-LANGUAGE.patchtext/x-diff; charset=us-asciiDownload
>From 8aa91a63c8a1b9cfc605c3d11c1dff17b31686ac Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 20 Mar 2015 16:20:58 -0300
Subject: [PATCH 32/37] deparse: support CREATE LANGUAGE

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 19769fa..9a36894 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3305,6 +3305,73 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreatePLangStmt(Oid objectId, Node *parsetree)
+{
+	CreatePLangStmt *node = (CreatePLangStmt *) parsetree;
+	ObjTree	   *createLang;
+	ObjTree	   *tmp;
+	HeapTuple	langTup;
+	Form_pg_language langForm;
+
+
+	langTup = SearchSysCache1(LANGOID,
+							  ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(langTup))
+		elog(ERROR, "cache lookup failed for language %u", objectId);
+	langForm = (Form_pg_language) GETSTRUCT(langTup);
+
+	if (node->plhandler == NIL)
+		createLang =
+			new_objtree_VA("CREATE %{or_replace}s %{trusted}s PROCEDURAL LANGUAGE %{identity}I", 0);
+	else
+		createLang =
+			new_objtree_VA("CREATE %{or_replace}s %{trusted}s PROCEDURAL LANGUAGE %{identity}I "
+						   "HANDLER %{handler}D %{inline}s %{validator}s", 0);
+
+	append_string_object(createLang, "or_replace",
+						 node->replace ? "OR REPLACE" : "");
+	append_string_object(createLang, "identity", node->plname);
+	append_string_object(createLang, "trusted",
+						 langForm->lanpltrusted ? "TRUSTED" : "");
+
+	if (node->plhandler != NIL)
+	{
+		/* Add the HANDLER clause */
+		append_object_object(createLang, "handler",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 langForm->lanplcallfoid));
+
+		/* Add the INLINE clause, if any */
+		tmp = new_objtree_VA("INLINE %{handler_name}D", 0);
+		if (OidIsValid(langForm->laninline))
+		{
+			append_object_object(tmp, "handler_name",
+								 new_objtree_for_qualname_id(ProcedureRelationId,
+															 langForm->laninline));
+		}
+		else
+			append_bool_object(tmp, "present", false);
+		append_object_object(createLang, "inline", tmp);
+
+		/* Add the VALIDATOR clause, if any */
+		tmp = new_objtree_VA("VALIDATOR %{handler_name}D", 0);
+		if (OidIsValid(langForm->lanvalidator))
+		{
+			append_object_object(tmp, "handler_name",
+								 new_objtree_for_qualname_id(ProcedureRelationId,
+															 langForm->lanvalidator));
+		}
+		else
+			append_bool_object(tmp, "present", false);
+		append_object_object(createLang, "validator", tmp);
+	}
+
+	ReleaseSysCache(langTup);
+
+	return createLang;
+}
+
+static ObjTree *
 deparse_CreateDomain(Oid objectId, Node *parsetree)
 {
 	ObjTree	   *createDomain;
@@ -6677,7 +6744,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreatePLangStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_CreatePLangStmt(objectId, parsetree);
 			break;
 
 		case T_CreateDomainStmt:
-- 
2.1.4

0033-deparse-infrastructure-for-ALTER-TEXT-SEARCH-CONFIG-.patchtext/x-diff; charset=us-asciiDownload
>From d5668f66fccf5118793717a85bd55dd416ede023 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Mar 2015 19:40:17 -0300
Subject: [PATCH 33/37] deparse: infrastructure for ALTER TEXT SEARCH CONFIG
 support

---
 src/backend/commands/event_trigger.c | 34 +++++++++++++++++++++++++++++++++-
 src/backend/commands/tsearchcmds.c   |  5 +++++
 src/backend/nodes/copyfuncs.c        |  1 +
 src/backend/nodes/equalfuncs.c       |  1 +
 src/backend/parser/gram.y            |  6 ++++++
 src/backend/tcop/deparse_utility.c   |  3 +++
 src/include/commands/event_trigger.h |  2 ++
 src/include/nodes/parsenodes.h       | 10 ++++++++++
 src/include/tcop/deparse_utility.h   | 11 ++++++++++-
 9 files changed, 71 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index ba97550..a01b90b 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_ts_config.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
@@ -1914,6 +1915,32 @@ EventTriggerStashCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
 	MemoryContextSwitchTo(oldcxt);
 }
 
+void
+EventTriggerStashAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
+							   Oid *dictIds, int ndicts)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc0(sizeof(StashedCommand));
+	stashed->type = SCT_AlterTSConfig;
+	stashed->in_extension = creating_extension;
+	stashed->d.atscfg.tscfgOid = cfgId;
+	stashed->d.atscfg.dictIds = palloc(sizeof(Oid) * ndicts);
+	memcpy(stashed->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
+	stashed->d.atscfg.ndicts = ndicts;
+	stashed->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
 
 /*
  * EventTriggerStashAlterDefPrivs
@@ -2042,7 +2069,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 		if (cmd->type == SCT_Simple ||
 			cmd->type == SCT_AlterTable ||
 			cmd->type == SCT_AlterOpFamily ||
-			cmd->type == SCT_CreateOpClass)
+			cmd->type == SCT_CreateOpClass ||
+			cmd->type == SCT_AlterTSConfig)
 		{
 			const char *tag;
 			char	   *identity;
@@ -2063,6 +2091,10 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 				ObjectAddressSet(addr,
 								 OperatorClassRelationId,
 								 cmd->d.createopc.opcOid);
+			else if (cmd->type == SCT_AlterTSConfig)
+				ObjectAddressSet(addr,
+								 TSConfigRelationId,
+								 cmd->d.atscfg.tscfgOid);
 
 			tag = CreateCommandTag(cmd->parsetree);
 
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 4c404e7..13eff3d 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_func.h"
@@ -1442,6 +1443,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 			}
 		}
 	}
+
+	EventTriggerStashAlterTSConfig(stmt, cfgId, dictIds, ndict);
 }
 
 /*
@@ -1509,6 +1512,8 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
 
 		i++;
 	}
+
+	EventTriggerStashAlterTSConfig(stmt, cfgId, NULL, 0);
 }
 
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 029761e..457735e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3879,6 +3879,7 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 {
 	AlterTSConfigurationStmt *newnode = makeNode(AlterTSConfigurationStmt);
 
+	COPY_SCALAR_FIELD(kind);
 	COPY_NODE_FIELD(cfgname);
 	COPY_NODE_FIELD(tokentype);
 	COPY_NODE_FIELD(dicts);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 190e50a..d830c4c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1993,6 +1993,7 @@ static bool
 _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 							   const AlterTSConfigurationStmt *b)
 {
+	COMPARE_SCALAR_FIELD(kind);
 	COMPARE_NODE_FIELD(cfgname);
 	COMPARE_NODE_FIELD(tokentype);
 	COMPARE_NODE_FIELD(dicts);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3aa9e42..3a77120 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8918,6 +8918,7 @@ AlterTSConfigurationStmt:
 			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_ADD_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = $11;
@@ -8928,6 +8929,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = $11;
@@ -8938,6 +8940,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name any_with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_REPLACE_DICT;
 					n->cfgname = $5;
 					n->tokentype = NIL;
 					n->dicts = list_make2($9,$11);
@@ -8948,6 +8951,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name any_with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = list_make2($11,$13);
@@ -8958,6 +8962,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING FOR name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_DROP_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->missing_ok = false;
@@ -8966,6 +8971,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING IF_P EXISTS FOR name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_DROP_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $11;
 					n->missing_ok = true;
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 9a36894..b362adf 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -6903,6 +6903,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterDefaultPrivileges:
 			tree = deparse_AlterDefaultPrivilegesStmt(cmd);
 			break;
+		case SCT_AlterTSConfig:
+			tree = deparse_AlterTSConfigurationStmt(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 8d53e8b..b732fd9 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -78,6 +78,8 @@ extern void EventTriggerStashAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
 							List *operators, List *procedures);
 extern void EventTriggerStashCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
 							   List *operators, List *procedures);
+extern void EventTriggerStashAlterTSConfig(AlterTSConfigurationStmt *stmt,
+							   Oid cfgId, Oid *dicts, int ndicts);
 extern void EventTriggerStashAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3ecfbac..3739eca 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2851,9 +2851,19 @@ typedef struct AlterTSDictionaryStmt
 /*
  * TS Configuration stmts: DefineStmt, RenameStmt and DropStmt are default
  */
+typedef enum AlterTSConfigType
+{
+	ALTER_TSCONFIG_ADD_MAPPING,
+	ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN,
+	ALTER_TSCONFIG_REPLACE_DICT,
+	ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN,
+	ALTER_TSCONFIG_DROP_MAPPING
+} AlterTSConfigType;
+
 typedef struct AlterTSConfigurationStmt
 {
 	NodeTag		type;
+	AlterTSConfigType	kind;	/* ALTER_TSCONFIG_ADD_MAPPING, etc */
 	List	   *cfgname;		/* qualified name (list of Value strings) */
 
 	/*
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 6ee3ce2..0e80266 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -32,7 +32,8 @@ typedef enum StashedCommandType
 	SCT_Grant,
 	SCT_AlterOpFamily,
 	SCT_AlterDefaultPrivileges,
-	SCT_CreateOpClass
+	SCT_CreateOpClass,
+	SCT_AlterTSConfig
 } StashedCommandType;
 
 /*
@@ -91,6 +92,14 @@ typedef struct StashedCommand
 			List   *procedures;
 		} createopc;
 
+		/* ALTER TEXT SEARCH CONFIGURATION ADD/ALTER/DROP MAPPING */
+		struct
+		{
+			Oid		tscfgOid;
+			Oid	   *dictIds;
+			int		ndicts;
+		} atscfg;
+
 		/* ALTER DEFAULT PRIVILEGES */
 		struct
 		{
-- 
2.1.4

0034-deparse-support-ALTER-TSCONFIG.patchtext/x-diff; charset=us-asciiDownload
>From a85e3b46d6f7de8387833a86ece04f3e2164f7d8 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Mar 2015 19:40:56 -0300
Subject: [PATCH 34/37] deparse: support ALTER TSCONFIG

---
 src/backend/tcop/deparse_utility.c | 95 +++++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c         |  4 +-
 2 files changed, 97 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index b362adf..5287dd1 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1604,6 +1604,98 @@ deparse_DefineStmt(Oid objectId, Node *parsetree, ObjectAddress secondaryObj)
 	return defStmt;
 }
 
+static ObjTree *
+deparse_AlterTSConfigurationStmt(StashedCommand *cmd)
+{
+	AlterTSConfigurationStmt *node = (AlterTSConfigurationStmt *) cmd->parsetree;
+	ObjTree *config;
+	const char *const fmtcommon = "ALTER TEXT SEARCH CONFIGURATION %{identity}D";
+	char	   *fmtrest;
+	List	   *list;
+	ListCell   *cell;
+	int			i;
+
+	/* determine the format string appropriate to each subcommand */
+	switch (node->kind)
+	{
+		case ALTER_TSCONFIG_ADD_MAPPING:
+			fmtrest = "ADD MAPPING FOR %{tokentype:, }I WITH %{dictionaries:, }D";
+			break;
+
+		case ALTER_TSCONFIG_DROP_MAPPING:
+			fmtrest = "DROP MAPPING %{if_exists}s FOR %{tokentype}I";
+			break;
+
+		case ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN:
+			fmtrest = "ALTER MAPPING FOR %{tokentype:, }I WITH %{dictionaries:, }D";
+			break;
+
+		case ALTER_TSCONFIG_REPLACE_DICT:
+			fmtrest = "ALTER MAPPING REPLACE %{old_dictionary}D WITH %{new_dictionary}D";
+			break;
+
+		case ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN:
+			fmtrest = "ALTER MAPPING FOR %{tokentype:, }I REPLACE %{old_dictionary}D WITH %{new_dictionary}D";
+			break;
+	}
+	config = new_objtree_VA(psprintf("%s %s", fmtcommon, fmtrest),
+							1, "identity",
+							ObjTypeObject,
+							new_objtree_for_qualname_id(TSConfigRelationId,
+														cmd->d.atscfg.tscfgOid));
+
+	/* Add the affected token list, for subcommands that have one */
+	if (node->kind == ALTER_TSCONFIG_ADD_MAPPING ||
+		node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN ||
+		node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN ||
+		node->kind == ALTER_TSCONFIG_DROP_MAPPING)
+	{
+		list = NIL;
+		foreach(cell, node->tokentype)
+			list = lappend(list, new_string_object(strVal(lfirst(cell))));
+		append_array_object(config, "tokentype", list);
+	}
+
+	/* add further subcommand-specific elements */
+	if (node->kind == ALTER_TSCONFIG_ADD_MAPPING ||
+		node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN)
+	{
+		/* ADD MAPPING and ALTER MAPPING FOR need a list of dictionaries */
+		list = NIL;
+		for (i = 0; i < cmd->d.atscfg.ndicts; i++)
+		{
+			ObjTree	*dictobj;
+
+			dictobj = new_objtree_for_qualname_id(TSDictionaryRelationId,
+												  cmd->d.atscfg.dictIds[i]);
+			list = lappend(list,
+						   new_object_object(dictobj));
+		}
+		append_array_object(config, "dictionaries", list);
+	}
+	else if (node->kind == ALTER_TSCONFIG_REPLACE_DICT ||
+			 node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN)
+	{
+		/* the REPLACE forms want old and new dictionaries */
+		Assert(cmd->d.atscfg.ndicts == 2);
+		append_object_object(config, "old_dictionary",
+							 new_objtree_for_qualname_id(TSDictionaryRelationId,
+														 cmd->d.atscfg.dictIds[0]));
+		append_object_object(config, "new_dictionary",
+							 new_objtree_for_qualname_id(TSDictionaryRelationId,
+														 cmd->d.atscfg.dictIds[1]));
+	}
+	else
+	{
+		/* DROP wants the IF EXISTS clause */
+		Assert(node->kind == ALTER_TSCONFIG_DROP_MAPPING);
+		append_string_object(config, "if_exists",
+							 node->missing_ok ? "IF EXISTS" : "");
+	}
+
+	return config;
+}
+
 /*
  * deparse_CreateExtensionStmt
  *		deparse a CreateExtensionStmt
@@ -6778,7 +6870,8 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterTSConfigurationStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
 			break;
 
 		case T_DropStmt:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4886a3f..b545d30 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1420,7 +1420,9 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				/* commands are stashed in AlterTSConfiguration */
+				commandStashed = true;
 				break;
 
 			case T_AlterTableMoveAllStmt:
-- 
2.1.4

0035-deparse-test-add-pg_regress-dbname-deparse-option.patchtext/x-diff; charset=us-asciiDownload
>From ced223811999d90ad30c2e4497a3eb0d3c67cac6 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 10 Feb 2015 18:50:44 -0300
Subject: [PATCH 35/37] deparse-test: add pg_regress --dbname-deparse option

---
 src/test/regress/pg_regress.c      | 18 ++++++++++++++++++
 src/test/regress/pg_regress.h      |  1 +
 src/test/regress/pg_regress_main.c |  1 +
 3 files changed, 20 insertions(+)

diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index 9d4fb9a..31ea350 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -82,6 +82,7 @@ const char *pretty_diff_opts = "-w -C3";
 
 /* options settable from command line */
 _stringlist *dblist = NULL;
+char	    deparse_test_db[NAMEDATALEN];
 bool		debug = false;
 char	   *inputdir = ".";
 char	   *outputdir = ".";
@@ -589,6 +590,7 @@ convert_sourcefiles_in(char *source_subdir, char *dest_dir, char *dest_subdir, c
 			replace_string(line, "@testtablespace@", testtablespace);
 			replace_string(line, "@libdir@", dlpath);
 			replace_string(line, "@DLSUFFIX@", DLSUFFIX);
+			replace_string(line, "@deparse_test_db@", deparse_test_db);
 			fputs(line, outfile);
 		}
 		fclose(infile);
@@ -2140,6 +2142,8 @@ help(void)
 	printf(_("  --config-auth=DATADIR     update authentication settings for DATADIR\n"));
 	printf(_("  --create-role=ROLE        create the specified role before testing\n"));
 	printf(_("  --dbname=DB               use database DB (default \"regression\")\n"));
+	printf(_("  --dbname-deparse=DB       use database DB for DDL deparse test (default\n"));
+	printf(_("                            \"regression_deparse\")\n"));
 	printf(_("  --debug                   turn on debug mode in programs that are run\n"));
 	printf(_("  --dlpath=DIR              look for dynamic libraries in DIR\n"));
 	printf(_("  --encoding=ENCODING       use ENCODING as the encoding\n"));
@@ -2205,6 +2209,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 		{"load-extension", required_argument, NULL, 22},
 		{"extra-install", required_argument, NULL, 23},
 		{"config-auth", required_argument, NULL, 24},
+		{"dbname-deparse", required_argument, NULL, 25},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -2252,6 +2257,14 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 				 */
 				free_stringlist(&dblist);
 				split_to_stringlist(strdup(optarg), ", ", &dblist);
+
+				/*
+				 * We need to update the default deparse test db name
+				 * to be a suffix of the provided database name
+				 */
+				snprintf(deparse_test_db, sizeof(deparse_test_db),
+						 "%s_deparse",
+						 dblist->str);
 				break;
 			case 2:
 				debug = true;
@@ -2322,6 +2335,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 			case 24:
 				config_auth_datadir = pstrdup(optarg);
 				break;
+			case 25:
+				strncpy(deparse_test_db, optarg, sizeof(deparse_test_db));
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
@@ -2629,6 +2645,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 		{
 			for (sl = dblist; sl; sl = sl->next)
 				drop_database_if_exists(sl->str);
+			drop_database_if_exists(deparse_test_db);
 			for (sl = extraroles; sl; sl = sl->next)
 				drop_role_if_exists(sl->str);
 		}
@@ -2641,6 +2658,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	{
 		for (sl = dblist; sl; sl = sl->next)
 			create_database(sl->str);
+		create_database(deparse_test_db);
 		for (sl = extraroles; sl; sl = sl->next)
 			create_role(sl->str, dblist);
 	}
diff --git a/src/test/regress/pg_regress.h b/src/test/regress/pg_regress.h
index a7af7a5..4d64080 100644
--- a/src/test/regress/pg_regress.h
+++ b/src/test/regress/pg_regress.h
@@ -38,6 +38,7 @@ extern char *datadir;
 extern char *host_platform;
 
 extern _stringlist *dblist;
+extern char deparse_test_db[NAMEDATALEN];
 extern bool debug;
 extern char *inputdir;
 extern char *outputdir;
diff --git a/src/test/regress/pg_regress_main.c b/src/test/regress/pg_regress_main.c
index a403965..dcac70e 100644
--- a/src/test/regress/pg_regress_main.c
+++ b/src/test/regress/pg_regress_main.c
@@ -88,6 +88,7 @@ psql_init(int argc, char **argv)
 {
 	/* set default regression database name */
 	add_stringlist_item(&dblist, "regression");
+	strlcpy(deparse_test_db, "regression_deparse", NAMEDATALEN);
 }
 
 int
-- 
2.1.4

0036-deparse-pg_regress-support-generate_files_only.patchtext/x-diff; charset=us-asciiDownload
>From b0c69d0aed5736bb6793c84505d23fa9820ccb66 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 12 Feb 2015 15:17:19 -0300
Subject: [PATCH 36/37] deparse: pg_regress support generate_files_only

---
 src/test/regress/GNUmakefile  |  6 ++++++
 src/test/regress/pg_regress.c | 11 +++++++++++
 2 files changed, 17 insertions(+)

diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile
index 110eb80..9eafcd8 100644
--- a/src/test/regress/GNUmakefile
+++ b/src/test/regress/GNUmakefile
@@ -127,6 +127,12 @@ tablespace-setup:
 	rm -rf ./testtablespace
 	mkdir ./testtablespace
 
+# DDL-deparse setup
+
+.PHONY: generate-files
+generate-files:
+	$(top_builddir)/src/test/regress/pg_regress --generate-files-only --inputdir=$(srcdir)/input
+
 
 ##
 ## Run tests
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index 31ea350..d81e772 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -107,6 +107,7 @@ static char *user = NULL;
 static _stringlist *extraroles = NULL;
 static _stringlist *extra_install = NULL;
 static char *config_auth_datadir = NULL;
+static bool generate_files_only = false;
 
 /* internal variables */
 static const char *progname;
@@ -2210,6 +2211,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 		{"extra-install", required_argument, NULL, 23},
 		{"config-auth", required_argument, NULL, 24},
 		{"dbname-deparse", required_argument, NULL, 25},
+		{"generate-files-only", no_argument, NULL, 26},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -2338,6 +2340,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 			case 25:
 				strncpy(deparse_test_db, optarg, sizeof(deparse_test_db));
 				break;
+			case 26:
+				generate_files_only = true;
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
@@ -2378,6 +2383,12 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	outputdir = make_absolute_path(outputdir);
 	dlpath = make_absolute_path(dlpath);
 
+	if (generate_files_only)
+	{
+		convert_sourcefiles();
+		exit(0);
+	}
+
 	/*
 	 * Initialization
 	 */
-- 
2.1.4

0037-deparse-initial-testing-framework.patchtext/x-diff; charset=us-asciiDownload
>From b1a8fbd4c7df5a8d8a150339db812ea56c992f7c Mon Sep 17 00:00:00 2001
From: Ian Barwick <ian@2ndquadrant.com>
Date: Thu, 27 Nov 2014 16:05:13 +0900
Subject: [PATCH 37/37] deparse: initial testing framework

Test with:

make -C src/test/regress deparsecheck
---
 src/test/regress/GNUmakefile                       |  15 ++
 src/test/regress/expected/alter_table.out          |   4 +-
 .../regress/expected/create_function_ddl_demo.out  |   4 +
 src/test/regress/expected/deparse_init.out         | 177 ++++++++++++++++++++
 src/test/regress/expected/sanity_check.out         |   5 +-
 src/test/regress/input/deparse_test.source         |  57 +++++++
 src/test/regress/output/deparse_test.source        |  53 ++++++
 src/test/regress/pg_regress.c                      |  10 +-
 src/test/regress/sql/alter_table.sql               |   4 +-
 src/test/regress/sql/create_function_ddl_demo.sql  |   4 +
 src/test/regress/sql/deparse_init.sql              | 181 +++++++++++++++++++++
 src/test/regress/sql/sanity_check.sql              |   5 +-
 12 files changed, 511 insertions(+), 8 deletions(-)
 create mode 100644 src/test/regress/expected/create_function_ddl_demo.out
 create mode 100644 src/test/regress/expected/deparse_init.out
 create mode 100644 src/test/regress/input/deparse_test.source
 create mode 100644 src/test/regress/output/deparse_test.source
 create mode 100644 src/test/regress/sql/create_function_ddl_demo.sql
 create mode 100644 src/test/regress/sql/deparse_init.sql

diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile
index 9eafcd8..b4ed761 100644
--- a/src/test/regress/GNUmakefile
+++ b/src/test/regress/GNUmakefile
@@ -133,6 +133,10 @@ tablespace-setup:
 generate-files:
 	$(top_builddir)/src/test/regress/pg_regress --generate-files-only --inputdir=$(srcdir)/input
 
+ddl_deparse_schedule: serial_schedule
+	echo "test: deparse_init" > $@
+	grep -v tablespace $(srcdir)/serial_schedule >> $@
+	echo "test: deparse_test" >> $@
 
 ##
 ## Run tests
@@ -158,6 +162,17 @@ installcheck-tests: all tablespace-setup
 standbycheck: all
 	$(pg_regress_installcheck) $(REGRESS_OPTS) --schedule=$(srcdir)/standby_schedule --use-existing
 
+deparsecheck: REGRESS_OPTS += --keep-install
+deparsecheck: all generate-files ddl_deparse_schedule
+	$(pg_regress_check) $(REGRESS_OPTS) --schedule=ddl_deparse_schedule
+	grep ERROR results/deparse_dump.out > results/deparse_dump.errors
+	$(bindir)/pg_ctl -w start -D ./tmp_check/data -l /dev/null
+	$(bindir)/pg_dump -d regression_deparse -s -f results/deparse.dump
+	$(bindir)/pg_dump -d regression -s -f results/regression.dump
+	$(bindir)/pg_ctl -D ./tmp_check/data stop
+	diff -c results/deparse.dump results/regression.dump
+	diff -c results/deparse_dump.errors $(srcdir)/expected/deparse_dump.errors
+
 # old interfaces follow...
 
 runcheck: check
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 6a31df7..ea843ae 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1858,7 +1858,9 @@ where virtualtransaction = (
         from pg_locks
         where transactionid = txid_current()::integer)
 and locktype = 'relation'
-and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
+and relnamespace NOT IN (
+	select oid from pg_namespace
+	where nspname IN ('pg_catalog', 'pg_deparse'))
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
diff --git a/src/test/regress/expected/create_function_ddl_demo.out b/src/test/regress/expected/create_function_ddl_demo.out
new file mode 100644
index 0000000..ff8a1d9
--- /dev/null
+++ b/src/test/regress/expected/create_function_ddl_demo.out
@@ -0,0 +1,4 @@
+CREATE FUNCTION check_foreign_key ()
+	RETURNS trigger
+	AS '/space/sda1/ibarwick/2ndquadrant_bdr/src/test/regress/refint.so'
+	LANGUAGE C;
diff --git a/src/test/regress/expected/deparse_init.out b/src/test/regress/expected/deparse_init.out
new file mode 100644
index 0000000..10ec843
--- /dev/null
+++ b/src/test/regress/expected/deparse_init.out
@@ -0,0 +1,177 @@
+--
+-- DEPARSE_INIT
+--
+CREATE SCHEMA deparse;
+UPDATE pg_namespace SET nspname = 'pg_deparse' WHERE nspname = 'deparse';
+CREATE UNLOGGED TABLE pg_deparse.deparse_test_commands (
+  backend_id int,
+  backend_start timestamptz,
+  lsn pg_lsn,
+  ord integer,
+  command TEXT
+);
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_ddl_command_end()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+BEGIN
+	BEGIN
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, command, ord, lsn)
+		SELECT id, pg_stat_get_backend_start(id),
+		     pg_event_trigger_expand_command(command), ordinality, lsn
+		FROM pg_event_trigger_get_creation_commands() WITH ORDINALITY,
+		pg_current_xlog_insert_location() lsn,
+		pg_stat_get_backend_idset() id
+		 WHERE pg_stat_get_backend_pid(id) = pg_backend_pid() AND
+		NOT command_tag = 'CREATE TABLE AS EXECUTE';
+	EXCEPTION WHEN OTHERS THEN 
+			RAISE WARNING 'state: % errm: %', sqlstate, sqlerrm;
+	END;
+END;
+$fn$;
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_sql_drop()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+DECLARE
+fmt	TEXT;
+obj RECORD;
+i	integer = 1;
+BEGIN
+
+	/* This function runs in the sql_drop event trigger.
+     *
+	 * When it runs, we know that all objects reported by the
+	 * pg_event_trigger_dropped_objects() function marked as "original" have
+	 * been mentioned in the DROP command, either directly by name or
+	 * indirectly by owner (DROP OWNED BY).  Since no objects that depend on
+	 * them can persist after that, we can replicate the effect of that by
+	 * executing an equivalent "DROP IF EXISTS object ... CASCADE".  CASCADE
+	 * lets the deletion work even in presence of objects that appear further
+	 * down in the return set of pg_event_trigger_dropped_objects, while IF
+	 * EXISTS let the deletion silently do nothing if the object was already
+	 * dropped because it was dependent on another object before it in the same
+	 * result set.
+     *
+     * (In general, it is impossible to reorder the result set in a way that
+     * would be completely free of dependency issues.)
+     */
+
+	FOR obj IN
+	SELECT object_type, address_names, address_args, object_identity
+	  FROM pg_event_trigger_dropped_objects()
+	 WHERE original
+	  LOOP
+
+		-- special case for default acls: ignore them.
+		IF obj.object_type = 'default acl' THEN
+			CONTINUE;
+		END IF;
+
+		/*
+		 * special cases for objects that are part of other objects: drop
+		 * each in a separate command.  Since we only deal with "original"
+		 * objects, these would not be reported in the complex case of
+		 * DROP OWNED.
+		 */
+		IF obj.object_type = 'table column' OR obj.object_type = 'foreign table column' THEN
+			fmt = format('ALTER TABLE %I.%I DROP COLUMN %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'composite type column' THEN
+			fmt = format('ALTER TYPE %I.%I DROP ATTRIBUTE %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TYPE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'table constraint' THEN
+			fmt = format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'domain constraint' THEN
+			fmt = format('ALTER DOMAIN %s DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_args[1]);
+		ELSIF obj.object_type = 'default value' THEN
+			fmt = format('ALTER TABLE %I.%I ALTER COLUMN %I DROP DEFAULT',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+		ELSIF obj.object_type = 'foreign-data wrapper' THEN
+			fmt = format('DROP FOREIGN DATA WRAPPER IF EXISTS %s CASCADE',
+				obj.object_identity);
+		ELSIF obj.object_type = 'user mapping' THEN
+			fmt = format('DROP USER MAPPING FOR %I SERVER %I',
+				obj.address_names[1], obj.address_args[1]);
+		ELSIF obj.object_type = 'operator of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP OPERATOR %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'function of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP FUNCTION %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSE
+			-- all other cases
+			fmt := format('DROP %s IF EXISTS %s CASCADE',
+				obj.object_type, obj.object_identity);
+		END IF;
+
+		IF fmt IS NULL THEN
+			CONTINUE;
+		END IF;
+
+		fmt := fmt || ' /* DROP support */';
+
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, lsn, ord, command)
+			 SELECT id, pg_stat_get_backend_start(id),
+			        pg_current_xlog_insert_location(), i, fmt
+			   FROM pg_stat_get_backend_idset() id
+			  WHERE pg_stat_get_backend_pid(id) = pg_backend_pid();
+		i := i + 1;
+	END LOOP;
+END;
+$fn$;
+CREATE OR REPLACE FUNCTION pg_deparse.output_commands() RETURNS SETOF text LANGUAGE PLPGSQL AS $$
+DECLARE
+        cmd text;
+        prev_id int = -1;
+        prev_start timestamptz = '-infinity';
+        sess_id int;
+        sess_start timestamptz;
+BEGIN
+   FOR cmd, sess_id, sess_start IN
+			   SELECT command, backend_id, backend_start
+                 FROM pg_deparse.deparse_test_commands
+			 ORDER BY lsn, ord
+   LOOP
+          IF (sess_id, sess_start) <> (prev_id, prev_start) THEN
+                prev_id := sess_id;
+                prev_start := sess_start;
+                RETURN NEXT '\c';
+          END IF;
+      RETURN NEXT cmd || ';' ;
+   END LOOP;
+END;
+$$;
+CREATE EVENT TRIGGER deparse_test_trg_sql_drop
+  ON sql_drop
+  EXECUTE PROCEDURE pg_deparse.deparse_test_sql_drop();
+CREATE EVENT TRIGGER deparse_test_trg_ddl_command_end
+  ON ddl_command_end
+  EXECUTE PROCEDURE pg_deparse.deparse_test_ddl_command_end();
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index c7be273..5239fec 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -3,13 +3,14 @@ VACUUM;
 -- sanity check, if we don't have indices the test will take years to
 -- complete.  But skip TOAST relations (since they will have varying
 -- names depending on the current OID counter) as well as temp tables
--- of other backends (to avoid timing-dependent behavior).
+-- of other backends (to avoid timing-dependent behavior).  Also exclude
+-- the schema used for the deparse test, as it might not be there at all.
 --
 -- temporarily disable fancy output, so catalog changes create less diff noise
 \a\t
 SELECT relname, relhasindex
    FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace
-   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_') IS NOT TRUE
+   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_' OR nspname ~ '^pg_deparse') IS NOT TRUE
    ORDER BY relname;
 a|f
 a_star|f
diff --git a/src/test/regress/input/deparse_test.source b/src/test/regress/input/deparse_test.source
new file mode 100644
index 0000000..e7ba0dd
--- /dev/null
+++ b/src/test/regress/input/deparse_test.source
@@ -0,0 +1,57 @@
+---
+--- DEPARSE_TEST
+---
+
+-- create roles used throughout the tests
+create role clstr_user;
+create role "current_user";
+create role foreign_data_user;
+create role "Public";
+create role regressgroup1;
+create role regressgroup2;
+create role regression_bob;
+create role regression_group;
+create role regression_user1;
+create role regression_user2;
+create role regression_user3;
+create role regression_user;
+create role regresslo;
+create role regress_rol_lock1;
+create role regress_test_indirect;
+create role regress_test_role;
+create role regress_test_role2;
+create role regress_test_role_super superuser;
+create role regressuser1;
+create role regressuser2;
+create role regressuser3;
+create role regressuser4;
+create role regressuser5;
+create role regtest_unpriv_user;
+create role regtest_addr_user;
+create role regtest_alter_user1;
+create role regtest_alter_user2;
+create role regtest_alter_user3;
+create role rls_regress_group1;
+create role rls_regress_group2;
+create role rls_regress_user0;
+create role rls_regress_user1;
+create role rls_regress_user2;
+create role rls_regress_exempt_user;
+create role schemauser2;
+create role seclabel_user1;
+create role seclabel_user2;
+create role selinto_user;
+create role testrol1;
+create role testrol2;
+create role testrolx;
+create role unprivileged_role;
+create role "user";
+create role view_user2;
+
+\pset format unaligned
+\pset tuples_only
+\o ./sql/deparse_dump.sql
+
+SELECT * FROM pg_deparse.output_commands();
+
+\! @abs_builddir@/../../bin/psql/psql --dbname=@deparse_test_db@ -e < ./sql/deparse_dump.sql > results/deparse_dump.out 2>&1
diff --git a/src/test/regress/output/deparse_test.source b/src/test/regress/output/deparse_test.source
new file mode 100644
index 0000000..f083e7c
--- /dev/null
+++ b/src/test/regress/output/deparse_test.source
@@ -0,0 +1,53 @@
+---
+--- DEPARSE_TEST
+---
+-- create roles used throughout the tests
+create role clstr_user;
+create role "current_user";
+create role foreign_data_user;
+create role "Public";
+create role regressgroup1;
+create role regressgroup2;
+create role regression_bob;
+create role regression_group;
+create role regression_user1;
+create role regression_user2;
+create role regression_user3;
+create role regression_user;
+create role regresslo;
+create role regress_rol_lock1;
+create role regress_test_indirect;
+create role regress_test_role;
+create role regress_test_role2;
+create role regress_test_role_super superuser;
+create role regressuser1;
+create role regressuser2;
+create role regressuser3;
+create role regressuser4;
+create role regressuser5;
+create role regtest_unpriv_user;
+create role regtest_addr_user;
+create role regtest_alter_user1;
+create role regtest_alter_user2;
+create role regtest_alter_user3;
+create role rls_regress_group1;
+create role rls_regress_group2;
+create role rls_regress_user0;
+create role rls_regress_user1;
+create role rls_regress_user2;
+create role rls_regress_exempt_user;
+create role schemauser2;
+create role seclabel_user1;
+create role seclabel_user2;
+create role selinto_user;
+create role testrol1;
+create role testrol2;
+create role testrolx;
+create role unprivileged_role;
+create role "user";
+create role view_user2;
+\pset format unaligned
+\pset tuples_only
+\o ./sql/deparse_dump.sql
+SELECT * FROM pg_deparse.output_commands();
+\! @abs_builddir@/../../bin/psql/psql --dbname=@deparse_test_db@ -e < ./sql/deparse_dump.sql > results/deparse_dump.out 2>&1
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index d81e772..97d2e5b 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -108,6 +108,7 @@ static _stringlist *extraroles = NULL;
 static _stringlist *extra_install = NULL;
 static char *config_auth_datadir = NULL;
 static bool generate_files_only = false;
+static bool keep_install = false;
 
 /* internal variables */
 static const char *progname;
@@ -2161,6 +2162,7 @@ help(void)
 	printf(_("                            (can be used multiple times to concatenate)\n"));
 	printf(_("  --temp-install=DIR        create a temporary installation in DIR\n"));
 	printf(_("  --use-existing            use an existing installation\n"));
+	printf(_("  --keep-install            don't destroy nor stop the installation\n"));
 	printf(_("\n"));
 	printf(_("Options for \"temp-install\" mode:\n"));
 	printf(_("  --extra-install=DIR       additional directory to install (e.g., contrib)\n"));
@@ -2212,6 +2214,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 		{"config-auth", required_argument, NULL, 24},
 		{"dbname-deparse", required_argument, NULL, 25},
 		{"generate-files-only", no_argument, NULL, 26},
+		{"keep-install", no_argument, NULL, 27},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -2343,6 +2346,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 			case 26:
 				generate_files_only = true;
 				break;
+			case 27:
+				keep_install = true;
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
@@ -2692,7 +2698,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	/*
 	 * Shut down temp installation's postmaster
 	 */
-	if (temp_install)
+	if (temp_install && !keep_install)
 	{
 		header(_("shutting down postmaster"));
 		stop_postmaster();
@@ -2703,7 +2709,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	 * conserve disk space.  (If there were errors, we leave the installation
 	 * in place for possible manual investigation.)
 	 */
-	if (temp_install && fail_count == 0 && fail_ignore_count == 0)
+	if (temp_install && fail_count == 0 && fail_ignore_count == 0 && !keep_install)
 	{
 		header(_("removing temporary installation"));
 		if (!rmtree(temp_install, true))
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index b5ee7b0..1342c29 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1289,7 +1289,9 @@ where virtualtransaction = (
         from pg_locks
         where transactionid = txid_current()::integer)
 and locktype = 'relation'
-and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
+and relnamespace NOT IN (
+	select oid from pg_namespace
+	where nspname IN ('pg_catalog', 'pg_deparse'))
 and c.relname != 'my_locks'
 group by c.relname;
 
diff --git a/src/test/regress/sql/create_function_ddl_demo.sql b/src/test/regress/sql/create_function_ddl_demo.sql
new file mode 100644
index 0000000..ac29426
--- /dev/null
+++ b/src/test/regress/sql/create_function_ddl_demo.sql
@@ -0,0 +1,4 @@
+CREATE FUNCTION check_foreign_key ()
+	RETURNS trigger
+	AS '/space/sda1/ibarwick/2ndquadrant_bdr/src/test/regress/refint.so'
+	LANGUAGE C;
\ No newline at end of file
diff --git a/src/test/regress/sql/deparse_init.sql b/src/test/regress/sql/deparse_init.sql
new file mode 100644
index 0000000..e90dcc1
--- /dev/null
+++ b/src/test/regress/sql/deparse_init.sql
@@ -0,0 +1,181 @@
+--
+-- DEPARSE_INIT
+--
+CREATE SCHEMA deparse;
+UPDATE pg_namespace SET nspname = 'pg_deparse' WHERE nspname = 'deparse';
+CREATE UNLOGGED TABLE pg_deparse.deparse_test_commands (
+  backend_id int,
+  backend_start timestamptz,
+  lsn pg_lsn,
+  ord integer,
+  command TEXT
+);
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_ddl_command_end()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+BEGIN
+	BEGIN
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, command, ord, lsn)
+		SELECT id, pg_stat_get_backend_start(id),
+		     pg_event_trigger_expand_command(command), ordinality, lsn
+		FROM pg_event_trigger_get_creation_commands() WITH ORDINALITY,
+		pg_current_xlog_insert_location() lsn,
+		pg_stat_get_backend_idset() id
+		 WHERE pg_stat_get_backend_pid(id) = pg_backend_pid() AND
+		NOT command_tag = 'CREATE TABLE AS EXECUTE';
+	EXCEPTION WHEN OTHERS THEN 
+			RAISE WARNING 'state: % errm: %', sqlstate, sqlerrm;
+	END;
+END;
+$fn$;
+
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_sql_drop()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+DECLARE
+fmt	TEXT;
+obj RECORD;
+i	integer = 1;
+BEGIN
+
+	/* This function runs in the sql_drop event trigger.
+     *
+	 * When it runs, we know that all objects reported by the
+	 * pg_event_trigger_dropped_objects() function marked as "original" have
+	 * been mentioned in the DROP command, either directly by name or
+	 * indirectly by owner (DROP OWNED BY).  Since no objects that depend on
+	 * them can persist after that, we can replicate the effect of that by
+	 * executing an equivalent "DROP IF EXISTS object ... CASCADE".  CASCADE
+	 * lets the deletion work even in presence of objects that appear further
+	 * down in the return set of pg_event_trigger_dropped_objects, while IF
+	 * EXISTS let the deletion silently do nothing if the object was already
+	 * dropped because it was dependent on another object before it in the same
+	 * result set.
+     *
+     * (In general, it is impossible to reorder the result set in a way that
+     * would be completely free of dependency issues.)
+     */
+
+	FOR obj IN
+	SELECT object_type, address_names, address_args, object_identity
+	  FROM pg_event_trigger_dropped_objects()
+	 WHERE original
+	  LOOP
+
+		-- special case for default acls: ignore them.
+		IF obj.object_type = 'default acl' THEN
+			CONTINUE;
+		END IF;
+
+		/*
+		 * special cases for objects that are part of other objects: drop
+		 * each in a separate command.  Since we only deal with "original"
+		 * objects, these would not be reported in the complex case of
+		 * DROP OWNED.
+		 */
+		IF obj.object_type = 'table column' OR obj.object_type = 'foreign table column' THEN
+			fmt = format('ALTER TABLE %I.%I DROP COLUMN %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'composite type column' THEN
+			fmt = format('ALTER TYPE %I.%I DROP ATTRIBUTE %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TYPE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'table constraint' THEN
+			fmt = format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'domain constraint' THEN
+			fmt = format('ALTER DOMAIN %s DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_args[1]);
+		ELSIF obj.object_type = 'default value' THEN
+			fmt = format('ALTER TABLE %I.%I ALTER COLUMN %I DROP DEFAULT',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+		ELSIF obj.object_type = 'foreign-data wrapper' THEN
+			fmt = format('DROP FOREIGN DATA WRAPPER IF EXISTS %s CASCADE',
+				obj.object_identity);
+		ELSIF obj.object_type = 'user mapping' THEN
+			fmt = format('DROP USER MAPPING FOR %I SERVER %I',
+				obj.address_names[1], obj.address_args[1]);
+		ELSIF obj.object_type = 'operator of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP OPERATOR %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'function of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP FUNCTION %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSE
+			-- all other cases
+			fmt := format('DROP %s IF EXISTS %s CASCADE',
+				obj.object_type, obj.object_identity);
+		END IF;
+
+		IF fmt IS NULL THEN
+			CONTINUE;
+		END IF;
+
+		fmt := fmt || ' /* DROP support */';
+
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, lsn, ord, command)
+			 SELECT id, pg_stat_get_backend_start(id),
+			        pg_current_xlog_insert_location(), i, fmt
+			   FROM pg_stat_get_backend_idset() id
+			  WHERE pg_stat_get_backend_pid(id) = pg_backend_pid();
+		i := i + 1;
+	END LOOP;
+END;
+$fn$;
+
+CREATE OR REPLACE FUNCTION pg_deparse.output_commands() RETURNS SETOF text LANGUAGE PLPGSQL AS $$
+DECLARE
+        cmd text;
+        prev_id int = -1;
+        prev_start timestamptz = '-infinity';
+        sess_id int;
+        sess_start timestamptz;
+BEGIN
+   FOR cmd, sess_id, sess_start IN
+			   SELECT command, backend_id, backend_start
+                 FROM pg_deparse.deparse_test_commands
+			 ORDER BY lsn, ord
+   LOOP
+          IF (sess_id, sess_start) <> (prev_id, prev_start) THEN
+                prev_id := sess_id;
+                prev_start := sess_start;
+                RETURN NEXT '\c';
+          END IF;
+      RETURN NEXT cmd || ';' ;
+   END LOOP;
+END;
+$$;
+
+CREATE EVENT TRIGGER deparse_test_trg_sql_drop
+  ON sql_drop
+  EXECUTE PROCEDURE pg_deparse.deparse_test_sql_drop();
+
+CREATE EVENT TRIGGER deparse_test_trg_ddl_command_end
+  ON ddl_command_end
+  EXECUTE PROCEDURE pg_deparse.deparse_test_ddl_command_end();
diff --git a/src/test/regress/sql/sanity_check.sql b/src/test/regress/sql/sanity_check.sql
index 0da838e..1d7a276 100644
--- a/src/test/regress/sql/sanity_check.sql
+++ b/src/test/regress/sql/sanity_check.sql
@@ -4,7 +4,8 @@ VACUUM;
 -- sanity check, if we don't have indices the test will take years to
 -- complete.  But skip TOAST relations (since they will have varying
 -- names depending on the current OID counter) as well as temp tables
--- of other backends (to avoid timing-dependent behavior).
+-- of other backends (to avoid timing-dependent behavior).  Also exclude
+-- the schema used for the deparse test, as it might not be there at all.
 --
 
 -- temporarily disable fancy output, so catalog changes create less diff noise
@@ -12,7 +13,7 @@ VACUUM;
 
 SELECT relname, relhasindex
    FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace
-   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_') IS NOT TRUE
+   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_' OR nspname ~ '^pg_deparse') IS NOT TRUE
    ORDER BY relname;
 
 -- restore normal output mode
-- 
2.1.4

#36Ryan Pedela
rpedela@datalanche.com
In reply to: Alvaro Herrera (#35)
Re: deparsing utility commands

On Wed, Mar 25, 2015 at 11:59 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

* Should we prohibit DDL from within event triggers?

Please don't prohibit DDL unless there is a really, really good reason to
do so. I have several use cases in mind for event triggers, but they are
only useful if I can perform DDL.

For example, I want to create an Elasticsearch FDW and use that to index
and search Postgres tables. When a table is created, I am planning to use
an event trigger to capture the CREATE event and automatically create a
foreign table via the Elasticsearch FDW. In addition, I would add normal
triggers to the new table which capture DML and update the foreign table
accordingly. In other words, I want to use FDWs and event triggers to
automatically sync table DDL and DML to Elasticsearch.

#37Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#35)
Re: deparsing utility commands

Alvaro Herrera wrote:

Here's an updated version of this series.

I just pushed patches 0001 and 0002, with very small tweaks; those had
already been reviewed and it didn't seem like there was much
controversy.

To test the posted series it's probably easiest to
git checkout b3196e65f5bfc997ec7fa3f91645a09289c10dee
and apply it all on top of that.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#38Robert Haas
robertmhaas@gmail.com
In reply to: Ryan Pedela (#36)
Re: deparsing utility commands

On Wed, Mar 25, 2015 at 3:21 PM, Ryan Pedela <rpedela@datalanche.com> wrote:

On Wed, Mar 25, 2015 at 11:59 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

* Should we prohibit DDL from within event triggers?

Please don't prohibit DDL unless there is a really, really good reason to do
so. I have several use cases in mind for event triggers, but they are only
useful if I can perform DDL.

For example, I want to create an Elasticsearch FDW and use that to index and
search Postgres tables. When a table is created, I am planning to use an
event trigger to capture the CREATE event and automatically create a foreign
table via the Elasticsearch FDW. In addition, I would add normal triggers to
the new table which capture DML and update the foreign table accordingly. In
other words, I want to use FDWs and event triggers to automatically sync
table DDL and DML to Elasticsearch.

+1. I think that if it's unsafe to run DDL from with event triggers,
then that might be a sign that it's not safe to run *any* user-defined
code at that location. I think it's the job of the event trigger to
make sure that it doesn't assume anything about what may have changed
while the user-defined code was running.

--
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

#39Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#38)
Re: deparsing utility commands

On 2015-04-02 12:05:13 -0400, Robert Haas wrote:

On Wed, Mar 25, 2015 at 3:21 PM, Ryan Pedela <rpedela@datalanche.com> wrote:

On Wed, Mar 25, 2015 at 11:59 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

* Should we prohibit DDL from within event triggers?

Please don't prohibit DDL unless there is a really, really good reason to do
so. I have several use cases in mind for event triggers, but they are only
useful if I can perform DDL.

For example, I want to create an Elasticsearch FDW and use that to index and
search Postgres tables. When a table is created, I am planning to use an
event trigger to capture the CREATE event and automatically create a foreign
table via the Elasticsearch FDW. In addition, I would add normal triggers to
the new table which capture DML and update the foreign table accordingly. In
other words, I want to use FDWs and event triggers to automatically sync
table DDL and DML to Elasticsearch.

+1. I think that if it's unsafe to run DDL from with event triggers,
then that might be a sign that it's not safe to run *any* user-defined
code at that location. I think it's the job of the event trigger to
make sure that it doesn't assume anything about what may have changed
while the user-defined code was running.

First of: I don't see a fundamental reason to forbid it, I think it's
just simpler to analyze that way. But I'm unconvinced by that
reasoning. As you know we prohibit executing DDL on some objects in
*lots* of places c.f. CheckTableNotInUse(), without that imo being a
sign of a more fundamental problem.

Greetings,

Andres Freund

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

#40Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#1)
Re: deparsing utility commands

Executive summary:

There is now a CommandDeparse_hook;
deparse_utility_command is provided as an extension, intended for 9.6;
rest of patch would be pushed to 9.5.

Long version:

I've made command deparsing hookable. Attached there are three patches:
the first patch contains changes to core that just add the "command
list" stuff, so that on ddl_command_end there is access to what has been
executed. This includes the OID of the object just created, the command
tag, and assorted other details; the deparsed command in JSON format is
not immediately part of the result.

The third patch contains all the deparse code, packaged as a contrib
module and extension named ddl_deparse. Essentially, it's everything
that was previously in tcop/deparse_utility.c and utils/adt/ddl_json.c:
the stuff that takes the parsenode and OID of a command execution and
turns it into a JSON blob, and also the support function that takes the
JSON blob and converts back into the plain text rendering of the
command.

The second patch contains some changes to core code that support the
ddl_deparse extension; mainly some ruleutils.c changes.

What links patches 0001 and 0003 is a hook, CommandDeparse_hook. If
unset, the pg_event_trigger_ddl_commands function returns some
boilerplate text like "no deparse function installed"; if the extension
is installed, the JSON rendering is returned instead and can be used
with the ddl_deparse_expand_command() function.

The rationale for doing things this way is that it will be useful to
have 9.5 expose the pg_event_trigger_ddl_commands() function for various
uses, while we refine the JSON bits some more and get it committed for
9.6. In reviews, it's clear that there's some more bits to fiddle so
that it can be as general as possible. I think we should label the
whole DDL command reporting as experimental in 9.5 and subject to
change, so that we can just remove the hook in 9.6 when the ddl_deparse
thing becomes part of core.

Thoughts?

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#41Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#40)
3 attachment(s)
Re: deparsing utility commands

Attachments.

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

Attachments:

0001-deparse-infrastructure-needed-for-command-deparsing.patchtext/x-diff; charset=us-asciiDownload
>From 27993aa9bac2bae0eaaabe0b6ef11bd14313a521 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 24 Sep 2014 15:53:04 -0300
Subject: [PATCH 1/3] deparse: infrastructure needed for command deparsing

This patch introduces unused infrastructure for command deparsing.
Each command executed is stored in a list if ddl_command_end event
triggers are installed; when that event fires, the trigger function can
call pg_event_trigger_ddl_commands() to obtain the list.  A hook is
provided there so that some deparsed form of the command is returned.
---
 src/backend/catalog/aclchk.c         |  37 +-
 src/backend/commands/event_trigger.c | 709 ++++++++++++++++++++++++++++++++++-
 src/backend/commands/opclasscmds.c   |  45 +--
 src/backend/commands/schemacmds.c    |  15 +
 src/backend/commands/tablecmds.c     |  11 +
 src/backend/commands/tsearchcmds.c   |   5 +
 src/backend/nodes/copyfuncs.c        |   1 +
 src/backend/nodes/equalfuncs.c       |   1 +
 src/backend/parser/gram.y            |   6 +
 src/backend/tcop/utility.c           | 276 ++++++++++----
 src/backend/utils/adt/format_type.c  |  14 +-
 src/include/catalog/opfam_internal.h |  28 ++
 src/include/catalog/pg_proc.h        |   2 +
 src/include/commands/defrem.h        |   1 +
 src/include/commands/event_trigger.h |  29 ++
 src/include/commands/extension.h     |   2 +-
 src/include/nodes/parsenodes.h       |  10 +
 src/include/tcop/deparse_utility.h   | 106 ++++++
 src/include/utils/aclchk_internal.h  |  45 +++
 src/include/utils/builtins.h         |   1 +
 20 files changed, 1207 insertions(+), 137 deletions(-)
 create mode 100644 src/include/catalog/opfam_internal.h
 create mode 100644 src/include/tcop/deparse_utility.h
 create mode 100644 src/include/utils/aclchk_internal.h

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 8e75c27..943909c 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "foreign/foreign.h"
@@ -56,6 +57,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
 #include "utils/acl.h"
+#include "utils/aclchk_internal.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
@@ -65,32 +67,6 @@
 
 
 /*
- * The information about one Grant/Revoke statement, in internal format: object
- * and grantees names have been turned into Oids, the privilege list is an
- * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
- * all_privs is true, 'privileges' will be internally set to the right kind of
- * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
- * InternalGrant struct!)
- *
- * Note: 'all_privs' and 'privileges' represent object-level privileges only.
- * There might also be column-level privilege specifications, which are
- * represented in col_privs (this is a list of untransformed AccessPriv nodes).
- * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
- */
-typedef struct
-{
-	bool		is_grant;
-	GrantObjectType objtype;
-	List	   *objects;
-	bool		all_privs;
-	AclMode		privileges;
-	List	   *col_privs;
-	List	   *grantees;
-	bool		grant_option;
-	DropBehavior behavior;
-} InternalGrant;
-
-/*
  * Internal format used by ALTER DEFAULT PRIVILEGES.
  */
 typedef struct
@@ -605,6 +581,15 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	if (EventTriggerSupportsGrantObjectType(istmt->objtype))
+		EventTriggerCollectGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 0026e53..3244f41 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -20,21 +20,28 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_ts_config.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/trigger.h"
 #include "funcapi.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -44,6 +51,9 @@
 
 typedef struct EventTriggerQueryState
 {
+	/* memory context for this state's objects */
+	MemoryContext cxt;
+
 	/* sql_drop */
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
@@ -52,7 +62,10 @@ typedef struct EventTriggerQueryState
 	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite event */
 	int			table_rewrite_reason;	/* AT_REWRITE reason */
 
-	MemoryContext cxt;
+	/* Support for command collection */
+	bool		commandCollectionInhibited;
+	CollectedCommand *currentCommand;
+	List	   *commandList;		/* list of CollectedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -71,6 +84,7 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
 	{"AGGREGATE", true},
 	{"CAST", true},
@@ -125,6 +139,9 @@ typedef struct SQLDropObject
 	slist_node	next;
 } SQLDropObject;
 
+/* Hook for plugins to get control in pg_event_trigger_ddl_commands */
+CommandDeparse_hook_type CommandDeparse_hook = NULL;
+
 static void AlterEventTriggerOwner_internal(Relation rel,
 								HeapTuple tup,
 								Oid newOwnerId);
@@ -138,6 +155,7 @@ static Oid insert_event_trigger_tuple(char *trigname, char *eventname,
 static void validate_ddl_tags(const char *filtervar, List *taglist);
 static void validate_table_rewrite_tags(const char *filtervar, List *taglist);
 static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
+static const char *stringify_grantobjtype(GrantObjectType objtype);
 
 /*
  * Create an event trigger.
@@ -1203,9 +1221,9 @@ EventTriggerBeginCompleteQuery(void)
 	MemoryContext cxt;
 
 	/*
-	 * Currently, sql_drop and table_rewrite events are the only reason to
-	 * have event trigger state at all; so if there are none, don't install
-	 * one.
+	 * Currently, sql_drop, table_rewrite, ddL_command_end events are the only
+	 * reason to have event trigger state at all; so if there are none, don't
+	 * install one.
 	 */
 	if (!trackDroppedObjectsNeeded())
 		return false;
@@ -1221,6 +1239,10 @@ EventTriggerBeginCompleteQuery(void)
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
 
+	state->commandCollectionInhibited = currentEventTriggerState ?
+		currentEventTriggerState->commandCollectionInhibited : false;
+	state->currentCommand = NULL;
+	state->commandList = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
 
@@ -1259,9 +1281,13 @@ EventTriggerEndCompleteQuery(void)
 bool
 trackDroppedObjectsNeeded(void)
 {
-	/* true if any sql_drop or table_rewrite event trigger exists */
+	/*
+	 * true if any sql_drop, table_rewrite, ddl_command_end event trigger
+	 * exists
+	 */
 	return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 ||
-		list_length(EventCacheLookup(EVT_TableRewrite)) > 0;
+		list_length(EventCacheLookup(EVT_TableRewrite)) > 0 ||
+		list_length(EventCacheLookup(EVT_DDLCommandEnd)) > 0;
 }
 
 /*
@@ -1563,3 +1589,674 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
 }
+
+/*-------------------------------------------------------------------------
+ * Support for DDL command deparsing
+ *
+ * The routines below enable an event trigger function to obtain a list of
+ * DDL commands as they are executed.  There are three main pieces to this
+ * feature:
+ *
+ * 1) Within ProcessUtilitySlow, or some sub-routine thereof, each DDL command
+ * must add an internal representation to the collected command list, using the
+ * routines below.
+ *
+ * 2) Some time after that, ddl_command_end fires and the command list is made
+ * available to the event trigger function via pg_event_trigger_ddl_commands().
+ *
+ * 3) An extension may have added a hook on CommandDeparse_hook().  That
+ * will transform the CollectedCommand into a text string representing the
+ * original command.
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Inhibit DDL command collection.
+ */
+void
+EventTriggerInhibitCommandCollection(void)
+{
+	if (!currentEventTriggerState)
+		return;
+
+	currentEventTriggerState->commandCollectionInhibited = true;
+}
+
+/*
+ * Re-establish DDL command collection.
+ */
+void
+EventTriggerUndoInhibitCommandCollection(void)
+{
+	if (!currentEventTriggerState)
+		return;
+
+	currentEventTriggerState->commandCollectionInhibited = false;
+}
+
+
+/*
+ * EventTriggerCollectSimpleCommand
+ *		Save data about a simple DDL command that was just executed
+ *
+ * address identifies the object being operated on.  secondaryObject is an
+ * object address that was related in some way to the executed command; its
+ * meaning is command-specific.
+ *
+ * For instance, for an ALTER obj SET SCHEMA command, objtype is the type of
+ * object being moved, objectId is its OID, and secondaryOid is the OID of the
+ * old schema.  (The destination schema OID can be obtained by catalog lookup
+ * of the object.)
+ */
+void
+EventTriggerCollectSimpleCommand(ObjectAddress address,
+								 ObjectAddress secondaryObject,
+								 Node *parsetree)
+{
+	MemoryContext oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+
+	command->type = SCT_Simple;
+	command->in_extension = creating_extension;
+
+	command->d.simple.address = address;
+	command->d.simple.secondaryObject = secondaryObject;
+	command->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->commandList = lappend(currentEventTriggerState->commandList,
+											  command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableStart
+ *		Prepare to receive data on an ALTER TABLE command about to be executed
+ *
+ * Note we don't collect the command immediately; instead we keep it in
+ * currentCommand, and only when we're done processing the subcommands we will
+ * add it to the command list.
+ *
+ * XXX -- this API isn't considering the possibility of an ALTER TABLE command
+ * being called reentrantly by an event trigger function.  Do we need stackable
+ * commands at this level?  Perhaps at least we should detect the condition and
+ * raise an error.
+ */
+void
+EventTriggerAlterTableStart(Node *parsetree)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+
+	command->type = SCT_AlterTable;
+	command->in_extension = creating_extension;
+
+	command->d.alterTable.classId = RelationRelationId;
+	command->d.alterTable.objectId = InvalidOid;
+	command->d.alterTable.subcmds = NIL;
+	command->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->currentCommand = command;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remember the OID of the object being affected by an ALTER TABLE.
+ *
+ * This is needed because in some cases we don't know the OID until later.
+ */
+void
+EventTriggerAlterTableRelid(Oid objectId)
+{
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	currentEventTriggerState->currentCommand->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerCollectAlterTableSubcmd
+ *		Save data about a single part of an ALTER TABLE.
+ *
+ * Several different commands go through this path, but apart from ALTER TABLE
+ * itself, they are all concerned with AlterTableCmd nodes that are generated
+ * internally, so that's all that this code needs to handle at the moment.
+ */
+void
+EventTriggerCollectAlterTableSubcmd(Node *subcmd, Oid relid,
+								  ObjectAddress address)
+{
+	MemoryContext	oldcxt;
+	CollectedATSubcmd *newsub;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId));
+
+	/*
+	 * If we receive a subcommand intended for a relation other than the one
+	 * we've started the ALTER TABLE for, ignore it.  This is chiefly concerned
+	 * with inheritance situations: in such cases, alter table would dispatch
+	 * multiple copies of the same command for child tables, but we're only
+	 * concerned with the one for the main table.
+	 */
+	if (relid != currentEventTriggerState->currentCommand->d.alterTable.objectId)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(CollectedATSubcmd));
+	newsub->address = address;
+	newsub->parsetree = copyObject(subcmd);
+
+	currentEventTriggerState->currentCommand->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableEnd
+ *		Finish up saving an ALTER TABLE command, and add it to command list
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerAlterTableEnd(void)
+{
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	/* If no subcommands, don't collect */
+	if (list_length(currentEventTriggerState->currentCommand->d.alterTable.subcmds) != 0)
+	{
+		currentEventTriggerState->commandList =
+			lappend(currentEventTriggerState->commandList,
+					currentEventTriggerState->currentCommand);
+	}
+	else
+		pfree(currentEventTriggerState->currentCommand);
+
+	currentEventTriggerState->currentCommand = NULL;
+}
+
+/*
+ * EventTriggerCollectGrant
+ *		Save data about a GRANT/REVOKE command being executed
+ *
+ * This function creates a copy of the InternalGrant, as the original might
+ * not have the right lifetime.
+ */
+void
+EventTriggerCollectGrant(InternalGrant *istmt)
+{
+	MemoryContext oldcxt;
+	CollectedCommand *command;
+	InternalGrant  *icopy;
+	ListCell	   *cell;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	/*
+	 * copying the node is moderately challenging ... 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)));
+
+	command = palloc(sizeof(CollectedCommand));
+	command->type = SCT_Grant;
+	command->in_extension = creating_extension;
+	command->d.grant.type = stringify_grantobjtype(istmt->objtype);
+	command->d.grant.istmt = icopy;
+	command->parsetree = NULL;
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterOpFam
+ *		Save data about an ALTER OPERATOR FAMILY ADD/DROP command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
+							List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+	command->type = SCT_AlterOpFamily;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.opfam.address,
+					 OperatorFamilyRelationId, opfamoid);
+	command->d.opfam.operators = operators;		/* XXX prolly need to copy */
+	command->d.opfam.procedures = procedures;	/* XXX ditto */
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectCreateOpClass
+ *		Save data about a CREATE OPERATOR CLASS command being executed
+ */
+void
+EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
+							   List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_CreateOpClass;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.createopc.address,
+					 OperatorClassRelationId, opcoid);
+	command->d.createopc.operators = operators;	/* XXX prolly need to copy */
+	command->d.createopc.procedures = procedures;	/* ditto */
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterTSConfig
+ *		Save data about an ALTER TEXT SEARCH CONFIGURATION command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
+								 Oid *dictIds, int ndicts)
+{
+	MemoryContext   oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_AlterTSConfig;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.atscfg.address,
+					 TSConfigRelationId, cfgId);
+	command->d.atscfg.dictIds = palloc(sizeof(Oid) * ndicts);
+	memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
+	command->d.atscfg.ndicts = ndicts;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterDefPrivs
+ *		Save data about an ALTER DEFAULT PRIVILEGES command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_AlterDefaultPrivileges;
+
+	switch (stmt->action->objtype)
+	{
+		case ACL_OBJECT_RELATION:
+			command->d.defprivs.objtype = "TABLES";
+			break;
+		case ACL_OBJECT_FUNCTION:
+			command->d.defprivs.objtype = "FUNCTIONS";
+			break;
+		case ACL_OBJECT_SEQUENCE:
+			command->d.defprivs.objtype = "SEQUENCES";
+			break;
+		case ACL_OBJECT_TYPE:
+			command->d.defprivs.objtype = "TYPES";
+			break;
+		default:
+			elog(ERROR, "unexpected object type %d", stmt->action->objtype);
+	}
+
+
+	command->in_extension = creating_extension;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * In a ddl_command_end event trigger, this functions reports then DDL commands
+ * that are being run.  If a CommandDeparse_hook has been installed, that's
+ * used to obtain a deparsed representation of the command.
+ */
+Datum
+pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_ddl_commands()")));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->commandList)
+	{
+		CollectedCommand *cmd = lfirst(lc);
+		char	   *command;
+		Datum		values[9];
+		bool		nulls[9];
+		ObjectAddress addr;
+		int			i = 0;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid.  Don't return anything.
+		 *
+		 * One might think that a viable alternative would be to look up the
+		 * Oid of the existing object and run the deparse with that.  But since
+		 * the parse tree might be different from the one that created the
+		 * object in the first place, we might not end up in a consistent state
+		 * anyway.
+		 */
+		if (cmd->type == SCT_Simple &&
+			!OidIsValid(cmd->d.simple.address.objectId))
+			continue;
+
+		if (CommandDeparse_hook)
+			command = CommandDeparse_hook(cmd);
+		else
+			command = "no deparse hook installed";
+
+		MemSet(nulls, 0, sizeof(nulls));
+
+		switch (cmd->type)
+		{
+			case SCT_Simple:
+			case SCT_AlterTable:
+			case SCT_AlterOpFamily:
+			case SCT_CreateOpClass:
+			case SCT_AlterTSConfig:
+				{
+					const char *tag;
+					char	   *identity;
+					char	   *type;
+					char	   *schema = NULL;
+
+					if (cmd->type == SCT_Simple)
+						addr = cmd->d.simple.address;
+					else if (cmd->type == SCT_AlterTable)
+						ObjectAddressSet(addr,
+										 cmd->d.alterTable.classId,
+										 cmd->d.alterTable.objectId);
+					else if (cmd->type == SCT_AlterOpFamily)
+						addr = cmd->d.opfam.address;
+					else if (cmd->type == SCT_CreateOpClass)
+						addr = cmd->d.createopc.address;
+					else if (cmd->type == SCT_AlterTSConfig)
+						addr = cmd->d.atscfg.address;
+
+					tag = CreateCommandTag(cmd->parsetree);
+
+					type = getObjectTypeDescription(&addr);
+					identity = getObjectIdentity(&addr);
+
+					/*
+					 * Obtain schema name, if any ("pg_temp" if a temp object).
+					 * If the object class is not in the supported list here,
+					 * we assume it's a schema-less object type, and thus
+					 * "schema" remains set to NULL.
+					 */
+					if (is_objectclass_supported(addr.classId))
+					{
+						AttrNumber	nspAttnum;
+
+						nspAttnum = get_object_attnum_namespace(addr.classId);
+						if (nspAttnum != InvalidAttrNumber)
+						{
+							Relation	catalog;
+							HeapTuple	objtup;
+							Oid			schema_oid;
+							bool		isnull;
+
+							catalog = heap_open(addr.classId, AccessShareLock);
+							objtup = get_catalog_object_by_oid(catalog,
+															   addr.objectId);
+							if (!HeapTupleIsValid(objtup))
+								elog(ERROR, "cache lookup failed for object %u/%u",
+									 addr.classId, addr.objectId);
+							schema_oid =
+								heap_getattr(objtup, nspAttnum,
+											 RelationGetDescr(catalog), &isnull);
+							if (isnull)
+								elog(ERROR,
+									 "invalid null namespace in object %u/%u/%d",
+									 addr.classId, addr.objectId, addr.objectSubId);
+							/* XXX not quite get_namespace_name_or_temp */
+							if (isAnyTempNamespace(schema_oid))
+								schema = pstrdup("pg_temp");
+							else
+								schema = get_namespace_name(schema_oid);
+
+							heap_close(catalog, AccessShareLock);
+						}
+					}
+
+					/* classid */
+					values[i++] = ObjectIdGetDatum(addr.classId);
+					/* objid */
+					values[i++] = ObjectIdGetDatum(addr.objectId);
+					/* objsubid */
+					values[i++] = Int32GetDatum(addr.objectSubId);
+					/* command tag */
+					values[i++] = CStringGetTextDatum(tag);
+					/* object_type */
+					values[i++] = CStringGetTextDatum(type);
+					/* schema */
+					if (schema == NULL)
+						nulls[i++] = true;
+					else
+						values[i++] = CStringGetTextDatum(schema);
+					/* identity */
+					values[i++] = CStringGetTextDatum(identity);
+					/* in_extension */
+					values[i++] = BoolGetDatum(cmd->in_extension);
+					/* command */
+					values[i++] = CStringGetTextDatum(command);
+				}
+				break;
+
+			case SCT_AlterDefaultPrivileges:
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree));
+				/* object_type */
+				values[i++] = CStringGetTextDatum(cmd->d.defprivs.objtype);
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+				break;
+
+			case SCT_Grant:
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.istmt->is_grant ?
+												  "GRANT" : "REVOKE");
+				/* object_type */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.type);
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+				break;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
+
+static const char *
+stringify_grantobjtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+			return "COLUMN";
+		case ACL_OBJECT_RELATION:
+			return "TABLE";
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case ACL_OBJECT_DATABASE:
+			return "DATABASE";
+		case ACL_OBJECT_DOMAIN:
+			return "DOMAIN";
+		case ACL_OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case ACL_OBJECT_FOREIGN_SERVER:
+			return "FOREIGN SERVER";
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTION";
+		case ACL_OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case ACL_OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case ACL_OBJECT_NAMESPACE:
+			return "SCHEMA";
+		case ACL_OBJECT_TABLESPACE:
+			return "TABLESPACE";
+		case ACL_OBJECT_TYPE:
+			return "TYPE";
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index c327cc0..37fd13b 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -25,6 +25,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
+#include "catalog/opfam_internal.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_namespace.h"
@@ -35,6 +36,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
@@ -47,24 +49,12 @@
 #include "utils/tqual.h"
 
 
-/*
- * We use lists of this struct type to keep track of both operators and
- * procedures while building or adding to an opfamily.
- */
-typedef struct
-{
-	Oid			object;			/* operator or support proc's OID */
-	int			number;			/* strategy or support proc number */
-	Oid			lefttype;		/* lefttype */
-	Oid			righttype;		/* righttype */
-	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
-} OpFamilyMember;
-
-
-static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+				 List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items);
-static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+				  List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				  int maxOpNumber, int maxProcNumber,
 				  List *items);
 static void processTypesSpec(List *args, Oid *lefttype, Oid *righttype);
@@ -675,6 +665,9 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	storeProcedures(stmt->opfamilyname, amoid, opfamilyoid,
 					opclassoid, procedures, false);
 
+	/* let event triggers know what happened */
+	EventTriggerCollectCreateOpClass(stmt, opclassoid, operators, procedures);
+
 	/*
 	 * Create dependencies for the opclass proper.  Note: we do not create a
 	 * dependency link to the AM, because we don't currently support DROP
@@ -822,11 +815,11 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
 	 * ADD and DROP cases need separate code from here on down.
 	 */
 	if (stmt->isDrop)
-		AlterOpFamilyDrop(stmt->opfamilyname, amoid, opfamilyoid,
+		AlterOpFamilyDrop(stmt, stmt->opfamilyname, amoid, opfamilyoid,
 						  maxOpNumber, maxProcNumber,
 						  stmt->items);
 	else
-		AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid,
+		AlterOpFamilyAdd(stmt, stmt->opfamilyname, amoid, opfamilyoid,
 						 maxOpNumber, maxProcNumber,
 						 stmt->items);
 
@@ -837,7 +830,8 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
  * ADD part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+				 List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items)
 {
@@ -962,13 +956,19 @@ AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				   InvalidOid, operators, true);
 	storeProcedures(opfamilyname, amoid, opfamilyoid,
 					InvalidOid, procedures, true);
+
+	/*
+	 * make information available to event triggers */
+	EventTriggerCollectAlterOpFam(stmt, opfamilyoid,
+								  operators, procedures);
 }
 
 /*
  * DROP part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+				  List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				  int maxOpNumber, int maxProcNumber,
 				  List *items)
 {
@@ -1035,6 +1035,9 @@ AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 	 */
 	dropOperators(opfamilyname, amoid, opfamilyoid, operators);
 	dropProcedures(opfamilyname, amoid, opfamilyoid, procedures);
+
+	EventTriggerCollectAlterOpFam(stmt, opfamilyoid,
+								  operators, procedures);
 }
 
 
@@ -1673,7 +1676,7 @@ RemoveAmProcEntryById(Oid entryOid)
 	heap_close(rel, RowExclusiveLock);
 }
 
-static char *
+char *
 get_am_name(Oid amOid)
 {
 	HeapTuple	tup;
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index c090ed2..1eb42df 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -25,6 +25,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/schemacmds.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
@@ -52,6 +53,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	Oid			saved_uid;
 	int			save_sec_context;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
 
@@ -142,6 +144,19 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	/* XXX should we clear overridePath->useTemp? */
 	PushOverrideSearchPath(overridePath);
 
+	address.classId = NamespaceRelationId;
+	address.objectId = namespaceId;
+	address.objectSubId = 0;
+
+	/*
+	 * 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.
+	 */
+	EventTriggerCollectSimpleCommand(address, InvalidObjectAddress,
+									 (Node *) stmt);
+
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
 	 * reorganize them into a sequentially executable order with no forward
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 06e4332..78d5909 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2781,6 +2781,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerAlterTableRelid(relid);
+
 	ATController(NULL, rel, cmds, recurse, lockmode);
 }
 
@@ -3668,6 +3670,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	(void) address;
 
 	/*
+	 * Report the subcommand to interested event triggers.
+	 */
+	EventTriggerCollectAlterTableSubcmd((Node *) cmd, RelationGetRelid(rel),
+										address);
+
+	/*
 	 * Bump the command counter to ensure the next subcommand in the sequence
 	 * can see the changes so far
 	 */
@@ -9702,7 +9710,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		cmds = lappend(cmds, cmd);
 
+		EventTriggerAlterTableStart((Node *) stmt);
+		/* OID is set by AlterTableInternal */
 		AlterTableInternal(lfirst_oid(l), cmds, false);
+		EventTriggerAlterTableEnd();
 	}
 
 	return new_tablespaceoid;
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 4c404e7..ff90040 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_func.h"
@@ -1442,6 +1443,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 			}
 		}
 	}
+
+	EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict);
 }
 
 /*
@@ -1509,6 +1512,8 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
 
 		i++;
 	}
+
+	EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
 }
 
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 029761e..457735e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3879,6 +3879,7 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 {
 	AlterTSConfigurationStmt *newnode = makeNode(AlterTSConfigurationStmt);
 
+	COPY_SCALAR_FIELD(kind);
 	COPY_NODE_FIELD(cfgname);
 	COPY_NODE_FIELD(tokentype);
 	COPY_NODE_FIELD(dicts);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 190e50a..d830c4c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1993,6 +1993,7 @@ static bool
 _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 							   const AlterTSConfigurationStmt *b)
 {
+	COMPARE_SCALAR_FIELD(kind);
 	COMPARE_NODE_FIELD(cfgname);
 	COMPARE_NODE_FIELD(tokentype);
 	COMPARE_NODE_FIELD(dicts);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 88ec83c..36109eb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8918,6 +8918,7 @@ AlterTSConfigurationStmt:
 			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_ADD_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = $11;
@@ -8928,6 +8929,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = $11;
@@ -8938,6 +8940,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name any_with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_REPLACE_DICT;
 					n->cfgname = $5;
 					n->tokentype = NIL;
 					n->dicts = list_make2($9,$11);
@@ -8948,6 +8951,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name any_with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = list_make2($11,$13);
@@ -8958,6 +8962,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING FOR name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_DROP_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->missing_ok = false;
@@ -8966,6 +8971,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING IF_P EXISTS FOR name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_DROP_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $11;
 					n->missing_ok = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index fd09d3a..06bfe35 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -887,7 +887,9 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	bool		commandCollected = false;
 	ObjectAddress address;
+	ObjectAddress secondaryObject = InvalidObjectAddress;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -906,6 +908,11 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * EventTriggerCollectSimpleCommand called by
+				 * CreateSchemaCommand
+				 */
+				commandCollected = true;
 				break;
 
 			case T_CreateStmt:
@@ -932,6 +939,9 @@ ProcessUtilitySlow(Node *parsetree,
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
 													 InvalidOid, NULL);
+							EventTriggerCollectSimpleCommand(address,
+															 secondaryObject,
+															 stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -964,10 +974,17 @@ ProcessUtilitySlow(Node *parsetree,
 													 InvalidOid, NULL);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
+							EventTriggerCollectSimpleCommand(address,
+															 secondaryObject,
+															 stmt);
 						}
 						else
 						{
-							/* Recurse for anything else */
+							/*
+							 * Recurse for anything else.  Note the recursive
+							 * call will stash the objects so created into our
+							 * event trigger context.
+							 */
 							ProcessUtility(stmt,
 										   queryString,
 										   PROCESS_UTILITY_SUBCOMMAND,
@@ -980,6 +997,12 @@ ProcessUtilitySlow(Node *parsetree,
 						if (lnext(l) != NULL)
 							CommandCounterIncrement();
 					}
+
+					/*
+					 * The multiple commands generated here are stashed
+					 * individually, so disable collection below.
+					 */
+					commandCollected = true;
 				}
 				break;
 
@@ -1006,6 +1029,10 @@ ProcessUtilitySlow(Node *parsetree,
 						stmts = transformAlterTableStmt(relid, atstmt,
 														queryString);
 
+						/* ... ensure we have an event trigger context ... */
+						EventTriggerAlterTableStart(parsetree);
+						EventTriggerAlterTableRelid(relid);
+
 						/* ... and do it */
 						foreach(l, stmts)
 						{
@@ -1019,25 +1046,41 @@ ProcessUtilitySlow(Node *parsetree,
 							}
 							else
 							{
-								/* Recurse for anything else */
+								/*
+								 * Recurse for anything else.  If we need to do
+								 * so, "close" the current complex-command set,
+								 * and start a new one at the bottom; this is
+								 * needed to ensure the ordering of queued
+								 * commands is consistent with the way they are
+								 * executed here.
+								 */
+								EventTriggerAlterTableEnd();
 								ProcessUtility(stmt,
 											   queryString,
 											   PROCESS_UTILITY_SUBCOMMAND,
 											   params,
 											   None_Receiver,
 											   NULL);
+								EventTriggerAlterTableStart(parsetree);
+								EventTriggerAlterTableRelid(relid);
 							}
 
 							/* Need CCI between commands */
 							if (lnext(l) != NULL)
 								CommandCounterIncrement();
 						}
+
+						/* done */
+						EventTriggerAlterTableEnd();
 					}
 					else
 						ereport(NOTICE,
 						  (errmsg("relation \"%s\" does not exist, skipping",
 								  atstmt->relation->relname)));
 				}
+
+				/* ALTER TABLE stashes commands internally */
+				commandCollected = true;
 				break;
 
 			case T_AlterDomainStmt:
@@ -1056,31 +1099,37 @@ ProcessUtilitySlow(Node *parsetree,
 							 * Recursively alter column default for table and,
 							 * if requested, for descendants
 							 */
-							AlterDomainDefault(stmt->typeName,
-											   stmt->def);
+							address =
+								AlterDomainDefault(stmt->typeName,
+												   stmt->def);
 							break;
 						case 'N':		/* ALTER DOMAIN DROP NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   false);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   false);
 							break;
 						case 'O':		/* ALTER DOMAIN SET NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   true);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   true);
 							break;
 						case 'C':		/* ADD CONSTRAINT */
-							AlterDomainAddConstraint(stmt->typeName,
-													 stmt->def,
-													 NULL);
+							address =
+								AlterDomainAddConstraint(stmt->typeName,
+														 stmt->def,
+														 &secondaryObject);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
-							AlterDomainDropConstraint(stmt->typeName,
-													  stmt->name,
-													  stmt->behavior,
-													  stmt->missing_ok);
+							address =
+								AlterDomainDropConstraint(stmt->typeName,
+														  stmt->name,
+														  stmt->behavior,
+														  stmt->missing_ok);
 							break;
 						case 'V':		/* VALIDATE CONSTRAINT */
-							AlterDomainValidateConstraint(stmt->typeName,
-														  stmt->name);
+							address =
+								AlterDomainValidateConstraint(stmt->typeName,
+															  stmt->name);
 							break;
 						default:		/* oops */
 							elog(ERROR, "unrecognized alter domain type: %d",
@@ -1100,41 +1149,46 @@ ProcessUtilitySlow(Node *parsetree,
 					switch (stmt->kind)
 					{
 						case OBJECT_AGGREGATE:
-							DefineAggregate(stmt->defnames, stmt->args,
-											stmt->oldstyle, stmt->definition,
-											queryString);
+							address =
+								DefineAggregate(stmt->defnames, stmt->args,
+												stmt->oldstyle,
+												stmt->definition, queryString);
 							break;
 						case OBJECT_OPERATOR:
 							Assert(stmt->args == NIL);
-							DefineOperator(stmt->defnames, stmt->definition);
+							address = DefineOperator(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TYPE:
 							Assert(stmt->args == NIL);
-							DefineType(stmt->defnames, stmt->definition);
+							address = DefineType(stmt->defnames,
+												  stmt->definition);
 							break;
 						case OBJECT_TSPARSER:
 							Assert(stmt->args == NIL);
-							DefineTSParser(stmt->defnames, stmt->definition);
+							address = DefineTSParser(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TSDICTIONARY:
 							Assert(stmt->args == NIL);
-							DefineTSDictionary(stmt->defnames,
-											   stmt->definition);
+							address = DefineTSDictionary(stmt->defnames,
+														  stmt->definition);
 							break;
 						case OBJECT_TSTEMPLATE:
 							Assert(stmt->args == NIL);
-							DefineTSTemplate(stmt->defnames,
-											 stmt->definition);
+							address = DefineTSTemplate(stmt->defnames,
+														stmt->definition);
 							break;
 						case OBJECT_TSCONFIGURATION:
 							Assert(stmt->args == NIL);
-							DefineTSConfiguration(stmt->defnames,
-												  stmt->definition,
-												  NULL);
+							address = DefineTSConfiguration(stmt->defnames,
+															 stmt->definition,
+															 &secondaryObject);
 							break;
 						case OBJECT_COLLATION:
 							Assert(stmt->args == NIL);
-							DefineCollation(stmt->defnames, stmt->definition);
+							address = DefineCollation(stmt->defnames,
+													   stmt->definition);
 							break;
 						default:
 							elog(ERROR, "unrecognized define stmt type: %d",
@@ -1175,204 +1229,258 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
-					DefineIndex(relid,	/* OID of heap relation */
-								stmt,
-								InvalidOid,		/* no predefined OID */
-								false,	/* is_alter_table */
-								true,	/* check_rights */
-								false,	/* skip_build */
-								false); /* quiet */
+					EventTriggerAlterTableStart(parsetree);
+					address =
+						DefineIndex(relid,	/* OID of heap relation */
+									stmt,
+									InvalidOid,		/* no predefined OID */
+									false,	/* is_alter_table */
+									true,	/* check_rights */
+									false,	/* skip_build */
+									false); /* quiet */
+					/*
+					 * Add the CREATE INDEX node itself to stash right away; if
+					 * there were any commands stashed in the ALTER TABLE code,
+					 * we need them to appear after this one.
+					 */
+					EventTriggerCollectSimpleCommand(address, secondaryObject,
+													 parsetree);
+					commandCollected = true;
+					EventTriggerAlterTableEnd();
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				address = CreateExtension((CreateExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				address = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
-											   NULL);
+				address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+														  &secondaryObject);
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				address = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
 				break;
 
 			case T_AlterFdwStmt:
-				AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				address = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				address = CreateForeignServer((CreateForeignServerStmt *) parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
-				AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				address = AlterForeignServer((AlterForeignServerStmt *) parsetree);
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				address = CreateUserMapping((CreateUserMappingStmt *) parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
-				AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				address = AlterUserMapping((AlterUserMappingStmt *) parsetree);
 				break;
 
 			case T_DropUserMappingStmt:
 				RemoveUserMapping((DropUserMappingStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_ImportForeignSchemaStmt:
 				ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+				/* commands are stashed inside ImportForeignSchema */
+				commandCollected = true;
 				break;
 
 			case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					address = DefineCompositeType(stmt->typevar,
+												  stmt->coldeflist);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				address = DefineEnum((CreateEnumStmt *) parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				address = DefineRange((CreateRangeStmt *) parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				address = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerAlterTableStart(parsetree);
+				address = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerCollectSimpleCommand(address, secondaryObject,
+												 parsetree);
+				/* stashed internally */
+				commandCollected = true;
+				EventTriggerAlterTableEnd();
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				address = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				address = AlterFunction((AlterFunctionStmt *) parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				address = DefineRule((RuleStmt *) parsetree, queryString);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				address = DefineSequence((CreateSeqStmt *) parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				address = AlterSequence((AlterSeqStmt *) parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
-								   queryString, params, completionTag);
+				/*
+				 * REFRSH CONCURRENTLY executes some DDL commands internally.
+				 * Inhibit DDL command collection here to avoid those commands
+				 * from showing up in the deparsed command queue.  The refresh
+				 * command itself is queued, which is enough.
+				 */
+				EventTriggerInhibitCommandCollection();
+				PG_TRY();
+				{
+					address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+												 queryString, params, completionTag);
+				}
+				PG_CATCH();
+				{
+					EventTriggerUndoInhibitCommandCollection();
+					PG_RE_THROW();
+				}
+				PG_END_TRY();
+				EventTriggerUndoInhibitCommandCollection();
 				break;
 
 			case T_CreateTrigStmt:
-				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
-									 InvalidOid, InvalidOid, InvalidOid,
-									 InvalidOid, false);
+				address = CreateTrigger((CreateTrigStmt *) parsetree,
+										 queryString, InvalidOid, InvalidOid,
+										 InvalidOid, InvalidOid, false);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				address = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				address = DefineDomain((CreateDomainStmt *) parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				address = CreateConversionCommand((CreateConversionStmt *) parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				address = CreateCast((CreateCastStmt *) parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				address = DefineOpClass((CreateOpClassStmt *) parsetree);
+				/* command is stashed in DefineOpClass */
+				commandCollected = true;
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				address = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
 				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				/* commands are stashed in AlterOpFamily */
+				commandCollected = true;
 				break;
 
 			case T_AlterTSDictionaryStmt:
-				AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				address = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
 				break;
 
 			case T_AlterTableMoveAllStmt:
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
+				/* commands are stashed in AlterTableMoveAll */
+				commandCollected = true;
 				break;
 
 			case T_DropStmt:
 				ExecDropStmt((DropStmt *) parsetree, isTopLevel);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree);
+				address = ExecRenameStmt((RenameStmt *) parsetree);
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
-										  NULL);
+				address =
+					ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
+											  &secondaryObject);
 				break;
 
 			case T_AlterOwnerStmt:
-				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
 			case T_CommentStmt:
-				CommentObject((CommentStmt *) parsetree);
+				address = CommentObject((CommentStmt *) parsetree);
 				break;
 
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
+				/* commands are stashed in ExecGrantStmt_oids */
+				commandCollected = true;
 				break;
 
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_AlterDefaultPrivilegesStmt:
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
+				EventTriggerCollectAlterDefPrivs((AlterDefaultPrivilegesStmt *) parsetree);
+				commandCollected = true;
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
-				CreatePolicy((CreatePolicyStmt *) parsetree);
+				address = CreatePolicy((CreatePolicyStmt *) parsetree);
 				break;
 
 			case T_AlterPolicyStmt:		/* ALTER POLICY */
-				AlterPolicy((AlterPolicyStmt *) parsetree);
+				address = AlterPolicy((AlterPolicyStmt *) parsetree);
 				break;
 
 			case T_SecLabelStmt:
-				ExecSecLabelStmt((SecLabelStmt *) parsetree);
+				address = ExecSecLabelStmt((SecLabelStmt *) parsetree);
 				break;
 
 			default:
@@ -1381,6 +1489,14 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 		}
 
+		/*
+		 * Remember the object so that ddl_command_end event triggers have
+		 * access to it.
+		 */
+		if (!commandCollected)
+			EventTriggerCollectSimpleCommand(address, secondaryObject,
+											 parsetree);
+
 		if (isCompleteQuery)
 		{
 			EventTriggerSQLDrop(parsetree);
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 2f0f0a1..9b674ce 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
 	return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
@@ -323,7 +326,6 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
-
 /*
  * Add typmod decoration to the basic type name
  */
@@ -338,7 +340,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,7 +352,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/include/catalog/opfam_internal.h b/src/include/catalog/opfam_internal.h
new file mode 100644
index 0000000..f01dcbe
--- /dev/null
+++ b/src/include/catalog/opfam_internal.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * opfam_internal.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/opfam_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OPFAM_INTERNAL_H
+#define OPFAM_INTERNAL_H
+
+/*
+ * We use lists of this struct type to keep track of both operators and
+ * procedures while building or adding to an opfamily.
+ */
+typedef struct
+{
+	Oid			object;			/* operator or support proc's OID */
+	int			number;			/* strategy or support proc number */
+	Oid			lefttype;		/* lefttype */
+	Oid			righttype;		/* righttype */
+	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
+} OpFamilyMember;
+
+#endif		/* OPFAM_INTERNAL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 62187fb..ea17084 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5126,6 +5126,8 @@ DATA(insert OID = 4566 (  pg_event_trigger_table_rewrite_oid	PGNSP PGUID 12 1 0
 DESCR("return Oid of the table getting rewritten");
 DATA(insert OID = 4567 (  pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ ));
 DESCR("return reason code for table getting rewritten");
+DATA(insert OID = 3590 (  pg_event_trigger_ddl_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,114}" "{o,o,o,o,o,o,o,o,o}" "{classid,objid,objsubid,command_tag,object_type,schema,identity,in_extension,command}" _null_ pg_event_trigger_ddl_commands _null_ _null_ _null_ ));
+DESCR("list DDL actions being executed by the current command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 595f93f..89a7e49 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -87,6 +87,7 @@ extern void IsThereOpClassInNamespace(const char *opcname, Oid opcmethod,
 extern void IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod,
 						   Oid opfnamespace);
 extern Oid	get_am_oid(const char *amname, bool missing_ok);
+extern char *get_am_name(Oid amOid);
 extern Oid	get_opclass_oid(Oid amID, List *opclassname, bool missing_ok);
 extern Oid	get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok);
 
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 7eb2156..5c8ed57 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -17,6 +17,8 @@
 #include "catalog/objectaddress.h"
 #include "catalog/pg_event_trigger.h"
 #include "nodes/parsenodes.h"
+#include "utils/aclchk_internal.h"
+#include "tcop/deparse_utility.h"
 
 typedef struct EventTriggerData
 {
@@ -38,6 +40,9 @@ typedef struct EventTriggerData
 #define CALLED_AS_EVENT_TRIGGER(fcinfo) \
 	((fcinfo)->context != NULL && IsA((fcinfo)->context, EventTriggerData))
 
+typedef char * (*CommandDeparse_hook_type) (CollectedCommand *command);
+extern PGDLLIMPORT CommandDeparse_hook_type CommandDeparse_hook;
+
 extern Oid	CreateEventTrigger(CreateEventTrigStmt *stmt);
 extern void RemoveEventTriggerById(Oid ctrigOid);
 extern Oid	get_event_trigger_oid(const char *trigname, bool missing_ok);
@@ -60,4 +65,28 @@ extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 							 bool original, bool normal);
 
+extern void EventTriggerInhibitCommandCollection(void);
+extern void EventTriggerUndoInhibitCommandCollection(void);
+
+extern void EventTriggerCollectSimpleCommand(ObjectAddress address,
+								 ObjectAddress secondaryObject,
+								 Node *parsetree);
+
+extern void EventTriggerAlterTableStart(Node *parsetree);
+extern void EventTriggerAlterTableRelid(Oid objectId);
+extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd, Oid relid,
+								  ObjectAddress address);
+extern void EventTriggerAlterTableEnd(void);
+
+extern void EventTriggerCollectGrant(InternalGrant *istmt);
+extern void EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt,
+							  Oid opfamoid, List *operators,
+							  List *procedures);
+extern void EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt,
+								 Oid opcoid, List *operators,
+								 List *procedures);
+extern void EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt,
+								 Oid cfgId, Oid *dictIds, int ndicts);
+extern void EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 40ecea2..0423350 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -24,7 +24,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0e257ac..51a9c5b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2850,9 +2850,19 @@ typedef struct AlterTSDictionaryStmt
 /*
  * TS Configuration stmts: DefineStmt, RenameStmt and DropStmt are default
  */
+typedef enum AlterTSConfigType
+{
+	ALTER_TSCONFIG_ADD_MAPPING,
+	ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN,
+	ALTER_TSCONFIG_REPLACE_DICT,
+	ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN,
+	ALTER_TSCONFIG_DROP_MAPPING
+} AlterTSConfigType;
+
 typedef struct AlterTSConfigurationStmt
 {
 	NodeTag		type;
+	AlterTSConfigType	kind;	/* ALTER_TSCONFIG_ADD_MAPPING, etc */
 	List	   *cfgname;		/* qualified name (list of Value strings) */
 
 	/*
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..1172e57
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,106 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+#include "access/attnum.h"
+#include "catalog/objectaddress.h"
+#include "nodes/nodes.h"
+#include "utils/aclchk_internal.h"
+
+
+/*
+ * Support for keeping track of collected commands.
+ */
+typedef enum CollectedCommandType
+{
+	SCT_Simple,
+	SCT_AlterTable,
+	SCT_Grant,
+	SCT_AlterOpFamily,
+	SCT_AlterDefaultPrivileges,
+	SCT_CreateOpClass,
+	SCT_AlterTSConfig
+} CollectedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct CollectedATSubcmd
+{
+	ObjectAddress	address; /* affected column, constraint, index, ... */
+	Node		   *parsetree;
+} CollectedATSubcmd;
+
+typedef struct CollectedCommand
+{
+	CollectedCommandType type;
+	bool		in_extension;
+	Node	   *parsetree;
+
+	union
+	{
+		/* most commands */
+		struct
+		{
+			ObjectAddress address;
+			ObjectAddress secondaryObject;
+		} simple;
+
+		/* ALTER TABLE, and internal uses thereof */
+		struct
+		{
+			Oid		objectId;
+			Oid		classId;
+			List   *subcmds;
+		} alterTable;
+
+		/* GRANT / REVOKE */
+		struct
+		{
+			InternalGrant *istmt;
+			const char *type;
+		} grant;
+
+		/* ALTER OPERATOR FAMILY */
+		struct
+		{
+			ObjectAddress address;
+			List   *operators;
+			List   *procedures;
+		} opfam;
+
+		/* CREATE OPERATOR CLASS */
+		struct
+		{
+			ObjectAddress address;
+			List   *operators;
+			List   *procedures;
+		} createopc;
+
+		/* ALTER TEXT SEARCH CONFIGURATION ADD/ALTER/DROP MAPPING */
+		struct
+		{
+			ObjectAddress address;
+			Oid	   *dictIds;
+			int		ndicts;
+		} atscfg;
+
+		/* ALTER DEFAULT PRIVILEGES */
+		struct
+		{
+			char   *objtype;
+		} defprivs;
+	} d;
+} CollectedCommand;
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/aclchk_internal.h b/src/include/utils/aclchk_internal.h
new file mode 100644
index 0000000..bac2728
--- /dev/null
+++ b/src/include/utils/aclchk_internal.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * aclchk_internal.h
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/aclchk_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLCHK_INTERNAL_H
+#define ACLCHK_INTERNAL_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+/*
+ * The information about one Grant/Revoke statement, in internal format: object
+ * and grantees names have been turned into Oids, the privilege list is an
+ * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
+ * all_privs is true, 'privileges' will be internally set to the right kind of
+ * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
+ * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
+ */
+typedef struct
+{
+	bool		is_grant;
+	GrantObjectType objtype;
+	List	   *objects;
+	bool		all_privs;
+	AclMode		privileges;
+	List	   *col_privs;
+	List	   *grantees;
+	bool		grant_option;
+	DropBehavior behavior;
+} InternalGrant;
+
+
+#endif	/* ACLCHK_INTERNAL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 33a453f..dc884ad 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1218,6 +1218,7 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
-- 
2.1.4

0002-changes-to-core-to-support-the-deparse-extension.patchtext/x-diff; charset=us-asciiDownload
>From 737f4bf82fc54cadfd05a44e8bc724ef92d907cc Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 7 Apr 2015 15:01:06 -0300
Subject: [PATCH 2/3] changes to core to support the deparse extension

---
 src/backend/commands/seclabel.c     |   5 +
 src/backend/commands/sequence.c     |  34 +++
 src/backend/commands/tablecmds.c    |   3 +-
 src/backend/utils/adt/format_type.c | 127 +++++++++
 src/backend/utils/adt/regproc.c     | 101 +++++--
 src/backend/utils/adt/ruleutils.c   | 514 ++++++++++++++++++++++++++++++++----
 src/include/commands/sequence.h     |   1 +
 src/include/utils/builtins.h        |   4 +
 src/include/utils/ruleutils.h       |  21 +-
 9 files changed, 729 insertions(+), 81 deletions(-)

diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 1ef98ce..aa4de97 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -63,6 +63,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("must specify provider when multiple security label providers have been loaded")));
 		provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
 	}
 	else
 	{
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6d316d6..73cf4da 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1503,6 +1503,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/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 78d5909..49f6a9e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8131,7 +8131,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/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 9b674ce..7821f25 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -327,6 +327,133 @@ format_type_internal(Oid type_oid, int32 typemod,
 }
 
 /*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name in this case.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *typarray)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID ||
+		type_oid == TIMEOID ||
+		type_oid == TIMETZOID)
+	{
+		*typarray = false;
+
+peculiar_typmod:
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPTZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmod, so fall through */
+			case TIMESTAMPOID:
+				*typname = pstrdup("TIMESTAMP");
+				break;
+			case TIMETZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIME WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmode, so fall through */
+			case TIMEOID:
+				*typname = pstrdup("TIME");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		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;
+		*typarray = true;
+
+		/*
+		 * If it's an array of one of the types with special typmod rules,
+		 * have the element type be processed as above, but now with typarray
+		 * set to true.
+		 */
+		if (type_oid == INTERVALOID ||
+			type_oid == TIMESTAMPTZOID ||
+			type_oid == TIMESTAMPOID ||
+			type_oid == TIMETZOID ||
+			type_oid == TIMEOID)
+			goto peculiar_typmod;
+	}
+	else
+		*typarray = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
+
+/*
  * Add typmod decoration to the basic type name
  */
 static char *
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 11d663b..d70a5ce 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -42,7 +42,11 @@
 #include "utils/tqual.h"
 
 static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
+static char *format_procedure_internal(Oid procedure_oid, bool force_qualify,
+						  bool args_only);
+static void format_procedure_args_internal(Form_pg_proc procform,
+							   StringInfo buf, bool force_qualify);
+
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -363,13 +367,36 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_internal(procedure_oid, false, false);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_internal(procedure_oid, true, false);
+}
+
+/*
+ * format_procedure_args	- converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+	StringInfoData buf;
+	HeapTuple	proctup;
+	Form_pg_proc procform;
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+	initStringInfo(&buf);
+	format_procedure_args_internal(procform, &buf, force_qualify);
+
+	ReleaseSysCache(proctup);
+
+	return buf.data;
 }
 
 /*
@@ -380,7 +407,7 @@ format_procedure_qualified(Oid procedure_oid)
  * qualified if the function is not in path.
  */
 static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+format_procedure_internal(Oid procedure_oid, bool force_qualify, bool args_only)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -391,8 +418,6 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 	{
 		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
 		char	   *proname = NameStr(procform->proname);
-		int			nargs = procform->pronargs;
-		int			i;
 		char	   *nspname;
 		StringInfoData buf;
 
@@ -400,29 +425,24 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		initStringInfo(&buf);
 
-		/*
-		 * Would this proc be found (given the right args) by regprocedurein?
-		 * If not, or if caller requests it, we need to qualify it.
-		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
-			nspname = NULL;
-		else
-			nspname = get_namespace_name(procform->pronamespace);
-
-		appendStringInfo(&buf, "%s(",
-						 quote_qualified_identifier(nspname, proname));
-		for (i = 0; i < nargs; i++)
+		if (!args_only)
 		{
-			Oid			thisargtype = procform->proargtypes.values[i];
-
-			if (i > 0)
-				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf,
-								   force_qualify ?
-								   format_type_be_qualified(thisargtype) :
-								   format_type_be(thisargtype));
+			/*
+			 * Would this proc be found (given the right args) by
+			 * regprocedurein?  If not, or if caller requests it, we need to
+			 * qualify it.
+			 */
+			if (!force_qualify && FunctionIsVisible(procedure_oid))
+				nspname = NULL;
+			else
+				nspname = get_namespace_name(procform->pronamespace);
+
+			appendStringInfo(&buf, "%s",
+							 quote_qualified_identifier(nspname, proname));
 		}
-		appendStringInfoChar(&buf, ')');
+
+		/* add the attributes */
+		format_procedure_args_internal(procform, &buf, force_qualify);
 
 		result = buf.data;
 
@@ -439,6 +459,33 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 }
 
 /*
+ * Append the parenthised arguments of the given pg_proc row into the output
+ * buffer.  force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+							   bool force_qualify)
+{
+	int			i;
+	int			nargs = procform->pronargs;
+
+	appendStringInfoChar(buf, '(');
+	for (i = 0; i < nargs; i++)
+	{
+		Oid			thisargtype = procform->proargtypes.values[i];
+
+		if (i > 0)
+			appendStringInfoChar(buf, ',');
+		appendStringInfoString(buf,
+							   force_qualify ?
+							   format_type_be_qualified(thisargtype) :
+							   format_type_be(thisargtype));
+	}
+	appendStringInfoChar(buf, ')');
+}
+
+/*
  * Output a objname/objargs representation for the procedure with the
  * given OID.  If it doesn't exist, an error is thrown.
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 29b5b1b..4ed7f60 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -449,6 +449,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)
@@ -599,6 +670,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
  */
@@ -673,6 +751,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def	- Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * get_triggerdef			- Get the definition of a trigger
  * ----------
@@ -822,59 +915,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char	   *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.ctes = NIL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
@@ -915,6 +961,63 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char		relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+	initStringInfo(&buf);
+
+	relkind = get_rel_relkind(trigrec->tgrelid);
+
+	/* Build minimal OLD and NEW RTEs for the rel */
+	oldrte = makeNode(RangeTblEntry);
+	oldrte->rtekind = RTE_RELATION;
+	oldrte->relid = trigrec->tgrelid;
+	oldrte->relkind = relkind;
+	oldrte->alias = makeAlias("old", NIL);
+	oldrte->eref = oldrte->alias;
+	oldrte->lateral = false;
+	oldrte->inh = false;
+	oldrte->inFromCl = true;
+
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_RELATION;
+	newrte->relid = trigrec->tgrelid;
+	newrte->relkind = relkind;
+	newrte->alias = makeAlias("new", NIL);
+	newrte->eref = newrte->alias;
+	newrte->lateral = false;
+	newrte->inh = false;
+	newrte->inFromCl = true;
+
+	/* Build two-element rtable */
+	memset(&dpns, 0, sizeof(dpns));
+	dpns.rtable = list_make2(oldrte, newrte);
+	dpns.ctes = NIL;
+	set_rtable_names(&dpns, NIL, NULL);
+	set_simple_column_names(&dpns);
+
+	/* Set up context with one-deep namespace stack */
+	context.buf = &buf;
+	context.namespaces = list_make1(&dpns);
+	context.windowClause = NIL;
+	context.windowTList = NIL;
+	context.varprefix = true;
+	context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+	context.wrapColumn = WRAP_COLUMN_DEFAULT;
+	context.indentLevel = PRETTYINDENT_STD;
+
+	get_rule_expr(whenClause, &context, false);
+
+	return buf.data;
+}
+
 /* ----------
  * get_indexdef			- Get the definition of an index
  *
@@ -978,6 +1081,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,
@@ -1257,6 +1362,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
@@ -1291,9 +1635,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);
 }
 
 /*
@@ -9401,3 +9745,69 @@ 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;
+}
+
+/*
+ * Return the default value of a domain.
+ */
+char *
+DomainGetDefault(HeapTuple domTup)
+{
+	Datum	def;
+	Node   *defval;
+	char   *defstr;
+	bool	isnull;
+
+	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+							 &isnull);
+	if (isnull)
+		elog(ERROR, "domain \"%s\" does not have a default value",
+			 NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+	defval = stringToNode(TextDatumGetCString(def));
+	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
+									   false, false, 0, 0);
+
+	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/commands/sequence.h b/src/include/commands/sequence.h
index 44862bb..8d8e556 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -72,6 +72,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 ObjectAddress DefineSequence(CreateSeqStmt *stmt);
 extern ObjectAddress AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index dc884ad..5f18f36 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -644,6 +644,7 @@ extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
 					  List **objargs);
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
@@ -1087,6 +1088,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);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index fed9c7b..2c020e9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,6 +13,7 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
@@ -20,8 +21,20 @@
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+						  Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+					   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
 
-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);
@@ -31,5 +44,11 @@ extern List *set_deparse_context_planstate(List *dpcontext,
 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);
+
+extern char *DomainGetDefault(HeapTuple domTup);
 
 #endif	/* RULEUTILS_H */
-- 
2.1.4

0003-ddl_deparse-extension.patchtext/x-diff; charset=us-asciiDownload
>From 1de5c88f364d4ef91c83eda0e27b8e8dc671870b Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 7 Apr 2015 15:42:07 -0300
Subject: [PATCH 3/3] ddl_deparse extension

---
 contrib/ddl_deparse/Makefile             |   20 +
 contrib/ddl_deparse/ddl_deparse--1.0.sql |    9 +
 contrib/ddl_deparse/ddl_deparse.c        | 7168 ++++++++++++++++++++++++++++++
 contrib/ddl_deparse/ddl_deparse.control  |    5 +
 contrib/ddl_deparse/ddl_deparse.h        |    8 +
 contrib/ddl_deparse/ddl_json.c           |  715 +++
 contrib/ddl_deparse/hook.c               |   32 +
 7 files changed, 7957 insertions(+)
 create mode 100644 contrib/ddl_deparse/Makefile
 create mode 100644 contrib/ddl_deparse/ddl_deparse--1.0.sql
 create mode 100644 contrib/ddl_deparse/ddl_deparse.c
 create mode 100644 contrib/ddl_deparse/ddl_deparse.control
 create mode 100644 contrib/ddl_deparse/ddl_deparse.h
 create mode 100644 contrib/ddl_deparse/ddl_json.c
 create mode 100644 contrib/ddl_deparse/hook.c

diff --git a/contrib/ddl_deparse/Makefile b/contrib/ddl_deparse/Makefile
new file mode 100644
index 0000000..b408779
--- /dev/null
+++ b/contrib/ddl_deparse/Makefile
@@ -0,0 +1,20 @@
+# contrib/ddl_deparse/Makefile
+
+MODULE_big = ddl_deparse
+OBJS = ddl_deparse.o ddl_json.o hook.o
+
+EXTENSION = ddl_deparse
+DATA = ddl_deparse--1.0.sql
+
+PGFILEDESC = "ddl_deparse - deparsing support for DDL"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/ddl_deparse
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/ddl_deparse/ddl_deparse--1.0.sql b/contrib/ddl_deparse/ddl_deparse--1.0.sql
new file mode 100644
index 0000000..4c8bac9
--- /dev/null
+++ b/contrib/ddl_deparse/ddl_deparse--1.0.sql
@@ -0,0 +1,9 @@
+/* contrib/ddl_deparse/ddl_deparse--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION ddl_deparse" to load this file. \quit
+
+CREATE FUNCTION ddl_deparse_expand_command(json)
+RETURNS text
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
diff --git a/contrib/ddl_deparse/ddl_deparse.c b/contrib/ddl_deparse/ddl_deparse.c
new file mode 100644
index 0000000..ddbd1f3
--- /dev/null
+++ b/contrib/ddl_deparse/ddl_deparse.c
@@ -0,0 +1,7168 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_deparse.c
+ *	  Functions to convert utility commands to machine-parseable
+ *	  representation
+ *
+ * Portions Copyright (c) 1996-2015, 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
+ *	  contrib/ddl_deparse/ddl_deparse.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "ddl_deparse.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/heap.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "catalog/opfam_internal.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_cast.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_opclass.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_policy.h"
+#include "catalog/pg_proc.h"
+#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"
+#include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "foreign/foreign.h"
+#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"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/jsonb.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Before they are turned into JSONB representation, each command is
+ * represented as an object tree, using the structs below.
+ */
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeInteger,
+	ObjTypeFloat,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjTree
+{
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+
+	union
+	{
+		bool		boolean;
+		char	   *string;
+		int64		integer;
+		float8		flt;
+		ObjTree	   *object;
+		List	   *array;
+	} value;
+	slist_node	node;
+} ObjElem;
+
+static ObjElem *new_null_object(void);
+static ObjElem *new_bool_object(bool value);
+static ObjElem *new_string_object(char *value);
+static ObjElem *new_object_object(ObjTree *value);
+static ObjElem *new_array_object(List *array);
+static ObjElem *new_integer_object(int64 value);
+static ObjElem *new_float_object(float8 value);
+static void append_null_object(ObjTree *tree, char *name);
+static void append_bool_object(ObjTree *tree, char *name, bool value);
+static void append_string_object(ObjTree *tree, char *name, char *value);
+static void append_object_object(ObjTree *tree, char *name, ObjTree *value);
+static void append_array_object(ObjTree *tree, char *name, List *array);
+static void append_integer_object(ObjTree *tree, char *name, int64 value);
+static void append_float_object(ObjTree *tree, char *name, float8 value);
+static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
+static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+static const char *stringify_objtype(ObjectType objtype);
+
+/*
+ * Allocate a new object tree to store parameter values.
+ */
+static ObjTree *
+new_objtree(void)
+{
+	ObjTree    *params;
+
+	params = palloc(sizeof(ObjTree));
+	params->numParams = 0;
+	slist_init(&params->params);
+
+	return params;
+}
+
+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one, a
+ * name (string), type (from the ObjType enum) and value must be supplied.  The
+ * value must match the type given; for instance, ObjTypeInteger requires an
+ * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of
+ * ObjElem), ObjTypeObject requires an ObjTree, and so on.  Each element type *
+ * must match the conversion specifier given in the format string, as described
+ * in ddl_deparse_expand_command, q.v.
+ *
+ * Note we don't have the luxury of sprintf-like compiler warnings for
+ * malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree();
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		char	   *name;
+		ObjType		type;
+		ObjElem	   *elem;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/*
+		 * 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:
+				elem = new_bool_object(va_arg(args, int));
+				break;
+			case ObjTypeString:
+				elem = new_string_object(va_arg(args, char *));
+				break;
+			case ObjTypeObject:
+				elem = new_object_object(va_arg(args, ObjTree *));
+				break;
+			case ObjTypeArray:
+				elem = new_array_object(va_arg(args, List *));
+				break;
+			case ObjTypeInteger:
+				elem = new_integer_object(va_arg(args, int64));
+				break;
+			case ObjTypeFloat:
+				elem = new_float_object(va_arg(args, double));
+				break;
+			case ObjTypeNull:
+				/* Null params don't have a value (obviously) */
+				elem = new_null_object();
+				break;
+			default:
+				elog(ERROR, "invalid ObjTree element type %d", type);
+		}
+
+		elem->name = name;
+		append_premade_object(tree, elem);
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/* Allocate a new parameter with a NULL value */
+static ObjElem *
+new_null_object(void)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = NULL;
+	param->objtype = ObjTypeNull;
+
+	return param;
+}
+
+/* Append a NULL object to a tree */
+static void
+append_null_object(ObjTree *tree, char *name)
+{
+	ObjElem    *param;
+
+	param = new_null_object();
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new boolean parameter */
+static ObjElem *
+new_bool_object(bool value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeBool;
+	param->value.boolean = value;
+
+	return param;
+}
+
+/* Append a boolean parameter to a tree */
+static void
+append_bool_object(ObjTree *tree, char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = new_bool_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new string object */
+static ObjElem *
+new_string_object(char *value)
+{
+	ObjElem    *param;
+
+	Assert(value);
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeString;
+	param->value.string = value;
+
+	return param;
+}
+
+/*
+ * Append a string parameter to a tree.
+ */
+static void
+append_string_object(ObjTree *tree, char *name, char *value)
+{
+	ObjElem	   *param;
+
+	Assert(name);
+	param = new_string_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+static ObjElem *
+new_integer_object(int64 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeInteger;
+	param->value.integer = value;
+
+	return param;
+}
+
+static ObjElem *
+new_float_object(float8 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeFloat;
+	param->value.flt = value;
+
+	return param;
+}
+
+/*
+ * Append an int64 parameter to a tree.
+ */
+static void
+append_integer_object(ObjTree *tree, char *name, int64 value)
+{
+	ObjElem	   *param;
+
+	param = new_integer_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/*
+ * Append a float8 parameter to a tree.
+ */
+static void
+append_float_object(ObjTree *tree, char *name, float8 value)
+{
+	ObjElem	   *param;
+
+	param = new_float_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new object parameter */
+static ObjElem *
+new_object_object(ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeObject;
+	param->value.object = value;
+
+	return param;
+}
+
+/* Append an object parameter to a tree */
+static void
+append_object_object(ObjTree *tree, char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	Assert(name);
+	param = new_object_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new array parameter */
+static ObjElem *
+new_array_object(List *array)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeArray;
+	param->value.array = array;
+
+	return param;
+}
+
+/* Append an array parameter to a tree */
+static void
+append_array_object(ObjTree *tree, char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = new_array_object(array);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Append a preallocated parameter to a tree */
+static inline void
+append_premade_object(ObjTree *tree, ObjElem *elem)
+{
+	slist_push_head(&tree->params, &elem->node);
+	tree->numParams++;
+}
+
+/*
+ * Helper for objtree_to_jsonb: process an individual element from an object or
+ * an array into the output parse state.
+ */
+static void
+objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object,
+						 JsonbIteratorToken elem_token)
+{
+	ListCell   *cell;
+	JsonbValue	val;
+
+	switch (object->objtype)
+	{
+		case ObjTypeNull:
+			val.type = jbvNull;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeString:
+			val.type = jbvString;
+			val.val.string.len = strlen(object->value.string);
+			val.val.string.val = object->value.string;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeInteger:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeFloat:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(float8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeBool:
+			val.type = jbvBool;
+			val.val.boolean = object->value.boolean;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeObject:
+			/* recursively add the object into the existing parse state */
+			objtree_to_jsonb_rec(object->value.object, state);
+			break;
+
+		case ObjTypeArray:
+			pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+			foreach(cell, object->value.array)
+			{
+				ObjElem   *elem = lfirst(cell);
+
+				objtree_to_jsonb_element(state, elem, WJB_ELEM);
+			}
+			pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+			break;
+
+		default:
+			elog(ERROR, "unrecognized object type %d", object->objtype);
+			break;
+	}
+}
+
+/*
+ * Recursive helper for objtree_to_jsonb
+ */
+static JsonbValue *
+objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state)
+{
+	slist_iter	iter;
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	slist_foreach(iter, &tree->params)
+	{
+		ObjElem    *object = slist_container(ObjElem, node, iter.cur);
+		JsonbValue	key;
+
+		/* Push the key first */
+		key.type = jbvString;
+		key.val.string.len = strlen(object->name);
+		key.val.string.val = object->name;
+		pushJsonbValue(&state, WJB_KEY, &key);
+
+		/* Then process the value according to its type */
+		objtree_to_jsonb_element(state, object, WJB_VALUE);
+	}
+
+	return pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Create a JSONB representation from an ObjTree.
+ */
+static Jsonb *
+objtree_to_jsonb(ObjTree *tree)
+{
+	JsonbValue *value;
+
+	value = objtree_to_jsonb_rec(tree, NULL);
+	return JsonbValueToJsonb(value);
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{
+	ObjTree    *typeParam;
+	Oid			typnspid;
+	char	   *typnsp;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		typarray;
+
+	format_type_detailed(typeId, typmod,
+						 &typnspid, &typename, &typmodstr, &typarray);
+
+	if (!OidIsValid(typnspid))
+		typnsp = pstrdup("");
+	else if (isAnyTempNamespace(typnspid))
+		typnsp = pstrdup("pg_temp");
+	else
+		typnsp = get_namespace_name(typnspid);
+
+	/* We don't use new_objtree_VA here because types don't have a "fmt" */
+	typeParam = new_objtree();
+	append_string_object(typeParam, "schemaname", typnsp);
+	append_string_object(typeParam, "typename", typename);
+	append_string_object(typeParam, "typmod", typmodstr);
+	append_bool_object(typeParam, "typarray", typarray);
+
+	return typeParam;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements
+ *
+ * Elements "schemaname" and "objname" are set.  If the namespace OID
+ * corresponds to a temp schema, that's set to "pg_temp".
+ *
+ * The difference between those two element types is whether the objname will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static ObjTree *
+new_objtree_for_qualname(Oid nspid, char *name)
+{
+	ObjTree    *qualified;
+	char	   *namespace;
+
+	/*
+	 * We don't use new_objtree_VA here because these names don't have a "fmt"
+	 */
+	qualified = new_objtree();
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+	append_string_object(qualified, "schemaname", namespace);
+	append_string_object(qualified, "objname", pstrdup(name));
+
+	return qualified;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements, with the object specified
+ * by classId/objId
+ *
+ * Elements "schemaname" and "objname" are set.  If the object is a temporary
+ * object, the schema name is set to "pg_temp".
+ */
+static ObjTree *
+new_objtree_for_qualname_id(Oid classId, Oid objectId)
+{
+	ObjTree    *qualified;
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		objnsp;
+	Datum		objname;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	bool		isnull;
+
+	catalog = heap_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL namespace");
+	objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL name");
+
+	qualified = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+										 NameStr(*DatumGetName(objname)));
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+/*
+ * Helper routine for %{}R objects, with role specified by OID.  (ACL_ID_PUBLIC
+ * means to use "public").
+ */
+static ObjTree *
+new_objtree_for_role_id(Oid roleoid)
+{
+	ObjTree    *role;
+
+	role = new_objtree();
+	append_bool_object(role, "is_public", roleoid == ACL_ID_PUBLIC);
+
+	if (roleoid != ACL_ID_PUBLIC)
+	{
+		HeapTuple	roltup;
+		char	   *rolename;
+
+		roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleoid));
+		if (!HeapTupleIsValid(roltup))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("role with OID %u does not exist", roleoid)));
+
+		rolename = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname);
+		append_string_object(role, "rolename", pstrdup(rolename));
+		ReleaseSysCache(roltup);
+	}
+
+	return role;
+}
+
+/*
+ * Helper routine for %{}R objects, with role specified by name.
+ */
+static ObjTree *
+new_objtree_for_role(char *rolename)
+{
+	ObjTree	   *role;
+	bool		is_public;
+
+	role = new_objtree();
+	is_public = strcmp(rolename, "public") == 0;
+	append_bool_object(role, "is_public", is_public);
+	if (!is_public)
+		append_string_object(role, "rolename", rolename);
+
+	return role;
+}
+
+/*
+ * Helper routine for %{}R objects, with role specified by RoleSpec node.
+ * Special values such as ROLESPEC_CURRENT_USER are expanded to their final
+ * names.
+ */
+static ObjTree *
+new_objtree_for_rolespec(RoleSpec *spec)
+{
+	ObjTree	   *role;
+
+	role = new_objtree();
+	append_bool_object(role, "is_public",
+					   spec->roletype == ROLESPEC_PUBLIC);
+	if (spec->roletype != ROLESPEC_PUBLIC)
+		append_string_object(role, "rolename", get_rolespec_name((Node *) spec));
+
+	return role;
+}
+
+/*
+ * 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:
+			elog(ERROR, "unexpected persistence marking %c", persistence);
+			return "";		/* make compiler happy */
+	}
+}
+
+/*
+ * Form an ObjElem to be used as a single argument in an aggregate argument
+ * list
+ */
+static ObjElem *
+form_agg_argument(int idx, char *modes, char **names, Oid *types)
+{
+	ObjTree	   *arg;
+
+	arg = new_objtree_VA("%{mode}s %{name}s %{type}T", 0);
+
+	append_string_object(arg, "mode",
+						 (modes && modes[idx] == 'v') ? "VARIADIC" : "");
+	append_string_object(arg, "name", names ? names[idx] : "");
+	append_object_object(arg, "type",
+						 new_objtree_for_type(types[idx], -1));
+
+	return new_object_object(arg);
+}
+
+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;
+	Form_pg_operator op;
+	HeapTuple	tup;
+
+	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)));
+
+	/*
+	 * Add the argument list.  There are three cases to consider:
+	 *
+	 * 1. no arguments, in which case the signature is (*).
+	 *
+	 * 2. Not an ordered-set aggregate.  In this case there's one or more
+	 * arguments.
+	 *
+	 * 3. Ordered-set aggregates. These have zero or more direct arguments, and
+	 * one or more ordered arguments.
+	 *
+	 * We don't need to consider default values or table parameters, and the
+	 * only mode that we need to consider is VARIADIC.
+	 */
+
+	if (proc->pronargs == 0)
+	{
+		append_string_object(stmt, "agg type", "noargs");
+		append_string_object(stmt, "types", "*");
+	}
+	else if (!AGGKIND_IS_ORDERED_SET(agg->aggkind))
+	{
+		int			i;
+		int			nargs;
+		Oid		   *types;
+		char	   *modes;
+		char	  **names;
+
+		nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+		/* only direct arguments in this case */
+		list = NIL;
+		for (i = 0; i < nargs; i++)
+		{
+			list = lappend(list, form_agg_argument(i, modes, names, types));
+		}
+
+		tmp = new_objtree_VA("%{direct:, }s", 1,
+							 "direct", ObjTypeArray, list);
+		append_object_object(stmt, "types", tmp);
+		append_string_object(stmt, "agg type", "plain");
+	}
+	else
+	{
+		int			i;
+		int			nargs;
+		Oid		   *types;
+		char	   *modes;
+		char	  **names;
+
+		nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+		tmp = new_objtree_VA("%{direct:, }s ORDER BY %{aggregated:, }s", 0);
+
+		/* direct arguments ... */
+		list = NIL;
+		for (i = 0; i < agg->aggnumdirectargs; i++)
+		{
+			list = lappend(list, form_agg_argument(i, modes, names, types));
+		}
+		append_array_object(tmp, "direct", list);
+
+		/*
+		 * ... and aggregated arguments.  If the last direct argument is
+		 * VARIADIC, we need to repeat it here rather than searching for more
+		 * arguments.  (See aggr_args production in gram.y for an explanation.)
+		 */
+		if (modes && modes[agg->aggnumdirectargs - 1] == 'v')
+		{
+			list = list_make1(form_agg_argument(agg->aggnumdirectargs - 1,
+												modes, names, types));
+		}
+		else
+		{
+			list = NIL;
+			for (i = agg->aggnumdirectargs; i < nargs; i++)
+			{
+				list = lappend(list, form_agg_argument(i, modes, names, types));
+			}
+		}
+		append_array_object(tmp, "aggregated", list);
+
+		append_object_object(stmt, "types", tmp);
+		append_string_object(stmt, "agg type", "ordered-set");
+	}
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* SFUNC */
+	tmp = new_objtree_VA("SFUNC=%{procedure}D", 1,
+						 "clause", ObjTypeString, "sfunc");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 agg->aggtransfn));
+	list = lappend(list, new_object_object(tmp));
+
+	/* STYPE */
+	tmp = new_objtree_VA("STYPE=%{type}T", 1,
+						 "clause", ObjTypeString, "stype");
+	append_object_object(tmp, "type",
+						 new_objtree_for_type(agg->aggtranstype, -1));
+	list = lappend(list, new_object_object(tmp));
+
+	/* SSPACE */
+	tmp = new_objtree_VA("SSPACE=%{space}n", 1,
+						 "clause", ObjTypeString, "sspace");
+	if (agg->aggtransspace != 0)
+		append_integer_object(tmp, "space", agg->aggtransspace);
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* FINALFUNC */
+	tmp = new_objtree_VA("FINALFUNC=%{procedure}D", 1,
+						 "clause", ObjTypeString, "finalfunc");
+	if (OidIsValid(agg->aggfinalfn)) append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggfinalfn));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* FINALFUNC_EXTRA */
+	tmp = new_objtree_VA("FINALFUNC_EXTRA=%{value}s", 1,
+						 "clause", ObjTypeString, "finalfunc_extra");
+	if (agg->aggfinalextra)
+		append_string_object(tmp, "value", "true");
+	else
+		append_string_object(tmp, "value", "false");
+	list = lappend(list, new_object_object(tmp));
+
+	/* INITCOND */
+	initval = SysCacheGetAttr(AGGFNOID, aggTup,
+							  Anum_pg_aggregate_agginitval,
+							  &isnull);
+	tmp = new_objtree_VA("INITCOND=%{initval}L", 1,
+						 "clause", ObjTypeString, "initcond");
+	if (!isnull)
+		append_string_object(tmp, "initval",
+							 TextDatumGetCString(initval));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MSFUNC */
+	tmp = new_objtree_VA("MSFUNC=%{procedure}D", 1,
+						 "clause", ObjTypeString, "msfunc");
+	if (OidIsValid(agg->aggmtransfn))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggmtransfn));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MSTYPE */
+	tmp = new_objtree_VA("MSTYPE=%{type}T", 1,
+						 "clause", ObjTypeString, "mstype");
+	if (OidIsValid(agg->aggmtranstype))
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(agg->aggmtranstype, -1));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MSSPACE */
+	tmp = new_objtree_VA("MSSPACE=%{space}n", 1,
+						 "clause", ObjTypeString, "msspace");
+	if (agg->aggmtransspace != 0)
+		append_integer_object(tmp, "space", agg->aggmtransspace);
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MINVFUNC */
+	tmp = new_objtree_VA("MINVFUNC=%{procedure}D", 1,
+						 "clause", ObjTypeString, "minvfunc");
+	if (OidIsValid(agg->aggminvtransfn))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggminvtransfn));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MFINALFUNC */
+	tmp = new_objtree_VA("MFINALFUNC=%{procedure}D", 1,
+						 "clause", ObjTypeString, "mfinalfunc");
+	if (OidIsValid(agg->aggmfinalfn))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggmfinalfn));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MFINALFUNC_EXTRA */
+	tmp = new_objtree_VA("MFINALFUNC_EXTRA=%{value}s", 1,
+						 "clause", ObjTypeString, "mfinalfunc_extra");
+	if (agg->aggmfinalextra)
+		append_string_object(tmp, "value", "true");
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MINITCOND */
+	initval = SysCacheGetAttr(AGGFNOID, aggTup,
+							  Anum_pg_aggregate_aggminitval,
+							  &isnull);
+	tmp = new_objtree_VA("MINITCOND=%{initval}L", 1,
+						 "clause", ObjTypeString, "minitcond");
+	if (!isnull)
+		append_string_object(tmp, "initval",
+							 TextDatumGetCString(initval));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* HYPOTHETICAL */
+	tmp = new_objtree_VA("HYPOTHETICAL=%{value}s", 1,
+						 "clause", ObjTypeString, "hypothetical");
+	if (agg->aggkind == AGGKIND_HYPOTHETICAL)
+		append_string_object(tmp, "value", "true");
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* SORTOP */
+	tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(agg->aggsortop));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for operator with OID %u", agg->aggsortop);
+	op = (Form_pg_operator) GETSTRUCT(tup);
+
+	tmp = new_objtree_VA("SORTOP=%{operator}D", 1,
+						 "clause", ObjTypeString, "sortop");
+	if (OidIsValid(agg->aggsortop))
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname(op->oprnamespace,
+													  NameStr(op->oprname)));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	ReleaseSysCache(tup);
+
+	/* Done with the definition clause */
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(procTup);
+	ReleaseSysCache(aggTup);
+
+	return stmt;
+}
+
+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}D (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;
+	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)));
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* PROCEDURE */
+	tmp = new_objtree_VA("PROCEDURE=%{procedure}D", 1,
+						 "clause", ObjTypeString, "procedure");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 oprForm->oprcode));
+	list = lappend(list, new_object_object(tmp));
+
+	/* LEFTARG */
+	tmp = new_objtree_VA("LEFTARG=%{type}T", 1,
+						 "clause", ObjTypeString, "leftarg");
+	if (OidIsValid(oprForm->oprleft))
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(oprForm->oprleft, -1));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* RIGHTARG */
+	tmp = new_objtree_VA("RIGHTARG=%{type}T", 1,
+						 "clause", ObjTypeString, "rightarg");
+	if (OidIsValid(oprForm->oprright))
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(oprForm->oprright, -1));
+	append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* COMMUTATOR */
+	tmp = new_objtree_VA("COMMUTATOR=%{oper}D", 1,
+						 "clause", ObjTypeString, "commutator");
+	if (OidIsValid(oprForm->oprcom))
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprcom));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* NEGATOR */
+	tmp = new_objtree_VA("NEGATOR=%{oper}D", 1,
+						 "clause", ObjTypeString, "negator");
+	if (OidIsValid(oprForm->oprnegate))
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprnegate));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* RESTRICT */
+	tmp = new_objtree_VA("RESTRICT=%{procedure}D", 1,
+						 "clause", ObjTypeString, "restrict");
+	if (OidIsValid(oprForm->oprrest))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 oprForm->oprrest));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* JOIN */
+	tmp = new_objtree_VA("JOIN=%{procedure}D", 1,
+						 "clause", ObjTypeString, "join");
+	if (OidIsValid(oprForm->oprjoin))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 oprForm->oprjoin));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MERGES */
+	tmp = new_objtree_VA("MERGES", 1,
+						 "clause", ObjTypeString, "merges");
+	if (!oprForm->oprcanmerge)
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* HASHES */
+	tmp = new_objtree_VA("HASHES", 1,
+						 "clause", ObjTypeString, "hashes");
+	if (!oprForm->oprcanhash)
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(oprTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define,
+							ObjectAddress copied)
+{
+	HeapTuple   tscTup;
+	HeapTuple   tspTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	Form_pg_ts_config tscForm;
+	Form_pg_ts_parser tspForm;
+	List	   *list;
+
+	tscTup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tscTup))
+		elog(ERROR, "cache lookup failed for text search configuration %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 %u",
+			 tscForm->cfgparser);
+	tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup);
+
+	stmt = new_objtree_VA("CREATE TEXT SEARCH CONFIGURATION %{identity}D (%{elems:, }s)",
+						  0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(tscForm->cfgnamespace,
+												  NameStr(tscForm->cfgname)));
+
+	/*
+	 * Add the definition clause.  If we have COPY'ed an existing config, add
+	 * a COPY clause; otherwise add a PARSER clause.
+	 */
+	list = NIL;
+	/* COPY */
+	tmp = new_objtree_VA("COPY=%{tsconfig}D", 1,
+						 "clause", ObjTypeString, "copy");
+	if (copied.objectId != InvalidOid)
+		append_object_object(tmp, "tsconfig",
+							 new_objtree_for_qualname_id(TSConfigRelationId,
+														 copied.objectId));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* PARSER */
+	tmp = new_objtree_VA("PARSER=%{parser}D", 1,
+						 "clause", ObjTypeString, "parser");
+	if (copied.objectId == InvalidOid)
+		append_object_object(tmp, "parser",
+							 new_objtree_for_qualname(tspForm->prsnamespace,
+													  NameStr(tspForm->prsname)));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+	ReleaseSysCache(tscTup);
+
+	return stmt;
+}
+
+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)));
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* START */
+	tmp = new_objtree_VA("START=%{procedure}D", 1,
+						 "clause", ObjTypeString, "start");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prsstart));
+	list = lappend(list, new_object_object(tmp));
+
+	/* GETTOKEN */
+	tmp = new_objtree_VA("GETTOKEN=%{procedure}D", 1,
+						 "clause", ObjTypeString, "gettoken");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prstoken));
+	list = lappend(list, new_object_object(tmp));
+
+	/* END */
+	tmp = new_objtree_VA("END=%{procedure}D", 1,
+						 "clause", ObjTypeString, "end");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prsend));
+	list = lappend(list, new_object_object(tmp));
+
+	/* LEXTYPES */
+	tmp = new_objtree_VA("LEXTYPES=%{procedure}D", 1,
+						 "clause", ObjTypeString, "lextypes");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prslextype));
+	list = lappend(list, new_object_object(tmp));
+
+	/* HEADLINE */
+	tmp = new_objtree_VA("HEADLINE=%{procedure}D", 1,
+						 "clause", ObjTypeString, "headline");
+	if (OidIsValid(tspForm->prsheadline))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 tspForm->prsheadline));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+
+	return stmt;
+}
+
+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)));
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* TEMPLATE */
+	tmp = new_objtree_VA("TEMPLATE=%{template}D", 1,
+						 "clause", ObjTypeString, "template");
+	append_object_object(tmp, "template",
+						 new_objtree_for_qualname_id(TSTemplateRelationId,
+													 tsdForm->dicttemplate));
+	list = lappend(list, new_object_object(tmp));
+
+	/*
+	 * options.  XXX this is a pretty useless representation, but we can't do
+	 * better without more ts_cache.c cooperation ...
+	 */
+	options = SysCacheGetAttr(TSDICTOID, tsdTup,
+							  Anum_pg_ts_dict_dictinitoption,
+							  &isnull);
+	tmp = new_objtree_VA("%{options}s", 0);
+	if (!isnull)
+		append_string_object(tmp, "options", TextDatumGetCString(options));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tsdTup);
+
+	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)));
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* INIT */
+	tmp = new_objtree_VA("INIT=%{procedure}D", 1,
+						 "clause", ObjTypeString, "init");
+	if (OidIsValid(tstForm->tmplinit))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 tstForm->tmplinit));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* LEXIZE */
+	tmp = new_objtree_VA("LEXIZE=%{procedure}D", 1,
+						 "clause", ObjTypeString, "lexize");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tstForm->tmpllexize));
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tstTup);
+
+	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", 1,
+							  "is_shell", ObjTypeBool, true);
+		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)", 1,
+						  "is_shell", ObjTypeBool, false);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(typForm->typnamespace,
+												  NameStr(typForm->typname)));
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* INPUT */
+	tmp = new_objtree_VA("INPUT=%{procedure}D", 1,
+						 "clause", ObjTypeString, "input");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 typForm->typinput));
+	list = lappend(list, new_object_object(tmp));
+
+	/* OUTPUT */
+	tmp = new_objtree_VA("OUTPUT=%{procedure}D", 1,
+						 "clause", ObjTypeString, "output");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 typForm->typoutput));
+	list = lappend(list, new_object_object(tmp));
+
+	/* RECEIVE */
+	tmp = new_objtree_VA("RECEIVE=%{procedure}D", 1,
+						 "clause", ObjTypeString, "receive");
+	if (OidIsValid(typForm->typreceive))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typreceive));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* SEND */
+	tmp = new_objtree_VA("SEND=%{procedure}D", 1,
+						 "clause", ObjTypeString, "send");
+	if (OidIsValid(typForm->typsend))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typsend));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* TYPMOD_IN */
+	tmp = new_objtree_VA("TYPMOD_IN=%{procedure}D", 1,
+						 "clause", ObjTypeString, "typmod_in");
+	if (OidIsValid(typForm->typmodin))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typmodin));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* TYPMOD_OUT */
+	tmp = new_objtree_VA("TYPMOD_OUT=%{procedure}D", 1,
+						 "clause", ObjTypeString, "typmod_out");
+	if (OidIsValid(typForm->typmodout))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typmodout));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* ANALYZE */
+	tmp = new_objtree_VA("ANALYZE=%{procedure}D", 1,
+						 "clause", ObjTypeString, "analyze");
+	if (OidIsValid(typForm->typanalyze))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typanalyze));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* INTERNALLENGTH */
+	if (typForm->typlen == -1)
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=VARIABLE", 0);
+	}
+	else
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}n", 1,
+							 "typlen", ObjTypeInteger, typForm->typlen);
+	}
+	append_string_object(tmp, "clause", "internallength");
+	list = lappend(list, new_object_object(tmp));
+
+	/* PASSEDBYVALUE */
+	tmp = new_objtree_VA("PASSEDBYVALUE", 1,
+						 "clause", ObjTypeString, "passedbyvalue");
+	if (!typForm->typbyval)
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* ALIGNMENT */
+	tmp = new_objtree_VA("ALIGNMENT=%{align}s", 1,
+						 "clause", ObjTypeString, "alignment");
+	/* XXX it's odd to represent alignment with schema-qualified type names */
+	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(tmp));
+
+	tmp = new_objtree_VA("STORAGE=%{storage}s", 1,
+						 "clause", ObjTypeString, "storage");
+	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(tmp));
+
+	/* CATEGORY */
+	tmp = new_objtree_VA("CATEGORY=%{category}L", 1,
+						 "clause", ObjTypeString, "category");
+	append_string_object(tmp, "category",
+						 psprintf("%c", typForm->typcategory));
+	list = lappend(list, new_object_object(tmp));
+
+	/* PREFERRED */
+	tmp = new_objtree_VA("PREFERRED=true", 1,
+						 "clause", ObjTypeString, "preferred");
+	if (!typForm->typispreferred)
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* DEFAULT */
+	dflt = SysCacheGetAttr(TYPEOID, typTup,
+						   Anum_pg_type_typdefault,
+						   &isnull);
+	tmp = new_objtree_VA("DEFAULT=%{default}L", 1,
+						 "clause", ObjTypeString, "default");
+	if (!isnull)
+		append_string_object(tmp, "default", TextDatumGetCString(dflt));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* ELEMENT */
+	tmp = new_objtree_VA("ELEMENT=%{elem}T", 1,
+						 "clause", ObjTypeString, "element");
+	if (OidIsValid(typForm->typelem))
+		append_object_object(tmp, "elem",
+							 new_objtree_for_type(typForm->typelem, -1));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* DELIMITER */
+	tmp = new_objtree_VA("DELIMITER=%{delim}L", 1,
+						 "clause", ObjTypeString, "delimiter");
+	append_string_object(tmp, "delim",
+						 psprintf("%c", typForm->typdelim));
+	list = lappend(list, new_object_object(tmp));
+
+	/* COLLATABLE */
+	tmp = new_objtree_VA("COLLATABLE=true", 1,
+						 "clause", ObjTypeString, "collatable");
+	if (!OidIsValid(typForm->typcollation))
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(typTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt(Oid objectId, Node *parsetree, ObjectAddress secondaryObj)
+{
+	DefineStmt *define = (DefineStmt *) parsetree;
+	ObjTree	   *defStmt;
+
+	switch (define->kind)
+	{
+		case OBJECT_AGGREGATE:
+			defStmt = deparse_DefineStmt_Aggregate(objectId, define);
+			break;
+
+		case OBJECT_COLLATION:
+			defStmt = deparse_DefineStmt_Collation(objectId, define);
+			break;
+
+		case OBJECT_OPERATOR:
+			defStmt = deparse_DefineStmt_Operator(objectId, define);
+			break;
+
+		case OBJECT_TSCONFIGURATION:
+			defStmt = deparse_DefineStmt_TSConfig(objectId, define,
+												  secondaryObj);
+			break;
+
+		case OBJECT_TSPARSER:
+			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;
+
+		case OBJECT_TYPE:
+			defStmt = deparse_DefineStmt_Type(objectId, define);
+			break;
+
+		default:
+			elog(ERROR, "unsupported object kind");
+			return NULL;
+	}
+
+	return defStmt;
+}
+
+static ObjTree *
+deparse_AlterTSConfigurationStmt(CollectedCommand *cmd)
+{
+	AlterTSConfigurationStmt *node = (AlterTSConfigurationStmt *) cmd->parsetree;
+	ObjTree *config;
+	ObjTree *tmp;
+	const char *const fmtcommon = "ALTER TEXT SEARCH CONFIGURATION %{identity}D";
+	char	   *fmtrest;
+	char	   *type;
+	List	   *list;
+	ListCell   *cell;
+	int			i;
+
+	/* determine the format string appropriate to each subcommand */
+	switch (node->kind)
+	{
+		case ALTER_TSCONFIG_ADD_MAPPING:
+			fmtrest = "ADD MAPPING FOR %{tokentype:, }I WITH %{dictionaries:, }D";
+			type = "add mapping for token";
+			break;
+
+		case ALTER_TSCONFIG_DROP_MAPPING:
+			fmtrest = "DROP MAPPING %{if_exists}s FOR %{tokentype}I";
+			type = "drop mapping for token";
+			break;
+
+		case ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN:
+			fmtrest = "ALTER MAPPING FOR %{tokentype:, }I WITH %{dictionaries:, }D";
+			type = "alter mapping for token";
+			break;
+
+		case ALTER_TSCONFIG_REPLACE_DICT:
+			fmtrest = "ALTER MAPPING REPLACE %{old_dictionary}D WITH %{new_dictionary}D";
+			type = "alter mapping replace";
+			break;
+
+		case ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN:
+			fmtrest = "ALTER MAPPING FOR %{tokentype:, }I REPLACE %{old_dictionary}D WITH %{new_dictionary}D";
+			type = "alter mapping replace for token";
+			break;
+	}
+	config = new_objtree_VA(psprintf("%s %s", fmtcommon, fmtrest),
+							2, "identity",
+							ObjTypeObject,
+							new_objtree_for_qualname_id(cmd->d.atscfg.address.classId,
+														cmd->d.atscfg.address.objectId),
+							"type", ObjTypeString, type);
+
+	/* Add the affected token list, for subcommands that have one */
+	if (node->kind == ALTER_TSCONFIG_ADD_MAPPING ||
+		node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN ||
+		node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN ||
+		node->kind == ALTER_TSCONFIG_DROP_MAPPING)
+	{
+		list = NIL;
+		foreach(cell, node->tokentype)
+			list = lappend(list, new_string_object(strVal(lfirst(cell))));
+		append_array_object(config, "tokentype", list);
+	}
+
+	/* add further subcommand-specific elements */
+	if (node->kind == ALTER_TSCONFIG_ADD_MAPPING ||
+		node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN)
+	{
+		/* ADD MAPPING and ALTER MAPPING FOR need a list of dictionaries */
+		list = NIL;
+		for (i = 0; i < cmd->d.atscfg.ndicts; i++)
+		{
+			ObjTree	*dictobj;
+
+			dictobj = new_objtree_for_qualname_id(TSDictionaryRelationId,
+												  cmd->d.atscfg.dictIds[i]);
+			list = lappend(list,
+						   new_object_object(dictobj));
+		}
+		append_array_object(config, "dictionaries", list);
+	}
+	else if (node->kind == ALTER_TSCONFIG_REPLACE_DICT ||
+			 node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN)
+	{
+		/* the REPLACE forms want old and new dictionaries */
+		Assert(cmd->d.atscfg.ndicts == 2);
+		append_object_object(config, "old_dictionary",
+							 new_objtree_for_qualname_id(TSDictionaryRelationId,
+														 cmd->d.atscfg.dictIds[0]));
+		append_object_object(config, "new_dictionary",
+							 new_objtree_for_qualname_id(TSDictionaryRelationId,
+														 cmd->d.atscfg.dictIds[1]));
+	}
+	else
+	{
+		/* DROP wants the IF EXISTS clause */
+		Assert(node->kind == ALTER_TSCONFIG_DROP_MAPPING);
+		tmp = new_objtree_VA("IF EXISTS", 0);
+		append_bool_object(tmp, "present", node->missing_ok);
+		append_object_object(config, "if_exists", tmp);
+	}
+
+	return config;
+}
+
+/*
+ * 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 ObjTree *
+deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
+{
+	CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *extStmt;
+	ObjTree	   *tmp;
+	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);
+
+	/* IF NOT EXISTS */
+	tmp = new_objtree_VA("IF NOT EXISTS", 0);
+	append_bool_object(tmp, "present", node->if_not_exists);
+	append_object_object(extStmt, "if_not_exists", tmp);
+
+	/* List of options */
+	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(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(tmp));
+		}
+		else
+			elog(ERROR, "unsupported option %s", opt->defname);
+	}
+
+	/* Add the SCHEMA option */
+	tmp = new_objtree_VA("SCHEMA %{schema}I",
+						 2, "type", ObjTypeString, "schema",
+						 "schema", ObjTypeString,
+						 get_namespace_name(extForm->extnamespace));
+	list = lappend(list, new_object_object(tmp));
+
+	/* done */
+	append_array_object(extStmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return extStmt;
+}
+
+static ObjTree *
+deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
+{
+	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list = NIL;
+	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 %{options: }s", 1,
+						  "identity", ObjTypeString,
+						  NameStr(extForm->extname));
+
+	foreach(cell, node->options)
+	{
+		DefElem *opt = (DefElem *) lfirst(cell);
+
+		if (strcmp(opt->defname, "new_version") == 0)
+		{
+			tmp = new_objtree_VA("TO %{version}L", 2,
+								 "type", ObjTypeString, "version",
+								 "version", ObjTypeString, defGetString(opt));
+			list = lappend(list, new_object_object(tmp));
+		}
+		else
+			elog(ERROR, "unsupported option %s", opt->defname);
+	}
+
+	append_array_object(stmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return stmt;
+}
+
+/*
+ * ALTER EXTENSION ext ADD/DROP object
+ */
+static ObjTree *
+deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree,
+								   ObjectAddress objectAddress)
+{
+	AlterExtensionContentsStmt *node = (AlterExtensionContentsStmt *) parsetree;
+	ObjTree	   *stmt;
+	char	   *fmt;
+
+	Assert(node->action == +1 || node->action == -1);
+
+	fmt = psprintf("ALTER EXTENSION %%{extidentity}I %s %s %%{objidentity}s",
+				   node->action == +1 ? "ADD" : "DROP",
+				   stringify_objtype(node->objtype));
+
+	stmt = new_objtree_VA(fmt, 2, "extidentity", ObjTypeString, node->extname,
+						  "objidentity", ObjTypeString,
+						  getObjectIdentity(&objectAddress));
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_AlterDomainStmt(Oid objectId, Node *parsetree,
+						ObjectAddress constraintAddr)
+{
+	AlterDomainStmt *node = (AlterDomainStmt *) parsetree;
+	HeapTuple	domTup;
+	Form_pg_type domForm;
+	ObjTree	   *alterDom;
+	char	   *fmt;
+	const char *type;
+
+	/* ALTER DOMAIN DROP CONSTRAINT is handled by the DROP support code */
+	if (node->subtype == 'X')
+		return NULL;
+
+	domTup = SearchSysCache1(TYPEOID, objectId);
+	if (!HeapTupleIsValid(domTup))
+		elog(ERROR, "cache lookup failed for domain with OID %u",
+			 objectId);
+	domForm = (Form_pg_type) GETSTRUCT(domTup);
+
+	switch (node->subtype)
+	{
+		case 'T':
+			/* SET DEFAULT / DROP DEFAULT */
+			if (node->def == NULL)
+			{
+				fmt = "ALTER DOMAIN %{identity}D DROP DEFAULT";
+				type = "drop default";
+			}
+			else
+			{
+				fmt = "ALTER DOMAIN %{identity}D SET DEFAULT %{default}s";
+				type = "set default";
+			}
+			break;
+		case 'N':
+			/* DROP NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D DROP NOT NULL";
+			type = "drop not null";
+			break;
+		case 'O':
+			/* SET NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D SET NOT NULL";
+			type = "set not null";
+			break;
+		case 'C':
+			/* ADD CONSTRAINT.  Only CHECK constraints are supported by domains */
+			fmt = "ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}I %{definition}s";
+			type = "add constraint";
+			break;
+		case 'V':
+			/* VALIDATE CONSTRAINT */
+			fmt = "ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}I";
+			type = "validate constraint";
+			break;
+		default:
+			elog(ERROR, "invalid subtype %c", node->subtype);
+	}
+
+	alterDom = new_objtree_VA(fmt, 1, "type", ObjTypeString, type);
+	append_object_object(alterDom, "identity",
+						 new_objtree_for_qualname(domForm->typnamespace,
+												  NameStr(domForm->typname)));
+
+	/*
+	 * Process subtype-specific options.  Validating a constraint
+	 * requires its name ...
+	 */
+	if (node->subtype == 'V')
+		append_string_object(alterDom, "constraint_name", node->name);
+
+	/* ... a new constraint has a name and definition ... */
+	if (node->subtype == 'C')
+	{
+		append_string_object(alterDom, "definition",
+							 pg_get_constraintdef_string(constraintAddr.objectId,
+														 false));
+		/* can't rely on node->name here; might not be defined */
+		append_string_object(alterDom, "constraint_name",
+							 get_constraint_name(constraintAddr.objectId));
+	}
+
+	/* ... and setting a default has a definition only. */
+	if (node->subtype == 'T' && node->def != NULL)
+		append_string_object(alterDom, "default", DomainGetDefault(domTup));
+
+	/* done */
+	ReleaseSysCache(domTup);
+
+	return alterDom;
+}
+
+/*
+ * If a column name is specified, add an element for it; otherwise it's a
+ * table-level option.
+ */
+static ObjTree *
+deparse_FdwOptions(List *options, char *colname)
+{
+	ObjTree	   *tmp;
+
+	if (colname)
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I OPTIONS (%{option:, }s)",
+							 1, "column", ObjTypeString, colname);
+	else
+		tmp = new_objtree_VA("OPTIONS (%{option:, }s)", 0);
+
+	if (options != NIL)
+	{
+		List	   *optout = NIL;
+		ListCell   *cell;
+
+		foreach(cell, options)
+		{
+			DefElem	   *elem;
+			ObjTree	   *opt;
+
+			elem = (DefElem *) lfirst(cell);
+
+			switch (elem->defaction)
+			{
+				case DEFELEM_UNSPEC:
+					opt = new_objtree_VA("%{label}I %{value}L", 0);
+					break;
+				case DEFELEM_SET:
+					opt = new_objtree_VA("SET %{label}I %{value}L", 0);
+					break;
+				case DEFELEM_ADD:
+					opt = new_objtree_VA("ADD %{label}I %{value}L", 0);
+					break;
+				case DEFELEM_DROP:
+					opt = new_objtree_VA("DROP %{label}I", 0);
+					break;
+				default:
+					elog(ERROR, "invalid def action %d", elem->defaction);
+					opt = NULL;
+			}
+
+			append_string_object(opt, "label", elem->defname);
+			append_string_object(opt, "value",
+								 elem->arg ? defGetString(elem) :
+								 defGetBoolean(elem) ? "TRUE" : "FALSE");
+
+			optout = lappend(optout, new_object_object(opt));
+		}
+
+		append_array_object(tmp, "option", optout);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+
+	return tmp;
+}
+
+static ObjTree *
+deparse_CreateFdwStmt(Oid objectId, Node *parsetree)
+{
+	CreateFdwStmt *node = (CreateFdwStmt *) parsetree;
+	HeapTuple		fdwTup;
+	Form_pg_foreign_data_wrapper fdwForm;
+	Relation	rel;
+
+	ObjTree	   *createStmt;
+	ObjTree	   *tmp;
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+							 ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(fdwTup))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", objectId);
+
+	fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup);
+
+	createStmt = new_objtree_VA("CREATE FOREIGN DATA WRAPPER %{identity}I"
+								" %{handler}s %{validator}s %{generic_options}s", 1,
+								"identity", ObjTypeString, NameStr(fdwForm->fdwname));
+
+	/* add HANDLER clause */
+	if (fdwForm->fdwhandler == InvalidOid)
+		tmp = new_objtree_VA("NO HANDLER", 0);
+	else
+	{
+		tmp = new_objtree_VA("HANDLER %{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 fdwForm->fdwhandler));
+	}
+	append_object_object(createStmt, "handler", tmp);
+
+	/* add VALIDATOR clause */
+	if (fdwForm->fdwvalidator == InvalidOid)
+		tmp = new_objtree_VA("NO VALIDATOR", 0);
+	else
+	{
+		tmp = new_objtree_VA("VALIDATOR %{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 fdwForm->fdwvalidator));
+	}
+	append_object_object(createStmt, "validator", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(fdwTup);
+	heap_close(rel, RowExclusiveLock);
+
+	return createStmt;
+}
+
+static ObjTree *
+deparse_AlterFdwStmt(Oid objectId, Node *parsetree)
+{
+	AlterFdwStmt *node = (AlterFdwStmt *) parsetree;
+	HeapTuple		fdwTup;
+	Form_pg_foreign_data_wrapper fdwForm;
+	Relation	rel;
+	ObjTree	   *alterStmt;
+	List	   *fdw_options = NIL;
+	ListCell   *cell;
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+							 ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(fdwTup))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", objectId);
+
+	fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup);
+
+	alterStmt = new_objtree_VA("ALTER FOREIGN DATA WRAPPER %{identity}I"
+							   " %{fdw_options: }s %{generic_options}s", 1,
+							   "identity", ObjTypeString, NameStr(fdwForm->fdwname));
+
+	/*
+	 * Iterate through options, to see what changed, but use catalog as basis
+	 * for new values.
+	 */
+	foreach(cell, node->func_options)
+	{
+		DefElem	   *elem;
+		ObjTree	   *tmp;
+
+		elem = lfirst(cell);
+
+		if (pg_strcasecmp(elem->defname, "handler") == 0)
+		{
+			/* add HANDLER clause */
+			if (fdwForm->fdwhandler == InvalidOid)
+				tmp = new_objtree_VA("NO HANDLER", 0);
+			else
+			{
+				tmp = new_objtree_VA("HANDLER %{procedure}D", 0);
+				append_object_object(tmp, "procedure",
+									 new_objtree_for_qualname_id(ProcedureRelationId,
+																 fdwForm->fdwhandler));
+			}
+			fdw_options = lappend(fdw_options, new_object_object(tmp));
+		}
+		else if (pg_strcasecmp(elem->defname, "validator") == 0)
+		{
+			/* add VALIDATOR clause */
+			if (fdwForm->fdwvalidator == InvalidOid)
+				tmp = new_objtree_VA("NO VALIDATOR", 0);
+			else
+			{
+				tmp = new_objtree_VA("VALIDATOR %{procedure}D", 0);
+				append_object_object(tmp, "procedure",
+									 new_objtree_for_qualname_id(ProcedureRelationId,
+																 fdwForm->fdwvalidator));
+			}
+			fdw_options = lappend(fdw_options, new_object_object(tmp));
+		}
+	}
+
+	/* Add HANDLER/VALIDATOR if specified */
+	append_array_object(alterStmt, "fdw_options", fdw_options);
+
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(fdwTup);
+	heap_close(rel, RowExclusiveLock);
+
+	return alterStmt;
+}
+
+static ObjTree *
+deparse_CreateForeignServerStmt(Oid objectId, Node *parsetree)
+{
+	CreateForeignServerStmt *node = (CreateForeignServerStmt *) parsetree;
+	ObjTree	   *createServer;
+	ObjTree	   *tmp;
+
+	createServer = new_objtree_VA("CREATE SERVER %{identity}I %{type}s %{version}s "
+								  "FOREIGN DATA WRAPPER %{fdw}I %{generic_options}s", 2,
+								  "identity", ObjTypeString, node->servername,
+								  "fdw",  ObjTypeString, node->fdwname);
+
+	/* add a TYPE clause, if any */
+	tmp = new_objtree_VA("TYPE %{type}L", 0);
+	if (node->servertype)
+		append_string_object(tmp, "type", node->servertype);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "type", tmp);
+
+	/* add a VERSION clause, if any */
+	tmp = new_objtree_VA("VERSION %{version}L", 0);
+	if (node->version)
+		append_string_object(tmp, "version", node->version);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "version", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createServer, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	return createServer;
+}
+
+static ObjTree *
+deparse_AlterForeignServerStmt(Oid objectId, Node *parsetree)
+{
+	AlterForeignServerStmt *node = (AlterForeignServerStmt *) parsetree;
+	ObjTree	   *alterServer;
+	ObjTree	   *tmp;
+
+	alterServer = new_objtree_VA("ALTER SERVER %{identity}I %{version}s "
+								  "%{generic_options}s", 1,
+								  "identity", ObjTypeString, node->servername);
+
+	/* add a VERSION clause, if any */
+	tmp = new_objtree_VA("VERSION %{version}s", 0);
+	if (node->has_version && node->version)
+		append_string_object(tmp, "version", quote_literal_cstr(node->version));
+	else if (node->has_version)
+		append_string_object(tmp, "version", "NULL");
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterServer, "version", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterServer, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	return alterServer;
+}
+
+static ObjTree *
+deparse_CreateUserMappingStmt(Oid objectId, Node *parsetree)
+{
+	CreateUserMappingStmt *node = (CreateUserMappingStmt *) parsetree;
+	ObjTree	   *createStmt;
+	Relation	rel;
+	HeapTuple	tp;
+	Form_pg_user_mapping form;
+	ForeignServer *server;
+
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	/*
+	 * Lookup up object in the catalog, so we don't have to deal with
+	 * current_user and such.
+	 */
+	tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for user mapping %u", objectId);
+
+	form = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+	server = GetForeignServer(form->umserver);
+
+	createStmt = new_objtree_VA("CREATE USER MAPPING FOR %{role}R SERVER %{server}I "
+								"%{generic_options}s", 1,
+								"server", ObjTypeString, server->servername);
+
+	append_object_object(createStmt, "role", new_objtree_for_role_id(form->umuser));
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(tp);
+	heap_close(rel, RowExclusiveLock);
+	return createStmt;
+}
+
+static ObjTree *
+deparse_AlterUserMappingStmt(Oid objectId, Node *parsetree)
+{
+	AlterUserMappingStmt *node = (AlterUserMappingStmt *) parsetree;
+	ObjTree	   *alterStmt;
+	Relation	rel;
+	HeapTuple	tp;
+	Form_pg_user_mapping form;
+	ForeignServer *server;
+
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	/*
+	 * Lookup up object in the catalog, so we don't have to deal with
+	 * current_user and such.
+	 */
+
+	tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for user mapping %u", objectId);
+
+	form = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+	server = GetForeignServer(form->umserver);
+
+	alterStmt = new_objtree_VA("ALTER USER MAPPING FOR %{role}R SERVER %{server}I "
+								"%{generic_options}s", 1,
+								"server", ObjTypeString, server->servername);
+
+	append_object_object(alterStmt, "role", new_objtree_for_role_id(form->umuser));
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(tp);
+	heap_close(rel, RowExclusiveLock);
+	return alterStmt;
+}
+
+/*
+ * deparse_ViewStmt
+ *		deparse a ViewStmt
+ *
+ * Given a view OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_ViewStmt(Oid objectId, Node *parsetree)
+{
+	ViewStmt   *node = (ViewStmt *) parsetree;
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	Relation	relation;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s",
+							  2,
+							  "or_replace", ObjTypeString,
+							  node->replace ? "OR REPLACE" : "",
+							  "persistence", ObjTypeString,
+					  get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(viewStmt, "identity", tmp);
+
+	append_string_object(viewStmt, "query",
+						 pg_get_viewdef_internal(objectId));
+
+	relation_close(relation, AccessShareLock);
+
+	return viewStmt;
+}
+
+/*
+ * deparse_CreateTrigStmt
+ *		Deparse a CreateTrigStmt (CREATE TRIGGER)
+ *
+ * Given a trigger OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
+{
+	CreateTrigStmt *node = (CreateTrigStmt *) parsetree;
+	Relation	pg_trigger;
+	HeapTuple	trigTup;
+	Form_pg_trigger trigForm;
+	ObjTree	   *trigger;
+	ObjTree	   *tmp;
+	int			tgnargs;
+	List	   *list;
+	List	   *events;
+
+	pg_trigger = heap_open(TriggerRelationId, AccessShareLock);
+
+	trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+	trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+	/*
+	 * Some of the elements only make sense for CONSTRAINT TRIGGERs, but it
+	 * seems simpler to use a single fmt string for both kinds of triggers.
+	 */
+	trigger =
+		new_objtree_VA("CREATE %{constraint}s TRIGGER %{name}I %{time}s %{events: OR }s "
+					   "ON %{relation}D %{from_table}s %{constraint_attrs: }s "
+					   "FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE %{function}s",
+					   2,
+					   "name", ObjTypeString, node->trigname,
+					   "constraint", ObjTypeString,
+					   node->isconstraint ? "CONSTRAINT" : "");
+
+	if (node->timing == TRIGGER_TYPE_BEFORE)
+		append_string_object(trigger, "time", "BEFORE");
+	else if (node->timing == TRIGGER_TYPE_AFTER)
+		append_string_object(trigger, "time", "AFTER");
+	else if (node->timing == TRIGGER_TYPE_INSTEAD)
+		append_string_object(trigger, "time", "INSTEAD OF");
+	else
+		elog(ERROR, "unrecognized trigger timing value %d", node->timing);
+
+	/*
+	 * Decode the events that the trigger fires for.  The output is a list;
+	 * in most cases it will just be a string with the even name, but when
+	 * there's an UPDATE with a list of columns, we return a JSON object.
+	 */
+	events = NIL;
+	if (node->events & TRIGGER_TYPE_INSERT)
+		events = lappend(events, new_string_object("INSERT"));
+	if (node->events & TRIGGER_TYPE_DELETE)
+		events = lappend(events, new_string_object("DELETE"));
+	if (node->events & TRIGGER_TYPE_TRUNCATE)
+		events = lappend(events, new_string_object("TRUNCATE"));
+	if (node->events & TRIGGER_TYPE_UPDATE)
+	{
+		if (node->columns == NIL)
+		{
+			events = lappend(events, new_string_object("UPDATE"));
+		}
+		else
+		{
+			ObjTree	   *update;
+			ListCell   *cell;
+			List	   *cols = NIL;
+
+			/*
+			 * Currently only UPDATE OF can be objects in the output JSON, but
+			 * we add a "kind" element so that user code can distinguish
+			 * possible future new event types.
+			 */
+			update = new_objtree_VA("UPDATE OF %{columns:, }I",
+									1, "kind", ObjTypeString, "update_of");
+
+			foreach(cell, node->columns)
+			{
+				char   *colname = strVal(lfirst(cell));
+
+				cols = lappend(cols,
+							   new_string_object(colname));
+			}
+
+			append_array_object(update, "columns", cols);
+
+			events = lappend(events,
+							 new_object_object(update));
+		}
+	}
+	append_array_object(trigger, "events", events);
+
+	tmp = new_objtree_for_qualname_id(RelationRelationId,
+									  trigForm->tgrelid);
+	append_object_object(trigger, "relation", tmp);
+
+	tmp = new_objtree_VA("FROM %{relation}D", 0);
+	if (trigForm->tgconstrrelid)
+	{
+		ObjTree	   *rel;
+
+		rel = new_objtree_for_qualname_id(RelationRelationId,
+										  trigForm->tgconstrrelid);
+		append_object_object(tmp, "relation", rel);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "from_table", tmp);
+
+	list = NIL;
+	if (node->deferrable)
+		list = lappend(list,
+					   new_string_object("DEFERRABLE"));
+	if (node->initdeferred)
+		list = lappend(list,
+					   new_string_object("INITIALLY DEFERRED"));
+	append_array_object(trigger, "constraint_attrs", list);
+
+	append_string_object(trigger, "for_each",
+						 node->row ? "ROW" : "STATEMENT");
+
+	tmp = new_objtree_VA("WHEN (%{clause}s)", 0);
+	if (node->whenClause)
+	{
+		Node	   *whenClause;
+		Datum		value;
+		bool		isnull;
+
+		value = fastgetattr(trigTup, Anum_pg_trigger_tgqual,
+							RelationGetDescr(pg_trigger), &isnull);
+		if (isnull)
+			elog(ERROR, "bogus NULL tgqual");
+
+		whenClause = stringToNode(TextDatumGetCString(value));
+		append_string_object(tmp, "clause",
+							 pg_get_trigger_whenclause(trigForm,
+													   whenClause,
+													   false));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "when", tmp);
+
+	tmp = new_objtree_VA("%{funcname}D(%{args:, }L)",
+						 1, "funcname", ObjTypeObject,
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 trigForm->tgfoid));
+	list = NIL;
+	tgnargs = trigForm->tgnargs;
+	if (tgnargs > 0)
+	{
+		bytea  *tgargs;
+		char   *argstr;
+		bool	isnull;
+		int		findx;
+		int		lentgargs;
+		char   *p;
+
+		tgargs = DatumGetByteaP(fastgetattr(trigTup,
+											Anum_pg_trigger_tgargs,
+											RelationGetDescr(pg_trigger),
+											&isnull));
+		if (isnull)
+			elog(ERROR, "invalid NULL tgargs");
+		argstr = (char *) VARDATA(tgargs);
+		lentgargs = VARSIZE_ANY_EXHDR(tgargs);
+
+		p = argstr;
+		for (findx = 0; findx < tgnargs; findx++)
+		{
+			size_t	tlen;
+
+			/* verify that the argument encoding is correct */
+			tlen = strlen(p);
+			if (p + tlen >= argstr + lentgargs)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid argument string (%s) for trigger \"%s\"",
+								argstr, NameStr(trigForm->tgname))));
+
+			list = lappend(list, new_string_object(p));
+
+			p += tlen + 1;
+		}
+	}
+
+	append_array_object(tmp, "args", list);		/* might be NIL */
+
+	append_object_object(trigger, "function", tmp);
+
+	heap_close(pg_trigger, AccessShareLock);
+
+	return trigger;
+}
+
+static ObjTree *
+deparse_RefreshMatViewStmt(Oid objectId, Node *parsetree)
+{
+	RefreshMatViewStmt *node = (RefreshMatViewStmt *) parsetree;
+	ObjTree	   *refreshStmt;
+	ObjTree	   *tmp;
+
+	refreshStmt = new_objtree_VA("REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D "
+								 "%{with_no_data}s", 0);
+	/* add a CONCURRENTLY clause */
+	append_string_object(refreshStmt, "concurrently",
+						 node->concurrent ? "CONCURRENTLY" : "");
+	/* add the matview name */
+	append_object_object(refreshStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->skipData ? true : false);
+	append_object_object(refreshStmt, "with_no_data", tmp);
+
+	return refreshStmt;
+}
+
+/*
+ * 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(Relation relation, List *dpcontext, bool composite,
+				  ColumnDef *coldef, bool is_alter)
+{
+	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);
+
+	/* Composite types use a slightly simpler format string */
+	if (composite)
+		column = new_objtree_VA("%{name}I %{coltype}T %{collation}s",
+								3,
+								"type", ObjTypeString, "column",
+								"name", ObjTypeString, coldef->colname,
+								"coltype", ObjTypeObject,
+								new_objtree_for_type(typid, typmod));
+	else
+		column = new_objtree_VA("%{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(typid, typmod));
+
+	tmp = new_objtree_VA("COLLATE %{name}D", 0);
+	if (OidIsValid(typcollation))
+	{
+		ObjTree *collname;
+
+		collname = new_objtree_for_qualname_id(CollationRelationId,
+											   typcollation);
+		append_object_object(tmp, "name", collname);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(column, "collation", tmp);
+
+	if (!composite)
+	{
+		/*
+		 * 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.  Therefore,
+		 * we scan the list of constraints attached to this column to determine
+		 * whether we need to emit anything.
+		 * (Fortunately, NOT NULL constraints cannot be table constraints.)
+		 *
+		 * In the ALTER TABLE cases, we also add a NOT NULL if the colDef is
+		 * marked is_not_null.
+		 */
+		saw_notnull = false;
+		foreach(cell, coldef->constraints)
+		{
+			Constraint *constr = (Constraint *) lfirst(cell);
+
+			if (constr->contype == CONSTR_NOTNULL)
+				saw_notnull = true;
+		}
+		if (is_alter && coldef->is_not_null)
+			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("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(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.
+	 * (This routine is never used for ALTER cases.)
+	 */
+	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("%{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(Relation relation, List *tableElements, List *dpcontext,
+					 bool typed, bool composite)
+{
+	List	   *elements = NIL;
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					ObjTree	   *tree;
+
+					tree = typed ?
+						deparse_ColumnDef_typed(relation, dpcontext,
+												(ColumnDef *) elt) :
+						deparse_ColumnDef(relation, dpcontext,
+										  composite, (ColumnDef *) elt,
+										  false);
+					if (tree != NULL)
+					{
+						ObjElem    *column;
+
+						column = new_object_object(tree);
+						elements = lappend(elements, column);
+					}
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+
+	return elements;
+}
+
+/*
+ * obtainConstraints
+ *		Subroutine for CREATE TABLE/CREATE DOMAIN deparsing
+ *
+ * Given a table OID or domain OID, obtain its constraints and append them to
+ * the given elements list.  The updated list is returned.
+ *
+ * This works for typed tables, regular tables, and domains.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static List *
+obtainConstraints(List *elements, Oid relationId, Oid domainId)
+{
+	Relation	conRel;
+	ScanKeyData key;
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	ObjTree    *tmp;
+
+	/* only one may be valid */
+	Assert(OidIsValid(relationId) ^ OidIsValid(domainId));
+
+	/*
+	 * scan pg_constraint to fetch all constraints linked to the given
+	 * relation.
+	 */
+	conRel = heap_open(ConstraintRelationId, AccessShareLock);
+	if (OidIsValid(relationId))
+	{
+		ScanKeyInit(&key,
+					Anum_pg_constraint_conrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relationId));
+		scan = systable_beginscan(conRel, ConstraintRelidIndexId,
+								  true, NULL, 1, &key);
+	}
+	else
+	{
+		Assert(OidIsValid(domainId));
+		ScanKeyInit(&key,
+					Anum_pg_constraint_contypid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(domainId));
+		scan = systable_beginscan(conRel, ConstraintTypidIndexId,
+								  true, NULL, 1, &key);
+	}
+
+	/*
+	 * 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:
+				continue;	/* not here */
+			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("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, new_object_object(tmp));
+	}
+
+	systable_endscan(scan);
+	heap_close(conRel, AccessShareLock);
+
+	return elements;
+}
+
+/*
+ * deparse the ON COMMMIT ... clause for CREATE ... TEMPORARY ...
+ */
+static ObjTree *
+deparse_OnCommitClause(OnCommitAction option)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0);
+	switch (option)
+	{
+		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;
+	}
+
+	return tmp;
+}
+
+/*
+ * Deparse DefElems, as used e.g. by ALTER COLUMN ... SET, into a list of SET
+ * (...)  or RESET (...) contents.
+ */
+static ObjTree *
+deparse_DefElem(DefElem *elem, bool is_reset)
+{
+	ObjTree	   *set;
+	ObjTree	   *optname;
+
+	if (elem->defnamespace != NULL)
+		optname = new_objtree_VA("%{schema}I.%{label}I", 1,
+								 "schema", ObjTypeString, elem->defnamespace);
+	else
+		optname = new_objtree_VA("%{label}I", 0);
+
+	append_string_object(optname, "label", elem->defname);
+
+	if (is_reset)
+		set = new_objtree_VA("%{label}s", 0);
+	else
+		set = new_objtree_VA("%{label}s = %{value}L", 1,
+							 "value", ObjTypeString,
+							 elem->arg ? defGetString(elem) :
+							 defGetBoolean(elem) ? "TRUE" : "FALSE");
+
+	append_object_object(set, "label", optname);
+	return set;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ */
+static ObjTree *
+deparse_ColumnSetOptions(AlterTableCmd *subcmd)
+{
+	List	   *sets = NIL;
+	ListCell   *cell;
+	ObjTree    *tmp;
+	bool		is_reset = subcmd->subtype == AT_ResetOptions;
+
+	if (is_reset)
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I RESET (%{options:, }s)", 0);
+	else
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I SET (%{options:, }s)", 0);
+
+	append_string_object(tmp, "column", subcmd->name);
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem	   *elem;
+		ObjTree	   *set;
+
+		elem = (DefElem *) lfirst(cell);
+		set = deparse_DefElem(elem, is_reset);
+		sets = lappend(sets, new_object_object(set));
+	}
+
+	append_array_object(tmp, "options", sets);
+
+	return tmp;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ */
+static ObjTree *
+deparse_RelSetOptions(AlterTableCmd *subcmd)
+{
+	List	   *sets = NIL;
+	ListCell   *cell;
+	ObjTree    *tmp;
+	bool		is_reset = subcmd->subtype == AT_ResetRelOptions;
+
+	if (is_reset)
+		tmp = new_objtree_VA("RESET (%{options:, }s)", 0);
+	else
+		tmp = new_objtree_VA("SET (%{options:, }s)", 0);
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem	   *elem;
+		ObjTree	   *set;
+
+		elem = (DefElem *) lfirst(cell);
+		set = deparse_DefElem(elem, is_reset);
+		sets = lappend(sets, new_object_object(set));
+	}
+
+	append_array_object(tmp, "options", sets);
+
+	return tmp;
+}
+
+/*
+ * deparse_CreateStmt
+ *		Deparse a CreateStmt (CREATE TABLE)
+ *
+ * Given a table OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *list;
+	ListCell   *cell;
+	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 %{if_not_exists}s %{identity}D "
+			"OF %{of_type}T %{table_elements}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) %{inherits}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+
+	createStmt =
+		new_objtree_VA(fmtstr, 1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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(relation->rd_rel->reloftype, -1);
+		append_object_object(createStmt, "of_type", tmp);
+
+		tableelts = deparseTableElements(relation, node->tableElts, dpcontext,
+										 true,		/* typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+		if (tableelts == NIL)
+			tmp = new_objtree_VA("", 1,
+								 "present", ObjTypeBool, false);
+		else
+			tmp = new_objtree_VA("(%{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(relation, node->tableElts, dpcontext,
+										 false,		/* not typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+		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("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(RelationRelationId,
+													 formInh->inhparent);
+				parents = lappend(parents, new_object_object(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("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);
+
+	append_object_object(createStmt, "on_commit",
+						 deparse_OnCommitClause(node->oncommit));
+
+	/*
+	 * WITH clause.  We always emit one, containing at least the OIDS option.
+	 * That way we don't depend on the default value for default_with_oids.
+	 * We can skip emitting other options if there don't appear in the parse
+	 * node.
+	 */
+	tmp = new_objtree_VA("oids=%{value}s", 2,
+						 "option", ObjTypeString, "oids",
+						 "value", ObjTypeString,
+						 relation->rd_rel->relhasoids ? "ON" : "OFF");
+	list = list_make1(new_object_object(tmp));
+
+	foreach(cell, node->options)
+	{
+		DefElem	*opt = (DefElem *) lfirst(cell);
+
+		/* already handled above */
+		if (strcmp(opt->defname, "oids") == 0)
+			continue;
+
+		tmp = deparse_DefElem(opt, false);
+		list = lappend(list, new_object_object(tmp));
+	}
+	append_array_object(createStmt, "with", list);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
+static ObjTree *
+deparse_CreateForeignTableStmt(Oid objectId, Node *parsetree)
+{
+	CreateForeignTableStmt *stmt = (CreateForeignTableStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *tableelts = NIL;
+
+	createStmt = new_objtree_VA(
+			"CREATE FOREIGN TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) SERVER %{server}I "
+			"%{generic_options}s", 0);
+
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(createStmt, "identity", tmp);
+
+	append_string_object(createStmt, "if_not_exists",
+						 stmt->base.if_not_exists ? "IF NOT EXISTS" : "");
+
+	append_string_object(createStmt, "server", stmt->servername);
+
+	dpcontext = deparse_context_for(RelationGetRelationName(relation),
+									objectId);
+
+	tableelts = deparseTableElements(relation, stmt->base.tableElts, dpcontext,
+									 false,		/* not typed table */
+									 false);	/* not composite */
+	tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+	append_array_object(createStmt, "table_elements", tableelts);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(stmt->options, NULL));
+
+	relation_close(relation, AccessShareLock);
+	return createStmt;
+}
+
+
+/*
+ * deparse_CompositeTypeStmt
+ *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
+{
+	CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree;
+	ObjTree	   *composite;
+	HeapTuple	typtup;
+	Form_pg_type typform;
+	Relation	typerel;
+	List	   *dpcontext;
+	List	   *tableelts = NIL;
+
+	/* Find the pg_type entry and open the corresponding relation */
+	typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(typtup))
+		elog(ERROR, "cache lookup failed for type %u", objectId);
+	typform = (Form_pg_type) GETSTRUCT(typtup);
+	typerel = relation_open(typform->typrelid, AccessShareLock);
+
+	dpcontext = deparse_context_for(RelationGetRelationName(typerel),
+									RelationGetRelid(typerel));
+
+	composite = new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)",
+							   0);
+	append_object_object(composite, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+
+	tableelts = deparseTableElements(typerel, node->coldeflist, dpcontext,
+									 false,		/* not typed */
+									 true);		/* composite type */
+
+	append_array_object(composite, "columns", tableelts);
+
+	heap_close(typerel, AccessShareLock);
+	ReleaseSysCache(typtup);
+
+	return composite;
+}
+
+/*
+ * deparse_CreateEnumStmt
+ *		Deparse a CreateEnumStmt (CREATE TYPE AS ENUM)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
+{
+	CreateEnumStmt *node = (CreateEnumStmt *) parsetree;
+	ObjTree	   *enumtype;
+	List	   *values;
+	ListCell   *cell;
+
+	enumtype = new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)",
+							  0);
+	append_object_object(enumtype, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	values = NIL;
+	foreach(cell, node->vals)
+	{
+		Value   *val = (Value *) lfirst(cell);
+
+		values = lappend(values, new_string_object(strVal(val)));
+	}
+	append_array_object(enumtype, "values", values);
+
+	return enumtype;
+}
+
+static ObjTree *
+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;
+
+	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(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(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(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(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(tmp));
+	}
+
+	append_array_object(range, "definition", definition);
+
+	systable_endscan(scan);
+	heap_close(pg_range, RowExclusiveLock);
+
+	return range;
+}
+
+static ObjTree *
+deparse_CreatePLangStmt(Oid objectId, Node *parsetree)
+{
+	CreatePLangStmt *node = (CreatePLangStmt *) parsetree;
+	ObjTree	   *createLang;
+	ObjTree	   *tmp;
+	HeapTuple	langTup;
+	Form_pg_language langForm;
+
+
+	langTup = SearchSysCache1(LANGOID,
+							  ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(langTup))
+		elog(ERROR, "cache lookup failed for language %u", objectId);
+	langForm = (Form_pg_language) GETSTRUCT(langTup);
+
+	if (node->plhandler == NIL)
+		createLang =
+			new_objtree_VA("CREATE %{or_replace}s %{trusted}s PROCEDURAL LANGUAGE %{identity}I", 0);
+	else
+		createLang =
+			new_objtree_VA("CREATE %{or_replace}s %{trusted}s PROCEDURAL LANGUAGE %{identity}I "
+						   "HANDLER %{handler}D %{inline}s %{validator}s", 0);
+
+	append_string_object(createLang, "or_replace",
+						 node->replace ? "OR REPLACE" : "");
+	append_string_object(createLang, "identity", node->plname);
+	append_string_object(createLang, "trusted",
+						 langForm->lanpltrusted ? "TRUSTED" : "");
+
+	if (node->plhandler != NIL)
+	{
+		/* Add the HANDLER clause */
+		append_object_object(createLang, "handler",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 langForm->lanplcallfoid));
+
+		/* Add the INLINE clause, if any */
+		tmp = new_objtree_VA("INLINE %{handler_name}D", 0);
+		if (OidIsValid(langForm->laninline))
+		{
+			append_object_object(tmp, "handler_name",
+								 new_objtree_for_qualname_id(ProcedureRelationId,
+															 langForm->laninline));
+		}
+		else
+			append_bool_object(tmp, "present", false);
+		append_object_object(createLang, "inline", tmp);
+
+		/* Add the VALIDATOR clause, if any */
+		tmp = new_objtree_VA("VALIDATOR %{handler_name}D", 0);
+		if (OidIsValid(langForm->lanvalidator))
+		{
+			append_object_object(tmp, "handler_name",
+								 new_objtree_for_qualname_id(ProcedureRelationId,
+															 langForm->lanvalidator));
+		}
+		else
+			append_bool_object(tmp, "present", false);
+		append_object_object(createLang, "validator", tmp);
+	}
+
+	ReleaseSysCache(langTup);
+
+	return createLang;
+}
+
+static ObjTree *
+deparse_CreateDomain(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *createDomain;
+	ObjTree	   *tmp;
+	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}T %{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_type(typForm->typbasetype, typForm->typtypmod));
+
+	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);
+
+	return createDomain;
+}
+
+static ObjTree *
+deparse_FunctionSet(VariableSetKind kind, char *name, char *value)
+{
+	ObjTree	   *r;
+
+	if (kind == VAR_RESET_ALL)
+	{
+		r = new_objtree_VA("RESET ALL", 0);
+	}
+	else if (value != NULL)
+	{
+		/*
+		 * Some GUC variable names are 'LIST' type and hence must not be
+		 * quoted. FIXME: shouldn't this and pg_get_functiondef() rather use
+		 * guc.c to check for GUC_LIST?
+		 */
+		if (pg_strcasecmp(name, "DateStyle") == 0
+			|| pg_strcasecmp(name, "search_path") == 0)
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}s", 0);
+		else
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}L", 0);
+
+		append_string_object(r, "set_name", name);
+		append_string_object(r, "set_value", value);
+	}
+	else
+	{
+		r = new_objtree_VA("RESET %{set_name}I", 0);
+		append_string_object(r, "set_name", name);
+	}
+
+	return r;
+}
+
+/*
+ * 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.
+ */
+static ObjTree *
+deparse_CreateFunction(Oid objectId, Node *parsetree)
+{
+	CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree;
+	ObjTree	   *createFunc;
+	ObjTree	   *sign;
+	ObjTree	   *tmp;
+	Datum		tmpdatum;
+	char	   *fmt;
+	char	   *definition;
+	char	   *source;
+	char	   *probin;
+	List	   *params;
+	List	   *defaults;
+	List	   *sets = NIL;
+	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)
+			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(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(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}n", 1,
+										"cost", ObjTypeFloat,
+										procForm->procost));
+
+	tmp = new_objtree_VA("ROWS %{rows}n", 0);
+	if (procForm->prorows == 0)
+		append_bool_object(tmp, "present", false);
+	else
+		append_float_object(tmp, "rows", procForm->prorows);
+	append_object_object(createFunc, "rows", tmp);
+
+	foreach(cell, node->options)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+			sets = lappend(sets, new_object_object(tmp));
+		}
+	}
+	append_array_object(createFunc, "set_options", sets);
+
+	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);
+
+	return createFunc;
+}
+
+/*
+ * deparse_AlterFunctionStmt
+ *		Deparse a AlterFunctionStmt (ALTER FUNCTION)
+ *
+ * Given a function OID and the parsetree that created it, return the JSON
+ * blob representing the alter command.
+ */
+static ObjTree *
+deparse_AlterFunction(Oid objectId, Node *parsetree)
+{
+	AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree;
+	ObjTree	   *alterFunc;
+	ObjTree	   *sign;
+	HeapTuple	procTup;
+	Form_pg_proc procForm;
+	List	   *params;
+	List	   *elems = NIL;
+	ListCell   *cell;
+	int			i;
+
+	/* 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);
+
+	alterFunc = new_objtree_VA("ALTER FUNCTION %{signature}s %{definition: }s", 0);
+
+	sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0);
+
+	params = NIL;
+
+	/*
+	 * ALTER FUNCTION does not change signature so we can use catalog
+	 * to get input type Oids.
+	 */
+	for (i = 0; i < procForm->pronargs; i++)
+	{
+		ObjTree	   *tmp = new_objtree_VA("%{type}T", 0);
+
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(procForm->proargtypes.values[i], -1));
+		params = lappend(params, new_object_object(tmp));
+	}
+
+	append_array_object(sign, "arguments", params);
+	append_object_object(sign, "identity",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 objectId));
+	append_object_object(alterFunc, "signature", sign);
+
+	foreach(cell, node->actions)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "volatility") == 0)
+		{
+			tmp = new_objtree_VA(strVal(defel->arg), 0);
+		}
+		else if (strcmp(defel->defname, "strict") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "RETURNS NULL ON NULL INPUT" :
+								 "CALLED ON NULL INPUT", 0);
+		}
+		else if (strcmp(defel->defname, "security") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "SECURITY DEFINER" : "SECURITY INVOKER", 0);
+		}
+		else if (strcmp(defel->defname, "leakproof") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "LEAKPROOF" : "NOT LEAKPROOF", 0);
+		}
+		else if (strcmp(defel->defname, "cost") == 0)
+		{
+			tmp = new_objtree_VA("COST %{cost}n", 1,
+								 "cost", ObjTypeFloat,
+								 defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "rows") == 0)
+		{
+			tmp = new_objtree_VA("ROWS %{rows}n", 0);
+			if (defGetNumeric(defel) == 0)
+				append_bool_object(tmp, "present", false);
+			else
+				append_float_object(tmp, "rows",
+									defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+		}
+
+		elems = lappend(elems, new_object_object(tmp));
+	}
+
+	append_array_object(alterFunc, "definition", elems);
+
+	ReleaseSysCache(procTup);
+
+	return alterFunc;
+}
+
+/*
+ * Return the given object type as a string.
+ */
+static const char *
+stringify_objtype(ObjectType objtype)
+{
+	switch (objtype)
+	{
+		case OBJECT_AGGREGATE:
+			return "AGGREGATE";
+		case OBJECT_CAST:
+			return "CAST";
+		case OBJECT_COLUMN:
+			return "COLUMN";
+		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 "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_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_USER_MAPPING:
+			return "USER MAPPING";
+		case OBJECT_VIEW:
+			return "VIEW";
+
+		default:
+			elog(ERROR, "unsupported object type %d", objtype);
+	}
+}
+
+static ObjTree *
+deparse_RenameStmt(ObjectAddress address, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree	   *renameStmt;
+	char	   *fmtstr;
+	Relation	relation;
+	Oid			schemaId;
+
+	/*
+	 * 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(address.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_ATTRIBUTE:
+		case OBJECT_COLUMN:
+			relation = relation_open(address.objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			if (node->renameType == OBJECT_ATTRIBUTE)
+			{
+				fmtstr = "ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I TO %{newname}I %{cascade}s";
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_object_object(renameStmt, "cascade",
+									 new_objtree_VA("CASCADE", 1,
+													"present", ObjTypeBool,
+													node->behavior == DROP_CASCADE));
+			}
+			else
+			{
+				fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME COLUMN %%{colname}I TO %%{newname}I",
+								  stringify_objtype(node->relationType));
+				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);
+
+			/* composite types do not support IF EXISTS */
+			if (node->relationType != OBJECT_TYPE)
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+
+			break;
+
+		case OBJECT_SCHEMA:
+			{
+				renameStmt =
+					new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I",
+								   0);
+				append_string_object(renameStmt, "identity", node->subname);
+			}
+			break;
+
+		case OBJECT_FDW:
+		case OBJECT_LANGUAGE:
+		case OBJECT_FOREIGN_SERVER:
+			{
+				fmtstr = psprintf("ALTER %s %%{identity}I RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_string_object(renameStmt, "identity",
+									 strVal(linitial(node->object)));
+			}
+			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:
+			{
+				HeapTuple	objTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				AttrNumber	Anum_namespace;
+				ObjTree	   *ident;
+
+				/* obtain object tuple */
+				catalog = relation_open(address.classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, address.objectId);
+
+				/* obtain namespace */
+				Anum_namespace = get_object_attnum_namespace(address.classId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				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(DatumGetObjectId(objnsp),
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+
+				relation_close(catalog, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_OPCLASS:
+		case OBJECT_OPFAMILY:
+			{
+				HeapTuple	objTup;
+				HeapTuple	amTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				AttrNumber	Anum_namespace;
+				Oid			amoid;
+				ObjTree	   *ident;
+
+				/* obtain object tuple */
+				catalog = relation_open(address.classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, address.objectId);
+
+				/* obtain namespace */
+				Anum_namespace = get_object_attnum_namespace(address.classId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				/* obtain AM tuple */
+				if (node->renameType == OBJECT_OPCLASS)
+					amoid = ((Form_pg_opclass) GETSTRUCT(objTup))->opcmethod;
+				else
+					amoid = ((Form_pg_opfamily) GETSTRUCT(objTup))->opfmethod;
+				amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+				if (!HeapTupleIsValid(amTup))
+					elog(ERROR, "cache lookup failed for access method %u", amoid);
+
+
+				fmtstr = psprintf("ALTER %s %%{identity}D USING %%{amname}I RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+
+				/* add the identity clauses */
+				ident = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+				append_string_object(renameStmt, "amname",
+									 pstrdup(NameStr(((Form_pg_am) GETSTRUCT(amTup))->amname)));
+
+				ReleaseSysCache(amTup);
+				relation_close(catalog, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			{
+				char	*ident;
+				HeapTuple proctup;
+				Form_pg_proc procform;
+
+				proctup = SearchSysCache1(PROCOID,
+										  ObjectIdGetDatum(address.objectId));
+				if (!HeapTupleIsValid(proctup))
+					elog(ERROR, "cache lookup failed for procedure %u",
+						 address.objectId);
+				procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+				/* XXX does this work for ordered-set aggregates? */
+				ident = psprintf("%s%s",
+								 quote_qualified_identifier(get_namespace_name(procform->pronamespace),
+															strVal(llast(node->object))),
+								 format_procedure_args(address.objectId, true));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 1,
+											"identity", ObjTypeString, ident);
+
+				ReleaseSysCache(proctup);
+			}
+			break;
+
+		case OBJECT_TABCONSTRAINT:
+		case OBJECT_DOMCONSTRAINT:
+			{
+				HeapTuple		conTup;
+				Form_pg_constraint	constrForm;
+				ObjTree		   *ident;
+
+				conTup = SearchSysCache1(CONSTROID, address.objectId);
+				constrForm = (Form_pg_constraint) GETSTRUCT(conTup);
+
+				if (node->renameType == OBJECT_TABCONSTRAINT)
+				{
+					fmtstr = "ALTER TABLE %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(RelationRelationId,
+														constrForm->conrelid);
+				}
+				else
+				{
+					fmtstr = "ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(TypeRelationId,
+														constrForm->contypid);
+				}
+				renameStmt = new_objtree_VA(fmtstr, 2,
+											"conname", ObjTypeString, node->subname,
+											"identity", ObjTypeObject, ident);
+				ReleaseSysCache(conTup);
+			}
+			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, address.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, address.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;
+
+		case OBJECT_POLICY:
+			{
+				HeapTuple	polTup;
+				Form_pg_policy polForm;
+				Relation	pg_policy;
+				ScanKeyData	key;
+				SysScanDesc	scan;
+
+				pg_policy = relation_open(PolicyRelationId, AccessShareLock);
+				ScanKeyInit(&key, ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(address.objectId));
+				scan = systable_beginscan(pg_policy, PolicyOidIndexId, true,
+										  NULL, 1, &key);
+				polTup = systable_getnext(scan);
+				if (!HeapTupleIsValid(polTup))
+					elog(ERROR, "cache lookup failed for policy %u", address.objectId);
+				polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+				renameStmt = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I on %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "policyname", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 polForm->polrelid));
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+				systable_endscan(scan);
+				relation_close(pg_policy, AccessShareLock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	append_string_object(renameStmt, "newname", node->newname);
+
+	return renameStmt;
+}
+
+/*
+ * deparse an ALTER ... SET SCHEMA command.
+ */
+static ObjTree *
+deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree,
+							  ObjectAddress oldschema)
+{
+	AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree;
+	ObjTree	   *alterStmt;
+	char	   *fmt;
+	char	   *identity;
+	char	   *newschema;
+	char	   *oldschname;
+	char	   *ident;
+
+	newschema = node->newschema;
+
+	fmt = psprintf("ALTER %s %%{identity}s SET SCHEMA %%{newschema}I",
+				   stringify_objtype(node->objectType));
+	alterStmt = new_objtree_VA(fmt, 0);
+	append_string_object(alterStmt, "newschema", newschema);
+
+	/*
+	 * Since the command has already taken place from the point of view of
+	 * catalogs, getObjectIdentity returns the object name with the already
+	 * changed schema.  The output of our deparsing must return the original
+	 * schema name however, so we chop the schema name off the identity string
+	 * and then prepend the quoted schema name.
+	 *
+	 * XXX This is pretty clunky. Can we do better?
+	 */
+	identity = getObjectIdentity(&address);
+	oldschname = get_namespace_name(oldschema.objectId);
+	if (!oldschname)
+		elog(ERROR, "cache lookup failed for schema with OID %u", oldschema.objectId);
+	ident = psprintf("%s%s",
+					 quote_identifier(oldschname),
+					 identity + strlen(quote_identifier(newschema)));
+	append_string_object(alterStmt, "identity", ident);
+
+	return alterStmt;
+}
+
+static ObjTree *
+deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+	ObjTree	   *ownerStmt;
+	char	   *fmt;
+
+	fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newowner}I",
+				   stringify_objtype(node->objectType));
+	ownerStmt = new_objtree_VA(fmt, 0);
+	append_string_object(ownerStmt, "newowner",
+						 get_rolespec_name(node->newowner));
+
+	append_string_object(ownerStmt, "identity",
+						 getObjectIdentity(&address));
+
+	return ownerStmt;
+}
+
+static inline ObjElem *
+deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->cache_value);
+	tmp = new_objtree_VA("CACHE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "cache",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Cycle(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA("%{no}s CYCLE",
+						 2,
+						 "clause", ObjTypeString, "cycle",
+						 "no", ObjTypeString,
+						 seqdata->is_cycled ? "" : "NO");
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_IncrementBy(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->increment_by);
+	tmp = new_objtree_VA("INCREMENT BY %{value}s",
+						 2,
+						 "clause", ObjTypeString, "increment_by",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Minvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->min_value);
+	tmp = new_objtree_VA("MINVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "minvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Maxvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->max_value);
+	tmp = new_objtree_VA("MAXVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "maxvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Startwith(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->start_value);
+	tmp = new_objtree_VA("START WITH %{value}s",
+						 2,
+						 "clause", ObjTypeString, "start",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Restart(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->last_value);
+	tmp = new_objtree_VA("RESTART %{value}s",
+						 2,
+						 "clause", ObjTypeString, "restart",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static ObjElem *
+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(RelationRelationId, ownerId);
+		append_string_object(tmp, "attrname", colname);
+		ownedby = new_objtree_VA("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("OWNED BY %{owner}D",
+								 3,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeNull,
+								 "present", ObjTypeBool, false);
+	return new_object_object(ownedby);
+}
+
+/*
+ * deparse_CreateSeqStmt
+ *		deparse a CreateSeqStmt
+ *
+ * Given a sequence OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *createSeq;
+	ObjTree    *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+
+	seqdata = get_sequence_values(objectId);
+
+	createSeq =
+		new_objtree_VA("CREATE %{persistence}s SEQUENCE %{identity}D "
+					   "%{definition: }s",
+					   1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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);
+
+	relation_close(relation, AccessShareLock);
+
+	return createSeq;
+}
+
+/*
+ * deparse_AlterSeqStmt
+ *		deparse an AlterSeqStmt
+ *
+ * Given a sequence OID and a parsetree that modified it, return an ObjTree
+ * representing the alter command.
+ */
+static ObjTree *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *alterSeq;
+	ObjTree	   *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+	ListCell   *cell;
+
+	seqdata = get_sequence_values(objectId);
+
+	alterSeq =
+		new_objtree_VA("ALTER SEQUENCE %{identity}D %{definition: }s", 0);
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(alterSeq, "identity", tmp);
+
+	foreach(cell, ((AlterSeqStmt *) parsetree)->options)
+	{
+		DefElem *elem = (DefElem *) lfirst(cell);
+		ObjElem *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") == 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);
+
+	relation_close(relation, AccessShareLock);
+
+	return alterSeq;
+}
+
+/*
+ * deparse_IndexStmt
+ *		deparse an IndexStmt
+ *
+ * Given an index OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ */
+static ObjTree *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+	IndexStmt  *node = (IndexStmt *) parsetree;
+	ObjTree    *indexStmt;
+	ObjTree    *tmp;
+	Relation	idxrel;
+	Relation	heaprel;
+	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);
+
+	pg_get_indexdef_detailed(objectId,
+							 &index_am, &definition, &reloptions,
+							 &tablespace, &whereClause);
+
+	indexStmt =
+		new_objtree_VA("CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   6,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "if_not_exists", ObjTypeString, node->if_not_exists ? "IF NOT EXISTS" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(heaprel->rd_rel->relnamespace,
+								   RelationGetRelationName(heaprel));
+	append_object_object(indexStmt, "table", tmp);
+
+	/* reloptions */
+	tmp = new_objtree_VA("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("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("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);
+
+	heap_close(idxrel, AccessShareLock);
+	heap_close(heaprel, AccessShareLock);
+
+	return indexStmt;
+}
+
+static ObjTree *
+deparse_RuleStmt(Oid objectId, Node *parsetree)
+{
+	RuleStmt *node = (RuleStmt *) parsetree;
+	ObjTree	   *ruleStmt;
+	ObjTree	   *tmp;
+	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(action));
+	}
+	append_array_object(ruleStmt, "actions", list);
+
+	systable_endscan(scan);
+	heap_close(pg_rewrite, AccessShareLock);
+
+	return ruleStmt;
+}
+
+static ObjTree *
+deparse_CreateTableAsStmt(Oid objectId, Node *parsetree)
+{
+	CreateTableAsStmt *node = (CreateTableAsStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	ObjTree	   *createStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	char	   *fmt;
+	List	   *list;
+	ListCell   *cell;
+
+	/*
+	 * Reject unsupported case right away.
+	 */
+	if (((Query *) (node->query))->commandType == CMD_UTILITY)
+		elog(ERROR, "unimplemented deparse of CREATE TABLE AS EXECUTE");
+
+	/*
+	 * Note that INSERT INTO is deparsed as CREATE TABLE AS.  They are
+	 * functionally equivalent synonyms so there is no harm from this.
+	 */
+	if (node->relkind == OBJECT_MATVIEW)
+		fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with_clause}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+	else
+		fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with_clause}s %{on_commit}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+
+	createStmt =
+		new_objtree_VA(fmt, 2,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(node->into->rel->relpersistence),
+					   "if_not_exists", ObjTypeString,
+					   node->if_not_exists ? "IF NOT EXISTS" : "");
+	append_object_object(createStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+
+	/* Add an ON COMMIT clause.  CREATE MATERIALIZED VIEW doesn't have one */
+	if (node->relkind == OBJECT_TABLE)
+	{
+		append_object_object(createStmt, "on_commit",
+							 deparse_OnCommitClause(node->into->onCommit));
+	}
+
+	/* add a TABLESPACE clause */
+	tmp = new_objtree_VA("TABLESPACE %{tablespace}I", 0);
+	if (node->into->tableSpaceName)
+		append_string_object(tmp, "tablespace", node->into->tableSpaceName);
+	else
+	{
+		append_null_object(tmp, "tablespace");
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(createStmt, "tablespace", tmp);
+
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->into->skipData ? true : false);
+	append_object_object(createStmt, "with_no_data", tmp);
+
+	/* add the column list, if any */
+	if (node->into->colNames == NIL)
+		tmp = new_objtree_VA("", 1,
+							 "present", ObjTypeBool, false);
+	else
+	{
+		ListCell *cell;
+
+		list = NIL;
+		foreach (cell, node->into->colNames)
+			list = lappend(list, new_string_object(strVal(lfirst(cell))));
+
+		tmp = new_objtree_VA("(%{columns:, }I)", 1,
+							 "columns", ObjTypeArray, list);
+	}
+	append_object_object(createStmt, "columns", tmp);
+
+	/* add the query */
+	Assert(IsA(node->query, Query));
+	append_string_object(createStmt, "query",
+						 pg_get_createtableas_def((Query *) node->query));
+
+
+	/*
+	 * WITH clause.  In CREATE TABLE AS, like for CREATE TABLE, we always emit
+	 * one, containing at least the OIDS option; CREATE MATERIALIZED VIEW
+	 * doesn't support the oids option, so we have to make the clause reduce to
+	 * empty if no other options are specified.
+	 */
+	tmp = new_objtree_VA("WITH (%{with:, }s)", 0);
+	if (node->relkind == OBJECT_MATVIEW && node->into->options == NIL)
+		append_bool_object(tmp, "present", false);
+	else
+	{
+		if (node->relkind == OBJECT_TABLE)
+		{
+			tmp2 = new_objtree_VA("oids=%{value}s", 2,
+								  "option", ObjTypeString, "oids",
+								  "value", ObjTypeString,
+								  relation->rd_rel->relhasoids ? "ON" : "OFF");
+			list = list_make1(new_object_object(tmp2));
+		}
+		else
+			list = NIL;
+
+		foreach (cell, node->into->options)
+		{
+			DefElem	*opt = (DefElem *) lfirst(cell);
+
+			if (strcmp(opt->defname, "oids") == 0)
+				continue;
+
+			tmp2 = deparse_DefElem(opt, false);
+			list = lappend(list, new_object_object(tmp2));
+		}
+		append_array_object(tmp, "with", list);
+	}
+	append_object_object(createStmt, "with_clause", tmp);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
+/*
+ * deparse_CreateSchemaStmt
+ *		deparse a CreateSchemaStmt
+ *
+ * Given a schema OID and the parsetree that created it, return an ObjTree
+ * 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 ObjTree *
+deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
+{
+	CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
+	ObjTree    *createSchema;
+	ObjTree    *auth;
+
+	createSchema =
+		new_objtree_VA("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("AUTHORIZATION %{authorization_role}I", 0);
+	if (node->authrole)
+		append_string_object(auth, "authorization_role",
+							 get_rolespec_name(node->authrole));
+	else
+	{
+		append_null_object(auth, "authorization_role");
+		append_bool_object(auth, "present", false);
+	}
+	append_object_object(createSchema, "authorization", auth);
+
+	return createSchema;
+}
+
+static ObjTree *
+deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
+{
+	AlterEnumStmt *node = (AlterEnumStmt *) parsetree;
+	ObjTree	   *alterEnum;
+	ObjTree	   *tmp;
+
+	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);
+
+	return alterEnum;
+}
+
+/*
+ * Append a NULL-or-quoted-literal clause.  Useful for COMMENT and SECURITY
+ * LABEL.
+ */
+static void
+append_literal_or_null(ObjTree *mainobj, char *elemname, char *value)
+{
+	ObjTree	*top;
+	ObjTree *part;
+
+	top = new_objtree_VA("%{null}s%{literal}s", 0);
+	part = new_objtree_VA("NULL", 1,
+						  "present", ObjTypeBool,
+						  !value);
+	append_object_object(top, "null", part);
+	part = new_objtree_VA("%{value}L", 1,
+						  "present", ObjTypeBool,
+						  !!value);
+	if (value)
+		append_string_object(part, "value", value);
+	append_object_object(top, "literal", part);
+
+	append_object_object(mainobj, elemname, top);
+}
+
+/*
+ * Deparse a CommentStmt when it pertains to a constraint.
+ */
+static ObjTree *
+deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	HeapTuple	constrTup;
+	Form_pg_constraint constrForm;
+	char	   *fmt;
+	ObjectAddress addr;
+
+	Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
+
+	constrTup = SearchSysCache1(CONSTROID, objectId);
+	if (!HeapTupleIsValid(constrTup))
+		elog(ERROR, "cache lookup failed for constraint %u", objectId);
+	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
+
+	if (OidIsValid(constrForm->conrelid))
+		ObjectAddressSet(addr, RelationRelationId, constrForm->conrelid);
+	else
+		ObjectAddressSet(addr, TypeRelationId, constrForm->contypid);
+
+	fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}s",
+				   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	comment = new_objtree_VA(fmt, 0);
+
+	/* Add the comment clause */
+	append_literal_or_null(comment, "comment", node->comment);
+
+	append_string_object(comment, "identity", pstrdup(NameStr(constrForm->conname)));
+
+	append_string_object(comment, "parentobj",
+						 getObjectIdentity(&addr));
+
+	ReleaseSysCache(constrTup);
+
+	return comment;
+}
+
+static ObjTree *
+deparse_CommentStmt(ObjectAddress address, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	char	   *fmt;
+	char	   *identity;
+
+	/*
+	 * Constraints are sufficiently different that it is easier to handle them
+	 * separately.
+	 */
+	if (node->objtype == OBJECT_DOMCONSTRAINT ||
+		node->objtype == OBJECT_TABCONSTRAINT)
+	{
+		Assert(address.classId == ConstraintRelationId);
+		return deparse_CommentOnConstraintSmt(address.objectId, parsetree);
+	}
+
+	fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}s",
+				   stringify_objtype(node->objtype));
+	comment = new_objtree_VA(fmt, 0);
+
+	/* Add the comment clause; can be either NULL or a quoted literal.  */
+	append_literal_or_null(comment, "comment", node->comment);
+
+	/*
+	 * Add the object identity clause.  For zero argument aggregates we need to
+	 * add the (*) bit; in all other cases we can just use getObjectIdentity.
+	 *
+	 * XXX shouldn't we instead fix the object identities for zero-argument
+	 * aggregates?
+	 */
+	if (node->objtype == OBJECT_AGGREGATE)
+	{
+		HeapTuple		procTup;
+		Form_pg_proc	procForm;
+
+		procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(address.objectId));
+		if (!HeapTupleIsValid(procTup))
+			elog(ERROR, "cache lookup failed for procedure %u", address.objectId);
+		procForm = (Form_pg_proc) GETSTRUCT(procTup);
+		if (procForm->pronargs == 0)
+			identity = psprintf("%s(*)",
+								quote_qualified_identifier(get_namespace_name(procForm->pronamespace),
+														   NameStr(procForm->proname)));
+		else
+			identity = getObjectIdentity(&address);
+		ReleaseSysCache(procTup);
+	}
+	else
+		identity = getObjectIdentity(&address);
+
+	append_string_object(comment, "identity", identity);
+
+	return comment;
+}
+
+/*
+ * Add common clauses to CreatePolicy or AlterPolicy deparse objects
+ */
+static void
+add_policy_clauses(ObjTree *policyStmt, Oid policyOid, List *roles,
+				   bool do_qual, bool do_with_check)
+{
+	Relation	polRel = heap_open(PolicyRelationId, AccessShareLock);
+	HeapTuple	polTup = get_catalog_object_by_oid(polRel, policyOid);
+	ObjTree	   *tmp;
+	Form_pg_policy polForm;
+
+	if (!HeapTupleIsValid(polTup))
+		elog(ERROR, "cache lookup failed for policy %u", policyOid);
+
+	polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+	/* add the "ON table" clause */
+	append_object_object(policyStmt, "table",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 polForm->polrelid));
+
+	/*
+	 * Add the "TO role" clause, if any.  In the CREATE case, it always
+	 * contains at least PUBLIC, but in the ALTER case it might be empty.
+	 */
+	tmp = new_objtree_VA("TO %{role:, }R", 0);
+	if (roles)
+	{
+		List   *list = NIL;
+		ListCell *cell;
+
+		foreach (cell, roles)
+		{
+			RoleSpec   *spec = (RoleSpec *) lfirst(cell);
+
+			list = lappend(list,
+						   new_object_object(new_objtree_for_rolespec(spec)));
+		}
+		append_array_object(tmp, "role", list);
+	}
+	else
+	{
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(policyStmt, "to_role", tmp);
+
+	/* add the USING clause, if any */
+	tmp = new_objtree_VA("USING (%{expression}s)", 0);
+	if (do_qual)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polqual expression in policy %u", policyOid);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policyStmt, "using", tmp);
+
+	/* add the WITH CHECK clause, if any */
+	tmp = new_objtree_VA("WITH CHECK (%{expression}s)", 0);
+	if (do_with_check)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polwithcheck expression in policy %u", policyOid);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policyStmt, "with_check", tmp);
+
+	relation_close(polRel, AccessShareLock);
+}
+
+static ObjTree *
+deparse_CreatePolicyStmt(Oid objectId, Node *parsetree)
+{
+	CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree;
+	ObjTree	   *policy;
+
+	policy = new_objtree_VA("CREATE POLICY %{identity}I ON %{table}D %{for_command}s "
+							"%{to_role}s %{using}s %{with_check}s", 2,
+							"identity", ObjTypeString, node->policy_name,
+							"for_command", ObjTypeObject,
+							new_objtree_VA("FOR %{command}s", 1,
+										   "command", ObjTypeString, node->cmd));
+
+	/* add the rest of the stuff */
+	add_policy_clauses(policy, objectId, node->roles, !!node->qual,
+					   !!node->with_check);
+
+	return policy;
+}
+
+static ObjTree *
+deparse_AlterPolicyStmt(Oid objectId, Node *parsetree)
+{
+	AlterPolicyStmt *node = (AlterPolicyStmt *) parsetree;
+	ObjTree	   *policy;
+
+	policy = new_objtree_VA("ALTER POLICY %{identity}I ON %{table}D "
+							"%{to_role}s %{using}s %{with_check}s", 1,
+							"identity", ObjTypeString, node->policy_name);
+
+	/* add the rest of the stuff */
+	add_policy_clauses(policy, objectId, node->roles, !!node->qual,
+					   !!node->with_check);
+
+	return policy;
+}
+
+static ObjTree *
+deparse_SecLabelStmt(ObjectAddress address, Node *parsetree)
+{
+	SecLabelStmt *node = (SecLabelStmt *) parsetree;
+	ObjTree	   *label;
+	char	   *fmt;
+
+	Assert(node->provider);
+
+	fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}s",
+				   stringify_objtype(node->objtype));
+	label = new_objtree_VA(fmt, 0);
+
+	/* Add the label clause; can be either NULL or a quoted literal. */
+	append_literal_or_null(label, "label", node->label);
+
+	/* Add the security provider clause */
+	append_string_object(label, "provider", node->provider);
+
+	/* Add the object identity clause */
+	append_string_object(label, "identity",
+						 getObjectIdentity(&address));
+
+	return label;
+}
+
+static ObjTree *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+	HeapTuple   conTup;
+	Relation	convrel;
+	Form_pg_conversion conForm;
+	ObjTree	   *ccStmt;
+
+	convrel = heap_open(ConversionRelationId, AccessShareLock);
+	conTup = get_catalog_object_by_oid(convrel, 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));
+
+	heap_close(convrel, AccessShareLock);
+
+	return ccStmt;
+}
+
+static ObjTree *
+deparse_CreateCastStmt(Oid objectId, Node *parsetree)
+{
+	CreateCastStmt *node = (CreateCastStmt *) parsetree;
+	Relation	castrel;
+	HeapTuple	castTup;
+	Form_pg_cast castForm;
+	ObjTree	   *createCast;
+	char	   *context;
+
+	castrel = heap_open(CastRelationId, AccessShareLock);
+	castTup = get_catalog_object_by_oid(castrel, objectId);
+	if (!HeapTupleIsValid(castTup))
+		elog(ERROR, "cache lookup failed for cast with OID %u", objectId);
+	castForm = (Form_pg_cast) GETSTRUCT(castTup);
+
+	createCast = new_objtree_VA("CREATE CAST (%{sourcetype}T AS %{targettype}T) %{mechanism}s %{context}s",
+								2, "sourcetype", ObjTypeObject,
+								new_objtree_for_type(castForm->castsource, -1),
+								"targettype", ObjTypeObject,
+								new_objtree_for_type(castForm->casttarget, -1));
+
+	if (node->inout)
+		append_string_object(createCast, "mechanism", "WITH INOUT");
+	else if (node->func == NULL)
+		append_string_object(createCast, "mechanism", "WITHOUT FUNCTION");
+	else
+	{
+		ObjTree	   *tmp;
+		StringInfoData func;
+		HeapTuple	funcTup;
+		Form_pg_proc funcForm;
+		int			i;
+
+		funcTup = SearchSysCache1(PROCOID, castForm->castfunc);
+		funcForm = (Form_pg_proc) GETSTRUCT(funcTup);
+
+		initStringInfo(&func);
+		appendStringInfo(&func, "%s(",
+						quote_qualified_identifier(get_namespace_name(funcForm->pronamespace),
+												   NameStr(funcForm->proname)));
+		for (i = 0; i < funcForm->pronargs; i++)
+			appendStringInfoString(&func,
+								   format_type_be_qualified(funcForm->proargtypes.values[i]));
+		appendStringInfoChar(&func, ')');
+
+		tmp = new_objtree_VA("WITH FUNCTION %{castfunction}s", 1,
+							 "castfunction", ObjTypeString, func.data);
+		append_object_object(createCast, "mechanism", tmp);
+
+		ReleaseSysCache(funcTup);
+	}
+
+	switch (node->context)
+	{
+		case COERCION_IMPLICIT:
+			context = "AS IMPLICIT";
+			break;
+		case COERCION_ASSIGNMENT:
+			context = "AS ASSIGNMENT";
+			break;
+		case COERCION_EXPLICIT:
+			context = "";
+			break;
+		default:
+			elog(ERROR, "invalid coercion code %c", node->context);
+			return NULL;	/* keep compiler quiet */
+	}
+	append_string_object(createCast, "context", context);
+
+	heap_close(castrel, AccessShareLock);
+
+	return createCast;
+}
+
+static ObjTree *
+deparse_CreateOpClassStmt(CollectedCommand *cmd)
+{
+	Oid			opcoid = cmd->d.createopc.address.objectId;
+	HeapTuple   opcTup;
+	HeapTuple   opfTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_opclass opcForm;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	ListCell   *cell;
+
+	opcTup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opcoid));
+	if (!HeapTupleIsValid(opcTup))
+		elog(ERROR, "cache lookup failed for opclass with OID %u", opcoid);
+	opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
+
+	opfTup = SearchSysCache1(OPFAMILYOID, opcForm->opcfamily);
+	if (!HeapTupleIsValid(opfTup))
+		elog(ERROR, "cache lookup failed for operator family with OID %u", opcForm->opcfamily);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+	stmt = new_objtree_VA("CREATE OPERATOR CLASS %{identity}D %{default}s "
+						  "FOR TYPE %{type}T USING %{amname}I %{opfamily}s "
+						  "AS %{items:, }s", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(opcForm->opcnamespace,
+												  NameStr(opcForm->opcname)));
+
+	/*
+	 * Add the FAMILY clause; but if it has the same name and namespace as the
+	 * opclass, then have it expand to empty, because it would cause a failure
+	 * if the opfamily was created internally.
+	 */
+	tmp = new_objtree_VA("FAMILY %{opfamily}D", 1,
+						 "opfamily", ObjTypeObject,
+						 new_objtree_for_qualname(opfForm->opfnamespace,
+												  NameStr(opfForm->opfname)));
+	if (strcmp(NameStr(opfForm->opfname), NameStr(opcForm->opcname)) == 0 &&
+		opfForm->opfnamespace == opcForm->opcnamespace)
+		append_bool_object(tmp, "present", false);
+	append_object_object(stmt, "opfamily",  tmp);
+
+	/* Add the DEFAULT clause */
+	append_string_object(stmt, "default",
+						 opcForm->opcdefault ? "DEFAULT" : "");
+
+	/* Add the FOR TYPE clause */
+	append_object_object(stmt, "type",
+						 new_objtree_for_type(opcForm->opcintype, -1));
+
+	/* Add the USING clause */
+	append_string_object(stmt, "amname", get_am_name(opcForm->opcmethod));
+
+	/*
+	 * Add the initial item list.  Note we always add the STORAGE clause, even
+	 * when it is implicit in the original command.
+	 */
+	tmp = new_objtree_VA("STORAGE %{type}T", 0);
+	append_object_object(tmp, "type",
+						 new_objtree_for_type(opcForm->opckeytype != InvalidOid ?
+											  opcForm->opckeytype : opcForm->opcintype,
+											  -1));
+	list = list_make1(new_object_object(tmp));
+
+	/* Add the declared operators */
+	/* XXX this duplicates code in deparse_AlterOpFamily */
+	foreach(cell, cmd->d.createopc.operators)
+	{
+		OpFamilyMember *oper = lfirst(cell);
+		ObjTree	   *tmp;
+
+		tmp = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s",
+							 0);
+		append_integer_object(tmp, "num", oper->number);
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oper->object));
+		/* add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(oper->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(oper->righttype, -1));
+		/* Add the FOR SEARCH / FOR ORDER BY clause */
+		if (oper->sortfamily == InvalidOid)
+			append_string_object(tmp, "purpose", "FOR SEARCH");
+		else
+		{
+			ObjTree	   *tmp2;
+
+			tmp2 = new_objtree_VA("FOR ORDER BY %{opfamily}D", 0);
+			append_object_object(tmp2, "opfamily",
+								 new_objtree_for_qualname_id(OperatorFamilyRelationId,
+															 oper->sortfamily));
+			append_object_object(tmp, "purpose", tmp2);
+		}
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	/* Add the declared support functions */
+	foreach(cell, cmd->d.createopc.procedures)
+	{
+		OpFamilyMember *proc = lfirst(cell);
+		ObjTree	   *tmp;
+		HeapTuple	procTup;
+		Form_pg_proc procForm;
+		Oid		   *proargtypes;
+		List	   *arglist;
+		int			i;
+
+		tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D(%{argtypes:, }T)", 0);
+		append_integer_object(tmp, "num", proc->number);
+		procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object));
+		if (!HeapTupleIsValid(procTup))
+			elog(ERROR, "cache lookup failed for procedure %u", proc->object);
+		procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+		append_object_object(tmp, "function",
+							 new_objtree_for_qualname(procForm->pronamespace,
+													  NameStr(procForm->proname)));
+		proargtypes = procForm->proargtypes.values;
+		arglist = NIL;
+		for (i = 0; i < procForm->pronargs; i++)
+		{
+			ObjTree	   *arg;
+
+			arg = new_objtree_for_type(proargtypes[i], -1);
+			arglist = lappend(arglist, new_object_object(arg));
+		}
+		append_array_object(tmp, "argtypes", arglist);
+
+		ReleaseSysCache(procTup);
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(proc->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(proc->righttype, -1));
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	append_array_object(stmt, "items", list);
+
+	ReleaseSysCache(opfTup);
+	ReleaseSysCache(opcTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_CreateOpFamily(Oid objectId, Node *parsetree)
+{
+	HeapTuple   opfTup;
+	HeapTuple   amTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_am  amForm;
+	ObjTree	   *copfStmt;
+	ObjTree	   *tmp;
+
+	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}I",
+							  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));
+
+	ReleaseSysCache(amTup);
+	ReleaseSysCache(opfTup);
+
+	return copfStmt;
+}
+
+static ObjTree *
+deparse_GrantStmt(CollectedCommand *cmd)
+{
+	InternalGrant *istmt;
+	ObjTree	   *grantStmt;
+	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 = "FOREIGN 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:, }R %%{grant_option}s",
+					   objtype);
+	else
+		fmt = psprintf("REVOKE %%{grant_option}s %%{privileges:, }s ON %s %%{privtarget:, }s "
+					   "FROM %%{grantees:, }R %%{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(tmp));
+	}
+	else
+	{
+		list = NIL;
+
+		if (istmt->privileges & ACL_INSERT)
+			list = lappend(list, new_string_object("INSERT"));
+		if (istmt->privileges & ACL_SELECT)
+			list = lappend(list, new_string_object("SELECT"));
+		if (istmt->privileges & ACL_UPDATE)
+			list = lappend(list, new_string_object("UPDATE"));
+		if (istmt->privileges & ACL_DELETE)
+			list = lappend(list, new_string_object("DELETE"));
+		if (istmt->privileges & ACL_TRUNCATE)
+			list = lappend(list, new_string_object("TRUNCATE"));
+		if (istmt->privileges & ACL_REFERENCES)
+			list = lappend(list, new_string_object("REFERENCES"));
+		if (istmt->privileges & ACL_TRIGGER)
+			list = lappend(list, new_string_object("TRIGGER"));
+		if (istmt->privileges & ACL_EXECUTE)
+			list = lappend(list, new_string_object("EXECUTE"));
+		if (istmt->privileges & ACL_USAGE)
+			list = lappend(list, new_string_object("USAGE"));
+		if (istmt->privileges & ACL_CREATE)
+			list = lappend(list, new_string_object("CREATE"));
+		if (istmt->privileges & ACL_CREATE_TEMP)
+			list = lappend(list, new_string_object("TEMPORARY"));
+		if (istmt->privileges & ACL_CONNECT)
+			list = lappend(list, new_string_object("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(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(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(tmp));
+	}
+	append_array_object(grantStmt, "privtarget", list);
+
+	/* list of grantees */
+	list = NIL;
+	foreach(cell, istmt->grantees)
+	{
+		Oid		grantee = lfirst_oid(cell);
+
+		tmp = new_objtree_for_role_id(grantee);
+		list = lappend(list, new_object_object(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", "");
+	}
+
+	return grantStmt;
+}
+
+/*
+ * Deparse an ALTER OPERATOR FAMILY ADD/DROP command.
+ */
+static ObjTree *
+deparse_AlterOpFamily(CollectedCommand *cmd)
+{
+	ObjTree	   *alterOpFam;
+	AlterOpFamilyStmt *stmt = (AlterOpFamilyStmt *) cmd->parsetree;
+	HeapTuple	ftp;
+	Form_pg_opfamily opfForm;
+	List	   *list;
+	ListCell   *cell;
+
+	ftp = SearchSysCache1(OPFAMILYOID,
+						  ObjectIdGetDatum(cmd->d.opfam.address.objectId));
+	if (!HeapTupleIsValid(ftp))
+		elog(ERROR, "cache lookup failed for operator family %u",
+			 cmd->d.opfam.address.objectId);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(ftp);
+
+	if (!stmt->isDrop)
+		alterOpFam = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D "
+									"USING %{amname}I ADD %{items:, }s", 0);
+	else
+		alterOpFam = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D "
+									"USING %{amname}I DROP %{items:, }s", 0);
+	append_object_object(alterOpFam, "identity",
+						 new_objtree_for_qualname(opfForm->opfnamespace,
+												  NameStr(opfForm->opfname)));
+	append_string_object(alterOpFam, "amname", stmt->amname);
+
+	list = NIL;
+	foreach(cell, cmd->d.opfam.operators)
+	{
+		OpFamilyMember *oper = lfirst(cell);
+		ObjTree	   *tmp;
+
+		if (!stmt->isDrop)
+			tmp = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s",
+								 0);
+		else
+			tmp = new_objtree_VA("OPERATOR %{num}n (%{ltype}T, %{rtype}T)",
+								 0);
+		append_integer_object(tmp, "num", oper->number);
+
+		/* Add the operator name; the DROP case doesn't have this */
+		if (!stmt->isDrop)
+		{
+			append_object_object(tmp, "operator",
+								 new_objtree_for_qualname_id(OperatorRelationId,
+															 oper->object));
+		}
+
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(oper->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(oper->righttype, -1));
+
+		/* Add the FOR SEARCH / FOR ORDER BY clause; not in the DROP case */
+		if (!stmt->isDrop)
+		{
+			if (oper->sortfamily == InvalidOid)
+				append_string_object(tmp, "purpose", "FOR SEARCH");
+			else
+			{
+				ObjTree	   *tmp2;
+
+				tmp2 = new_objtree_VA("FOR ORDER BY %{opfamily}D", 0);
+				append_object_object(tmp2, "opfamily",
+									 new_objtree_for_qualname_id(OperatorFamilyRelationId,
+																 oper->sortfamily));
+				append_object_object(tmp, "purpose", tmp2);
+			}
+		}
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	foreach(cell, cmd->d.opfam.procedures)
+	{
+		OpFamilyMember *proc = lfirst(cell);
+		ObjTree	   *tmp;
+
+		if (!stmt->isDrop)
+			tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D(%{argtypes:, }T)", 0);
+		else
+			tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T)", 0);
+		append_integer_object(tmp, "num", proc->number);
+
+		/* Add the function name and arg types; the DROP case doesn't have this */
+		if (!stmt->isDrop)
+		{
+			HeapTuple	procTup;
+			Form_pg_proc procForm;
+			Oid		   *proargtypes;
+			List	   *arglist;
+			int			i;
+
+			procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object));
+			if (!HeapTupleIsValid(procTup))
+				elog(ERROR, "cache lookup failed for procedure %u", proc->object);
+			procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+			append_object_object(tmp, "function",
+								 new_objtree_for_qualname(procForm->pronamespace,
+														  NameStr(procForm->proname)));
+			proargtypes = procForm->proargtypes.values;
+			arglist = NIL;
+			for (i = 0; i < procForm->pronargs; i++)
+			{
+				ObjTree	   *arg;
+
+				arg = new_objtree_for_type(proargtypes[i], -1);
+				arglist = lappend(arglist, new_object_object(arg));
+			}
+			append_array_object(tmp, "argtypes", arglist);
+
+			ReleaseSysCache(procTup);
+		}
+
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(proc->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(proc->righttype, -1));
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	append_array_object(alterOpFam, "items", list);
+
+	ReleaseSysCache(ftp);
+
+	return alterOpFam;
+}
+
+static ObjTree *
+deparse_AlterDefaultPrivilegesStmt(CollectedCommand *cmd)
+{
+	ObjTree	   *alterStmt;
+	AlterDefaultPrivilegesStmt *stmt = (AlterDefaultPrivilegesStmt *) cmd->parsetree;
+	List	   *roles = NIL;
+	List	   *schemas = NIL;
+	List	   *grantees;
+	List	   *privs;
+	ListCell   *cell;
+	ObjTree	   *tmp;
+	ObjTree	   *grant;
+
+	alterStmt = new_objtree_VA("ALTER DEFAULT PRIVILEGES %{in_schema}s "
+							   "%{for_roles}s %{grant}s", 0);
+
+	/* Scan the parse node to dig out the FOR ROLE and IN SCHEMA clauses */
+	foreach(cell, stmt->options)
+	{
+		DefElem	   *opt = (DefElem *) lfirst(cell);
+		ListCell   *cell2;
+
+		Assert(IsA(opt, DefElem));
+		Assert(IsA(opt->arg, List));
+		if (strcmp(opt->defname, "roles") == 0)
+		{
+			foreach(cell2, (List *) opt->arg)
+			{
+				Value  *val = lfirst(cell2);
+				ObjTree *obj = new_objtree_for_role(strVal(val));
+
+				roles = lappend(roles, new_object_object(obj));
+			}
+		}
+		else if (strcmp(opt->defname, "schemas") == 0)
+		{
+			foreach(cell2, (List *) opt->arg)
+			{
+				Value  *val = lfirst(cell2);
+
+				schemas = lappend(schemas,
+								  new_string_object(strVal(val)));
+			}
+		}
+	}
+
+	/* Add the FOR ROLE clause, if any */
+	tmp = new_objtree_VA("FOR ROLE %{roles:, }R", 0);
+	append_array_object(tmp, "roles", roles);
+	if (roles == NIL)
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterStmt, "for_roles", tmp);
+
+	/* Add the IN SCHEMA clause, if any */
+	tmp = new_objtree_VA("IN SCHEMA %{schemas:, }I", 0);
+	append_array_object(tmp, "schemas", schemas);
+	if (schemas == NIL)
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterStmt, "in_schema", tmp);
+
+	/* Add the GRANT subcommand */
+	if (stmt->action->is_grant)
+		grant = new_objtree_VA("GRANT %{privileges:, }s ON %{target}s "
+							   "TO %{grantees:, }R %{grant_option}s", 0);
+	else
+		grant = new_objtree_VA("REVOKE %{grant_option}s %{privileges:, }s "
+							   "ON %{target}s FROM %{grantees:, }R", 0);
+
+	/* add the GRANT OPTION clause */
+	tmp = new_objtree_VA(stmt->action->is_grant ?
+						   "WITH GRANT OPTION" : "GRANT OPTION FOR",
+						   1, "present", ObjTypeBool,
+						   stmt->action->grant_option);
+	append_object_object(grant, "grant_option", tmp);
+
+	/* add the target object type */
+	append_string_object(grant, "target", cmd->d.defprivs.objtype);
+
+	/* add the grantee list */
+	grantees = NIL;
+	foreach(cell, stmt->action->grantees)
+	{
+		RoleSpec   *spec = (RoleSpec *) lfirst(cell);
+		ObjTree	   *obj = new_objtree_for_rolespec(spec);
+
+		grantees = lappend(grantees, new_object_object(obj));
+	}
+	append_array_object(grant, "grantees", grantees);
+
+	/*
+	 * Add the privileges list.  This uses the parser struct, as opposed to the
+	 * InternalGrant format used by GRANT.  There are enough other differences
+	 * that this doesn't seem worth improving.
+	 */
+	if (stmt->action->privileges == NIL)
+		privs = list_make1(new_string_object("ALL PRIVILEGES"));
+	else
+	{
+		privs = NIL;
+
+		foreach(cell, stmt->action->privileges)
+		{
+			AccessPriv *priv = lfirst(cell);
+
+			Assert(priv->cols == NIL);
+			privs = lappend(privs,
+							new_string_object(priv->priv_name));
+		}
+	}
+
+	append_array_object(grant, "privileges", privs);
+
+	append_object_object(alterStmt, "grant", grant);
+
+	return alterStmt;
+}
+
+static ObjTree *
+deparse_AlterTableStmt(CollectedCommand *cmd)
+{
+	ObjTree	   *alterTableStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	List	   *dpcontext;
+	Relation	rel;
+	List	   *subcmds = NIL;
+	ListCell   *cell;
+	char	   *fmtstr;
+	const char *reltype;
+	bool		istype = false;
+
+	Assert(cmd->type == SCT_AlterTable);
+
+	rel = relation_open(cmd->d.alterTable.objectId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									cmd->d.alterTable.objectId);
+
+	switch (rel->rd_rel->relkind)
+	{
+		case RELKIND_RELATION:
+			reltype = "TABLE";
+			break;
+		case RELKIND_INDEX:
+			reltype = "INDEX";
+			break;
+		case RELKIND_VIEW:
+			reltype = "VIEW";
+			break;
+		case RELKIND_COMPOSITE_TYPE:
+			reltype = "TYPE";
+			istype = true;
+			break;
+		case RELKIND_FOREIGN_TABLE:
+			reltype = "FOREIGN TABLE";
+			break;
+
+		default:
+			elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind);
+			reltype = NULL;;
+	}
+
+	fmtstr = psprintf("ALTER %s %%{identity}D %%{subcmds:, }s", reltype);
+	alterTableStmt = new_objtree_VA(fmtstr, 0);
+
+	tmp = new_objtree_for_qualname(rel->rd_rel->relnamespace,
+								   RelationGetRelationName(rel));
+	append_object_object(alterTableStmt, "identity", tmp);
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
+		AlterTableCmd	*subcmd = (AlterTableCmd *) sub->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, true);
+				fmtstr = psprintf("ADD %s %%{definition}s",
+								  istype ? "ATTRIBUTE" : "COLUMN");
+				tmp = new_objtree_VA(fmtstr, 2,
+									 "type", ObjTypeString, "add column",
+									 "definition", ObjTypeObject, tree);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			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(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(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(tmp));
+				break;
+
+			case AT_SetStatistics:
+				{
+					Assert(IsA(subcmd->def, Integer));
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n",
+										 3, "type", ObjTypeString, "set statistics",
+										 "column", ObjTypeString, subcmd->name,
+										 "statistics", ObjTypeInteger,
+										 intVal((Value *) subcmd->def));
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_SetOptions:
+			case AT_ResetOptions:
+				subcmds = lappend(subcmds, new_object_object(
+									  deparse_ColumnSetOptions(subcmd)));
+				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(tmp));
+				break;
+
+			case AT_DropColumnRecurse:
+			case AT_DropColumn:
+				fmtstr = psprintf("DROP %s %%{column}I %%{cascade}s",
+								  istype ? "ATTRIBUTE" : "COLUMN");
+				tmp = new_objtree_VA(fmtstr, 2,
+									 "type", ObjTypeString, "drop column",
+									 "column", ObjTypeString, subcmd->name);
+				tmp2 = new_objtree_VA("CASCADE", 1,
+									  "present", ObjTypeBool, subcmd->behavior);
+				append_object_object(tmp, "cascade", tmp2);
+
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddIndex:
+				{
+					Oid			idxOid = sub->address.objectId;
+					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(tmp));
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;
+
+			case AT_AddConstraint:
+			case AT_AddConstraintRecurse:
+				{
+					/* XXX need to set the "recurse" bit somewhere? */
+					Oid			constrOid = sub->address.objectId;
+
+					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(tmp));
+				}
+				break;
+
+			case AT_AlterConstraint:
+				{
+					Oid		constrOid = sub->address.objectId;
+					Constraint *c = (Constraint *) subcmd->def;
+
+					/* if no constraint was altered, silently skip it */
+					if (!OidIsValid(constrOid))
+						break;
+
+					Assert(IsA(c, Constraint));
+					tmp = new_objtree_VA("ALTER CONSTRAINT %{name}I %{deferrable}s %{init_deferred}s",
+										 2, "type", ObjTypeString, "alter constraint",
+										 "name", ObjTypeString, get_constraint_name(constrOid));
+					append_string_object(tmp, "deferrable", c->deferrable ?
+										 "DEFERRABLE" : "NOT DEFERRABLE");
+					append_string_object(tmp, "init_deferred", c->initdeferred ?
+										 "INITIALLY DEFERRED" : "INITIALLY IMMEDIATE");
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_ValidateConstraintRecurse:
+			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(tmp));
+				break;
+
+			case AT_DropConstraintRecurse:
+			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(tmp));
+				break;
+
+			case AT_AlterColumnType:
+				{
+					TupleDesc tupdesc = RelationGetDescr(rel);
+					Form_pg_attribute att;
+					ColumnDef	   *def;
+
+					att = tupdesc->attrs[sub->address.objectSubId - 1];
+					def = (ColumnDef *) subcmd->def;
+					Assert(IsA(def, ColumnDef));
+
+					fmtstr = psprintf("ALTER %s %%{column}I SET DATA TYPE %%{datatype}T %%{collation}s %s",
+									  istype ? "ATTRIBUTE" : "COLUMN",
+									  istype ? "%{cascade}s" : "%{using}s");
+
+					tmp = new_objtree_VA(fmtstr, 2,
+										 "type", ObjTypeString, "alter column type",
+										 "column", ObjTypeString, subcmd->name);
+					/* add the TYPE clause */
+					append_object_object(tmp, "datatype",
+										 new_objtree_for_type(att->atttypid,
+															  att->atttypmod));
+
+					/* add a COLLATE clause, if needed */
+					tmp2 = new_objtree_VA("COLLATE %{name}D", 0);
+					if (OidIsValid(att->attcollation))
+					{
+						ObjTree *collname;
+
+						collname = new_objtree_for_qualname_id(CollationRelationId,
+															   att->attcollation);
+						append_object_object(tmp2, "name", collname);
+					}
+					else
+						append_bool_object(tmp2, "present", false);
+					append_object_object(tmp, "collation", tmp2);
+
+					/* if not a composite type, add the USING clause */
+					if (!istype)
+					{
+						/*
+						 * If there's a USING clause, transformAlterTableStmt
+						 * ran it through transformExpr and stored the
+						 * resulting node in cooked_default, which we can use
+						 * here.
+						 */
+						tmp2 = new_objtree_VA("USING %{expression}s", 0);
+						if (def->raw_default)
+						{
+							Datum	deparsed;
+							char   *defexpr;
+
+							defexpr = nodeToString(def->cooked_default);
+							deparsed = DirectFunctionCall2(pg_get_expr,
+														   CStringGetTextDatum(defexpr),
+														   RelationGetRelid(rel));
+							append_string_object(tmp2, "expression",
+												 TextDatumGetCString(deparsed));
+						}
+						else
+							append_bool_object(tmp2, "present", false);
+						append_object_object(tmp, "using", tmp2);
+					}
+
+					/* if it's a composite type, add the CASCADE clause */
+					if (istype)
+					{
+						tmp2 = new_objtree_VA("CASCADE", 0);
+						if (subcmd->behavior != DROP_CASCADE)
+							append_bool_object(tmp2, "present", false);
+						append_object_object(tmp, "cascade", tmp2);
+					}
+
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				tmp = deparse_FdwOptions((List *) subcmd->def,
+										 subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_ChangeOwner:
+				tmp = new_objtree_VA("OWNER TO %{owner}I",
+									 2, "type", ObjTypeString, "change owner",
+									 "owner",  ObjTypeString,
+									 get_rolespec_name(subcmd->newowner));
+				subcmds = lappend(subcmds, new_object_object(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(tmp));
+				break;
+
+			case AT_DropCluster:
+				tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+									 "type", ObjTypeString, "set without cluster");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetLogged:
+				tmp = new_objtree_VA("SET LOGGED", 1,
+									 "type", ObjTypeString, "set logged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetUnLogged:
+				tmp = new_objtree_VA("SET UNLOGGED", 1,
+									 "type", ObjTypeString, "set unlogged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOidsRecurse:
+			case AT_AddOids:
+				tmp = new_objtree_VA("SET WITH OIDS", 1,
+									 "type", ObjTypeString, "set with oids");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOids:
+				tmp = new_objtree_VA("SET WITHOUT OIDS", 1,
+									 "type", ObjTypeString, "set without oids");
+				subcmds = lappend(subcmds, new_object_object(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(tmp));
+				break;
+
+			case AT_SetRelOptions:
+			case AT_ResetRelOptions:
+				subcmds = lappend(subcmds, new_object_object(
+									  deparse_RelSetOptions(subcmd)));
+				break;
+
+			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(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(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(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(tmp));
+				break;
+
+			case AT_EnableTrigAll:
+				tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "enable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigAll:
+				tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "disable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableTrigUser:
+				tmp = new_objtree_VA("ENABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "enable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigUser:
+				tmp = new_objtree_VA("DISABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "disable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableRule:
+				tmp = new_objtree_VA("ENABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableAlwaysRule:
+				tmp = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable always rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableReplicaRule:
+				tmp = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable replica rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRule:
+				tmp = new_objtree_VA("DISABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "disable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddInherit:
+				tmp = new_objtree_VA("INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 sub->address.objectId));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropInherit:
+				tmp = new_objtree_VA("NO INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "drop inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 sub->address.objectId));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOf:
+				tmp = new_objtree_VA("OF %{type_of}T",
+									 2, "type", ObjTypeString, "add of",
+									 "type_of", ObjTypeObject,
+									 new_objtree_for_type(sub->address.objectId, -1));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOf:
+				tmp = new_objtree_VA("NOT OF",
+									 1, "type", ObjTypeString, "not of");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				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(tmp));
+				break;
+
+			case AT_EnableRowSecurity:
+				tmp = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "enable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRowSecurity:
+				tmp = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "disable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_GenericOptions:
+				tmp = deparse_FdwOptions((List *) subcmd->def, NULL);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+	}
+
+	heap_close(rel, AccessShareLock);
+
+	if (list_length(subcmds) == 0)
+		return NULL;
+
+	append_array_object(alterTableStmt, "subcmds", subcmds);
+	return alterTableStmt;
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function contains a large switch that mirrors that in
+ * ProcessUtilitySlow.  All cases covered there should also be covered here.
+ */
+static ObjTree *
+deparse_simple_command(CollectedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+	ObjTree	   *command;
+
+	Assert(cmd->type == SCT_Simple);
+
+	parsetree = cmd->parsetree;
+	objectId = cmd->d.simple.address.objectId;
+
+	/* This switch needs to handle everything that ProcessUtilitySlow does */
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			command = deparse_CreateSchemaStmt(objectId, parsetree);
+			break;
+
+		case T_CreateStmt:
+			command = deparse_CreateStmt(objectId, parsetree);
+			break;
+
+		case T_CreateForeignTableStmt:
+			command = deparse_CreateForeignTableStmt(objectId, parsetree);
+			break;
+
+		case T_AlterTableStmt:
+		case T_AlterTableMoveAllStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterDomainStmt:
+			command = deparse_AlterDomainStmt(objectId, parsetree,
+											  cmd->d.simple.secondaryObject);
+			break;
+
+			/* other local objects */
+		case T_DefineStmt:
+			command = deparse_DefineStmt(objectId, parsetree,
+										 cmd->d.simple.secondaryObject);
+			break;
+
+		case T_IndexStmt:
+			command = deparse_IndexStmt(objectId, parsetree);
+			break;
+
+		case T_CreateExtensionStmt:
+			command = deparse_CreateExtensionStmt(objectId, parsetree);
+			break;
+
+		case T_AlterExtensionStmt:
+			command = deparse_AlterExtensionStmt(objectId, parsetree);
+			break;
+
+		case T_AlterExtensionContentsStmt:
+			command = deparse_AlterExtensionContentsStmt(objectId, parsetree,
+														 cmd->d.simple.secondaryObject);
+			break;
+
+		case T_CreateFdwStmt:
+			command = deparse_CreateFdwStmt(objectId, parsetree);
+			break;
+
+		case T_AlterFdwStmt:
+			command = deparse_AlterFdwStmt(objectId, parsetree);
+			break;
+
+		case T_CreateForeignServerStmt:
+			command = deparse_CreateForeignServerStmt(objectId, parsetree);
+			break;
+
+		case T_AlterForeignServerStmt:
+			command = deparse_AlterForeignServerStmt(objectId, parsetree);
+			break;
+
+		case T_CreateUserMappingStmt:
+			command = deparse_CreateUserMappingStmt(objectId, parsetree);
+			break;
+
+		case T_AlterUserMappingStmt:
+			command = deparse_AlterUserMappingStmt(objectId, parsetree);
+			break;
+
+		case T_DropUserMappingStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_ImportForeignSchemaStmt:
+			/* generated commands are stashed individually */
+			elog(ERROR, "unexpected command %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
+			command = deparse_CompositeTypeStmt(objectId, parsetree);
+			break;
+
+		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
+			command = deparse_CreateEnumStmt(objectId, parsetree);
+			break;
+
+		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+			command = deparse_CreateRangeStmt(objectId, parsetree);
+			break;
+
+		case T_AlterEnumStmt:
+			command = deparse_AlterEnumStmt(objectId, parsetree);
+			break;
+
+		case T_ViewStmt:		/* CREATE VIEW */
+			command = deparse_ViewStmt(objectId, parsetree);
+			break;
+
+		case T_CreateFunctionStmt:
+			command = deparse_CreateFunction(objectId, parsetree);
+			break;
+
+		case T_AlterFunctionStmt:
+			command = deparse_AlterFunction(objectId, parsetree);
+			break;
+
+		case T_RuleStmt:
+			command = deparse_RuleStmt(objectId, parsetree);
+			break;
+
+		case T_CreateSeqStmt:
+			command = deparse_CreateSeqStmt(objectId, parsetree);
+			break;
+
+		case T_AlterSeqStmt:
+			command = deparse_AlterSeqStmt(objectId, parsetree);
+			break;
+
+		case T_CreateTableAsStmt:
+			command = deparse_CreateTableAsStmt(objectId, parsetree);
+			break;
+
+		case T_RefreshMatViewStmt:
+			command = deparse_RefreshMatViewStmt(objectId, parsetree);
+			break;
+
+		case T_CreateTrigStmt:
+			command = deparse_CreateTrigStmt(objectId, parsetree);
+			break;
+
+		case T_CreatePLangStmt:
+			command = deparse_CreatePLangStmt(objectId, parsetree);
+			break;
+
+		case T_CreateDomainStmt:
+			command = deparse_CreateDomain(objectId, parsetree);
+			break;
+
+		case T_CreateConversionStmt:
+			command = deparse_CreateConversion(objectId, parsetree);
+			break;
+
+		case T_CreateCastStmt:
+			command = deparse_CreateCastStmt(objectId, parsetree);
+			break;
+
+		case T_CreateOpClassStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpFamilyStmt:
+			command = deparse_CreateOpFamily(objectId, parsetree);
+			break;
+
+		case T_AlterOpFamilyStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSDictionaryStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSConfigurationStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_RenameStmt:
+			command = deparse_RenameStmt(cmd->d.simple.address, parsetree);
+			break;
+
+		case T_AlterObjectSchemaStmt:
+			command = deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
+													parsetree,
+													cmd->d.simple.secondaryObject);
+			break;
+
+		case T_AlterOwnerStmt:
+			command = deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree);
+			break;
+
+		case T_CommentStmt:
+			command = deparse_CommentStmt(cmd->d.simple.address, parsetree);
+			break;
+
+		case T_GrantStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropOwnedStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_AlterDefaultPrivilegesStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePolicyStmt:	/* CREATE POLICY */
+			command = deparse_CreatePolicyStmt(objectId, parsetree);
+			break;
+
+		case T_AlterPolicyStmt:		/* ALTER POLICY */
+			command = deparse_AlterPolicyStmt(objectId, parsetree);
+			break;
+
+		case T_SecLabelStmt:
+			command = deparse_SecLabelStmt(cmd->d.simple.address, parsetree);
+			break;
+
+		default:
+			command = NULL;
+			elog(LOG, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return command;
+}
+
+/*
+ * Given a CollectedCommand, return a JSON representation of it.
+ *
+ * The command is expanded fully, so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+char *
+deparse_utility_command(CollectedCommand *cmd)
+{
+	OverrideSearchPath *overridePath;
+	MemoryContext	oldcxt;
+	MemoryContext	tmpcxt;
+	ObjTree		   *tree;
+	char		   *command;
+	StringInfoData  str;
+
+	/*
+	 * Allocate everything done by the deparsing routines into a temp context,
+	 * to avoid having to sprinkle them with memory handling code; but allocate
+	 * the output StringInfo before switching.
+	 */
+	initStringInfo(&str);
+	tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "deparse ctx",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * in order to obtain deparsed versions of expressions.  In such results,
+	 * we want all object names to be qualified, so that results are "portable"
+	 * to environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = true;
+	PushOverrideSearchPath(overridePath);
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			tree = deparse_simple_command(cmd);
+			break;
+		case SCT_AlterTable:
+			tree = deparse_AlterTableStmt(cmd);
+			break;
+		case SCT_Grant:
+			tree = deparse_GrantStmt(cmd);
+			break;
+		case SCT_AlterOpFamily:
+			tree = deparse_AlterOpFamily(cmd);
+			break;
+		case SCT_CreateOpClass:
+			tree = deparse_CreateOpClassStmt(cmd);
+			break;
+		case SCT_AlterDefaultPrivileges:
+			tree = deparse_AlterDefaultPrivilegesStmt(cmd);
+			break;
+		case SCT_AlterTSConfig:
+			tree = deparse_AlterTSConfigurationStmt(cmd);
+			break;
+		default:
+			elog(ERROR, "unexpected deparse node type %d", cmd->type);
+	}
+
+	PopOverrideSearchPath();
+
+	if (tree)
+	{
+		Jsonb *jsonb;
+
+		jsonb = objtree_to_jsonb(tree);
+		command = JsonbToCString(&str, &jsonb->root, 128);
+	}
+	else
+		command = NULL;
+
+	/*
+	 * Clean up.  Note that since we created the StringInfo in the caller's
+	 * context, the output string is not deleted here.
+	 */
+	MemoryContextSwitchTo(oldcxt);
+	MemoryContextDelete(tmpcxt);
+
+	return command;
+}
diff --git a/contrib/ddl_deparse/ddl_deparse.control b/contrib/ddl_deparse/ddl_deparse.control
new file mode 100644
index 0000000..bfecb93
--- /dev/null
+++ b/contrib/ddl_deparse/ddl_deparse.control
@@ -0,0 +1,5 @@
+# ddl_deparse extension
+comment = 'DDL deparse support'
+default_version = '1.0'
+module_pathname = '$libdir/ddl_deparse'
+relocatable = true
diff --git a/contrib/ddl_deparse/ddl_deparse.h b/contrib/ddl_deparse/ddl_deparse.h
new file mode 100644
index 0000000..d48ccac
--- /dev/null
+++ b/contrib/ddl_deparse/ddl_deparse.h
@@ -0,0 +1,8 @@
+#ifndef DDL_DEPARSE_H
+#define DDL_DEPARSE_H
+
+#include "tcop/deparse_utility.h"
+
+extern char *deparse_utility_command(CollectedCommand *cmd);
+
+#endif		/* DDL_DEPARSE_H */
diff --git a/contrib/ddl_deparse/ddl_json.c b/contrib/ddl_deparse/ddl_json.c
new file mode 100644
index 0000000..b67a32a
--- /dev/null
+++ b/contrib/ddl_deparse/ddl_json.c
@@ -0,0 +1,715 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_json.c
+ *	  JSON code related to DDL command deparsing
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  contrib/ddl_deparse/ddl_json.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "lib/stringinfo.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+PG_FUNCTION_INFO_V1(ddl_deparse_expand_command);
+
+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	SpecNumber,
+	SpecStringLiteral,
+	SpecIdentifier,
+	SpecRole
+} convSpecifier;
+
+typedef enum
+{
+	tv_absent,
+	tv_true,
+	tv_false
+} trivalue;
+
+static void expand_one_jsonb_element(StringInfo out, char *param,
+						 JsonbValue *jsonval, convSpecifier specifier,
+						 const char *fmt);
+static void expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep,
+				   convSpecifier specifier, const char *fmt);
+static void fmtstr_error_callback(void *arg);
+
+static trivalue
+find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	bool		result;
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+		return tv_absent;
+	if (value->type != jbvBool)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not of type boolean",
+						keyname)));
+	result = value->val.boolean ? tv_true : tv_false;
+	pfree(value);
+
+	return result;
+}
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvString, an error is raised.  If it doesn't
+ * exist, an error is raised if missing_ok; otherwise return NULL.
+ *
+ * If it exists and is a string, a freshly palloc'ed copy is returned.
+ *
+ * If *length is not NULL, it is set to the length of the string.
+ */
+static char *
+find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname,
+							  bool missing_ok, int *length)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	char	   *str;
+
+	/* XXX verify that this is an object, not an array */
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+	{
+		if (missing_ok)
+			return NULL;
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing element \"%s\" in json object", keyname)));
+	}
+
+	str = pnstrdup(value->val.string.val, value->val.string.len);
+	if (length)
+		*length = value->val.string.len;
+	pfree(value);
+	return str;
+}
+
+#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)
+
+/*
+ * Recursive helper for pg_event_trigger_expand_command
+ *
+ * Find the "fmt" element in the given container, and expand it into the
+ * provided StringInfo.
+ */
+static void
+expand_fmt_recursive(JsonbContainer *container, StringInfo out)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	int			len;
+
+	start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len);
+	end_ptr = start_ptr + len;
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{
+		convSpecifier specifier;
+		bool		is_array;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		is_array = false;
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			for (; cp < end_ptr;)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing conversion name in conversion specifier")));
+
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'L':
+				specifier = SpecStringLiteral;
+				break;
+			case 'T':
+				specifier = SpecTypename;
+				break;
+			case 'O':
+				specifier = SpecOperatorname;
+				break;
+			case 'n':
+				specifier = SpecNumber;
+				break;
+			case 'R':
+				specifier = SpecRole;
+				break;
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid conversion specifier \"%c\"", *cp)));
+		}
+
+		/*
+		 * Obtain the element to be expanded.
+		 */
+		key.type = jbvString;
+		key.val.string.val = param;
+		key.val.string.len = strlen(param);
+
+		value = findJsonbValueFromContainer(container, JB_FOBJECT, &key);
+
+		/* Validate that we got an array if the format string specified one. */
+
+		/* And finally print out the data */
+		if (is_array)
+			expand_jsonb_array(out, param, value, arraysep, specifier, start_ptr);
+		else
+			expand_one_jsonb_element(out, param, value, specifier, start_ptr);
+	}
+}
+
+/*
+ * Expand a json value as an identifier.  The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	Assert(jsonval->type == jbvString);
+
+	str = pnstrdup(jsonval->val.string.val,
+				   jsonval->val.string.len);
+	appendStringInfoString(buf, quote_identifier(str));
+	pfree(str);
+}
+
+/*
+ * Expand a json value as a dot-separated-name.  The value must be of type
+ * object and must contain elements "schemaname" (optional), "objname"
+ * (mandatory), "attrname" (optional).  Double quotes are added to each element
+ * as necessary, and dot separators where needed.
+ *
+ * One day we might need a "catalog" element as well, but no current use case
+ * needs that.
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+		pfree(str);
+	}
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfo(buf, "%s", quote_identifier(str));
+	pfree(str);
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"attrname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, ".%s", quote_identifier(str));
+		pfree(str);
+	}
+}
+
+/*
+ * expand a json value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename;
+	char	   *typmodstr;
+	trivalue	is_array;
+	char	   *array_decor;
+
+	/*
+	 * We omit schema-qualifying the output name if the schema element is
+	 * either the empty string or NULL; the difference between those two cases
+	 * is that in the latter we quote the type name, in the former we don't.
+	 * This allows for types with special typmod needs, such as interval and
+	 * timestamp (see format_type_detailed), while at the same time allowing
+	 * for the schema name to be omitted from type names that require quotes
+	 * but are to be obtained from a user schema.
+	 */
+
+	schema = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										   "schemaname", true, NULL);
+	typename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											 "typename", false, NULL);
+	typmodstr = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											  "typmod", true, NULL);
+	is_array = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+										   "typarray");
+	switch (is_array)
+	{
+		default:
+		case tv_absent:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("missing typarray element")));
+			break;
+		case tv_true:
+			array_decor = "[]";
+			break;
+		case tv_false:
+			array_decor = "";
+			break;
+	}
+
+	if (schema == NULL)
+		appendStringInfo(buf, "%s%s%s",
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else if (schema[0] == '\0')
+		appendStringInfo(buf, "%s%s%s",
+						 typename,
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else
+		appendStringInfo(buf, "%s.%s%s%s",
+						 quote_identifier(schema),
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+}
+
+/*
+ * Expand a json value as an operator name
+ */
+static void
+expand_jsonval_operator(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	/* schema might be NULL or empty */
+	if (str != NULL && str[0] != '\0')
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfoString(buf, str);
+}
+
+/*
+ * Expand a json value as a string.  The value must be of type string or of
+ * type object.  In the latter 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, JsonbValue *jsonval)
+{
+	if (jsonval->type == jbvString)
+	{
+		appendBinaryStringInfo(buf, jsonval->val.string.val,
+							   jsonval->val.string.len);
+	}
+	else if (jsonval->type == jbvBinary)
+	{
+		trivalue	present;
+
+		present = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											  "present");
+		/*
+		 * If "present" is set to false, this element expands to empty;
+		 * otherwise (either true or absent), fall through to expand "fmt".
+		 */
+		if (present == tv_false)
+			return;
+
+		expand_fmt_recursive(jsonval->val.binary.data, buf);
+	}
+}
+
+/*
+ * Expand a json value as a string literal
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval)
+{
+	char   *str;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int         dqnextchar = 0;
+
+	str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+
+	/* easy case: if there are no ' and no \, just use a single quote */
+	if (strchr(str, '\'') == NULL &&
+		strchr(str, '\\') == NULL)
+	{
+		appendStringInfo(buf, "'%s'", str);
+		pfree(str);
+		return;
+	}
+
+	/* Otherwise need to find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(str, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar %= sizeof(dqsuffixes) - 1;
+	}
+	/* add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data);
+	pfree(dqdelim.data);
+	pfree(str);
+}
+
+/*
+ * Expand a json value as an integer quantity
+ */
+static void
+expand_jsonval_number(StringInfo buf, JsonbValue *jsonval)
+{
+	char *strdatum;
+
+	strdatum = DatumGetCString(DirectFunctionCall1(numeric_out,
+												   NumericGetDatum(jsonval->val.numeric)));
+	appendStringInfoString(buf, strdatum);
+}
+
+/*
+ * Expand a json value as a role name.  If the is_public element is set to
+ * true, PUBLIC is expanded (no quotes); otherwise, expand the given role name,
+ * quoting as an identifier.
+ */
+static void
+expand_jsonval_role(StringInfo buf, JsonbValue *jsonval)
+{
+	trivalue	is_public;
+
+	is_public = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											"is_public");
+	if (is_public == tv_true)
+		appendStringInfoString(buf, "PUBLIC");
+	else
+	{
+		char *rolename;
+
+		rolename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+												 "rolename", false, NULL);
+		appendStringInfoString(buf, quote_identifier(rolename));
+	}
+}
+
+/*
+ * Expand one json element into the output StringInfo according to the
+ * conversion specifier.  The element type is validated, and an error is raised
+ * if it doesn't match what we expect for the conversion specifier.
+ */
+static void
+expand_one_jsonb_element(StringInfo out, char *param, JsonbValue *jsonval,
+						 convSpecifier specifier, const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (!jsonval)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" not found", param)));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%I element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_identifier(out, jsonval);
+			break;
+
+		case SpecDottedName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%D element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_dottedname(out, jsonval);
+			break;
+
+		case SpecString:
+			if (jsonval->type != jbvString &&
+				jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string or object for %%s element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_string(out, jsonval);
+			break;
+
+		case SpecStringLiteral:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%L element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_strlit(out, jsonval);
+			break;
+
+		case SpecTypename:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%T element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_typename(out, jsonval);
+			break;
+
+		case SpecOperatorname:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%O element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_operator(out, jsonval);
+			break;
+
+		case SpecNumber:
+			if (jsonval->type != jbvNumeric)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON numeric for %%n element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_number(out, jsonval);
+			break;
+
+		case SpecRole:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%R element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_role(out, jsonval);
+			break;
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*
+ * Iterate on the elements of a JSON array, expanding each one into the output
+ * StringInfo per the given conversion specifier, separated by the given
+ * separator.
+ */
+static void
+expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep, convSpecifier specifier,
+				   const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+	JsonbContainer *container;
+	JsonbIterator  *it;
+	JsonbValue	v;
+	int			type;
+	bool		first = true;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (jsonarr->type != jbvBinary)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	container = jsonarr->val.binary.data;
+	if ((container->header & JB_FARRAY) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	it = JsonbIteratorInit(container);
+	while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+	{
+		switch (type)
+		{
+			case WJB_ELEM:
+				if (!first)
+					appendStringInfoString(out, arraysep);
+				first = false;
+				expand_one_jsonb_element(out, param, &v, specifier, NULL);
+				break;
+		}
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ * n		expand as a simple number (no quoting)
+ * R		expand as a role name (possibly quoted name, or PUBLIC)
+ *
+ * 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.
+ *------
+ */
+Datum
+ddl_deparse_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	Datum		d;
+	Jsonb	   *jsonb;
+	StringInfoData out;
+
+	initStringInfo(&out);
+	d = DirectFunctionCall1(jsonb_in,
+							PointerGetDatum(TextDatumGetCString(json)));
+	jsonb = (Jsonb *) DatumGetPointer(d);
+
+	expand_fmt_recursive(&jsonb->root, &out);
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(out.data));
+}
+
+/*
+ * Error context callback for JSON format string expansion.
+ *
+ * Possible improvement: indicate which element we're expanding, if applicable
+ */
+static void
+fmtstr_error_callback(void *arg)
+{
+	errcontext("while expanding format string \"%s\"", (char *) arg);
+
+}
diff --git a/contrib/ddl_deparse/hook.c b/contrib/ddl_deparse/hook.c
new file mode 100644
index 0000000..766c230
--- /dev/null
+++ b/contrib/ddl_deparse/hook.c
@@ -0,0 +1,32 @@
+/*--------------------------------------------------------------------------
+ * contrib/ddl_deparse/hook.c
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "ddl_deparse.h"
+#include "commands/event_trigger.h"
+#include "miscadmin.h"
+
+PG_MODULE_MAGIC;
+
+void		_PG_init(void);
+
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("module must be loaded via shared_preload_libraries")));
+
+	/* CommandDeparse hook.  There can only be one */
+	if (CommandDeparse_hook != NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("only one module can provide a CommandDeparse hook")));
+
+	CommandDeparse_hook = deparse_utility_command;
+}
-- 
2.1.4

#42David Steele
david@pgmasters.net
In reply to: Alvaro Herrera (#40)
Re: deparsing utility commands

On 4/7/15 4:32 PM, Alvaro Herrera wrote:

Executive summary:

There is now a CommandDeparse_hook;
deparse_utility_command is provided as an extension, intended for 9.6;
rest of patch would be pushed to 9.5.

Long version:

I've made command deparsing hookable. Attached there are three patches:
the first patch contains changes to core that just add the "command
list" stuff, so that on ddl_command_end there is access to what has been
executed. This includes the OID of the object just created, the command
tag, and assorted other details; the deparsed command in JSON format is
not immediately part of the result.

I'm looking forward to testing this with pg_audit. I think the first
patch alone will give us the bulk of the desired functionality.

The third patch contains all the deparse code, packaged as a contrib
module and extension named ddl_deparse. Essentially, it's everything
that was previously in tcop/deparse_utility.c and utils/adt/ddl_json.c:
the stuff that takes the parsenode and OID of a command execution and
turns it into a JSON blob, and also the support function that takes the
JSON blob and converts back into the plain text rendering of the
command.

The second patch contains some changes to core code that support the
ddl_deparse extension; mainly some ruleutils.c changes.

What links patches 0001 and 0003 is a hook, CommandDeparse_hook. If
unset, the pg_event_trigger_ddl_commands function returns some
boilerplate text like "no deparse function installed"; if the extension
is installed, the JSON rendering is returned instead and can be used
with the ddl_deparse_expand_command() function.

I would prefer for the column to be NULL or to have a boolean column
that indicates if JSON output is available (or both). I'd rather not do
string matching if I can help it (or test for invalid JSON).

The rationale for doing things this way is that it will be useful to
have 9.5 expose the pg_event_trigger_ddl_commands() function for various
uses, while we refine the JSON bits some more and get it committed for
9.6. In reviews, it's clear that there's some more bits to fiddle so
that it can be as general as possible. I think we should label the
whole DDL command reporting as experimental in 9.5 and subject to
change, so that we can just remove the hook in 9.6 when the ddl_deparse
thing becomes part of core.

I'm completely on board with this. I think deparse is a cool piece of
code and I see a bunch of potential uses for it, but at the same time
I'm not sure it has finished baking yet. This is a smart and safe choice.

Is there a particular commit you'd recommend for applying the deparse
patches (especially the first set)?

--
- David Steele
david@pgmasters.net

#43Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#40)
3 attachment(s)
Re: deparsing utility commands

Alvaro Herrera wrote:

Executive summary:

There is now a CommandDeparse_hook;
deparse_utility_command is provided as an extension, intended for 9.6;
rest of patch would be pushed to 9.5.

Actually here's another approach I like better: use a new pseudotype,
pg_ddl_command, which internally is just a pointer to a CollectedCommand
struct. We cannot give access to the pointer directly in SQL, so much
like type internal or pg_node_tree the in/out functions should just
error out. But the type can be returned as a column in
pg_event_trigger_ddl_command. An extension can create a function that
receives that type as argument, and return a different representation of
the command; the third patch creates a function ddl_deparse_to_json()
which does that.

You can have as many extensions as you want, and your event triggers can
use the column as many times as necessary. This removes the limitation
of the previous approach that you could only have a single extension
providing a CommandDeparse_hook function.

This patch applies cleanly on top of current master. You need to
install the extension with "CREATE EXTENSION ddl_deparse" after building
it in contrib.

Note: the extension is NOT to be committed to core, only the first two
patches; that will give us more room to revisit the JSON representation
more promptly. My intention is that the extension will be published
elsewhere.

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

Attachments:

0001-deparse-infrastructure-needed-for-command-deparsing.patchtext/x-diff; charset=us-asciiDownload
>From 47bb0e24c6704209b80bcc4f9d5af4e790260633 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 24 Sep 2014 15:53:04 -0300
Subject: [PATCH 1/3] deparse: infrastructure needed for command deparsing

This patch introduces unused infrastructure for command deparsing.
Each command executed is stored in a list if ddl_command_end event
triggers are installed; when that event fires, the trigger function can
call pg_event_trigger_ddl_commands() to obtain the list.
---
 src/backend/catalog/aclchk.c         |  37 +-
 src/backend/commands/event_trigger.c | 699 ++++++++++++++++++++++++++++++++++-
 src/backend/commands/opclasscmds.c   |  45 +--
 src/backend/commands/schemacmds.c    |  12 +
 src/backend/commands/tablecmds.c     |  11 +
 src/backend/commands/tsearchcmds.c   |   5 +
 src/backend/nodes/copyfuncs.c        |   1 +
 src/backend/nodes/equalfuncs.c       |   1 +
 src/backend/parser/gram.y            |   6 +
 src/backend/tcop/utility.c           | 276 ++++++++++----
 src/backend/utils/adt/format_type.c  |  14 +-
 src/include/catalog/opfam_internal.h |  28 ++
 src/include/catalog/pg_proc.h        |   2 +
 src/include/catalog/pg_type.h        |   5 +
 src/include/commands/defrem.h        |   1 +
 src/include/commands/event_trigger.h |  26 ++
 src/include/commands/extension.h     |   2 +-
 src/include/nodes/parsenodes.h       |  10 +
 src/include/tcop/deparse_utility.h   | 106 ++++++
 src/include/utils/aclchk_internal.h  |  45 +++
 src/include/utils/builtins.h         |   1 +
 21 files changed, 1196 insertions(+), 137 deletions(-)
 create mode 100644 src/include/catalog/opfam_internal.h
 create mode 100644 src/include/tcop/deparse_utility.h
 create mode 100644 src/include/utils/aclchk_internal.h

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 8e75c27..943909c 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "foreign/foreign.h"
@@ -56,6 +57,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
 #include "utils/acl.h"
+#include "utils/aclchk_internal.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
@@ -65,32 +67,6 @@
 
 
 /*
- * The information about one Grant/Revoke statement, in internal format: object
- * and grantees names have been turned into Oids, the privilege list is an
- * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
- * all_privs is true, 'privileges' will be internally set to the right kind of
- * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
- * InternalGrant struct!)
- *
- * Note: 'all_privs' and 'privileges' represent object-level privileges only.
- * There might also be column-level privilege specifications, which are
- * represented in col_privs (this is a list of untransformed AccessPriv nodes).
- * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
- */
-typedef struct
-{
-	bool		is_grant;
-	GrantObjectType objtype;
-	List	   *objects;
-	bool		all_privs;
-	AclMode		privileges;
-	List	   *col_privs;
-	List	   *grantees;
-	bool		grant_option;
-	DropBehavior behavior;
-} InternalGrant;
-
-/*
  * Internal format used by ALTER DEFAULT PRIVILEGES.
  */
 typedef struct
@@ -605,6 +581,15 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	if (EventTriggerSupportsGrantObjectType(istmt->objtype))
+		EventTriggerCollectGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index f07fd06..6e559e3 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -20,21 +20,28 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_ts_config.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/trigger.h"
 #include "funcapi.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -44,6 +51,9 @@
 
 typedef struct EventTriggerQueryState
 {
+	/* memory context for this state's objects */
+	MemoryContext cxt;
+
 	/* sql_drop */
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
@@ -52,7 +62,10 @@ typedef struct EventTriggerQueryState
 	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite event */
 	int			table_rewrite_reason;	/* AT_REWRITE reason */
 
-	MemoryContext cxt;
+	/* Support for command collection */
+	bool		commandCollectionInhibited;
+	CollectedCommand *currentCommand;
+	List	   *commandList;		/* list of CollectedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -71,6 +84,7 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
 	{"AGGREGATE", true},
 	{"CAST", true},
@@ -138,6 +152,7 @@ static Oid insert_event_trigger_tuple(char *trigname, char *eventname,
 static void validate_ddl_tags(const char *filtervar, List *taglist);
 static void validate_table_rewrite_tags(const char *filtervar, List *taglist);
 static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
+static const char *stringify_grantobjtype(GrantObjectType objtype);
 
 /*
  * Create an event trigger.
@@ -1203,9 +1218,9 @@ EventTriggerBeginCompleteQuery(void)
 	MemoryContext cxt;
 
 	/*
-	 * Currently, sql_drop and table_rewrite events are the only reason to
-	 * have event trigger state at all; so if there are none, don't install
-	 * one.
+	 * Currently, sql_drop, table_rewrite, ddL_command_end events are the only
+	 * reason to have event trigger state at all; so if there are none, don't
+	 * install one.
 	 */
 	if (!trackDroppedObjectsNeeded())
 		return false;
@@ -1221,6 +1236,10 @@ EventTriggerBeginCompleteQuery(void)
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
 
+	state->commandCollectionInhibited = currentEventTriggerState ?
+		currentEventTriggerState->commandCollectionInhibited : false;
+	state->currentCommand = NULL;
+	state->commandList = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
 
@@ -1259,9 +1278,13 @@ EventTriggerEndCompleteQuery(void)
 bool
 trackDroppedObjectsNeeded(void)
 {
-	/* true if any sql_drop or table_rewrite event trigger exists */
+	/*
+	 * true if any sql_drop, table_rewrite, ddl_command_end event trigger
+	 * exists
+	 */
 	return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 ||
-		list_length(EventCacheLookup(EVT_TableRewrite)) > 0;
+		list_length(EventCacheLookup(EVT_TableRewrite)) > 0 ||
+		list_length(EventCacheLookup(EVT_DDLCommandEnd)) > 0;
 }
 
 /*
@@ -1563,3 +1586,667 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
 }
+
+/*-------------------------------------------------------------------------
+ * Support for DDL command deparsing
+ *
+ * The routines below enable an event trigger function to obtain a list of
+ * DDL commands as they are executed.  There are three main pieces to this
+ * feature:
+ *
+ * 1) Within ProcessUtilitySlow, or some sub-routine thereof, each DDL command
+ * adds a struct CollectedCommand representation of itself to the command list,
+ * using the routines below.
+ *
+ * 2) Some time after that, ddl_command_end fires and the command list is made
+ * available to the event trigger function via pg_event_trigger_ddl_commands();
+ * the complete command details are exposed as a column of type pg_ddl_command.
+ *
+ * 3) An extension can install a function capable of taking a value of type
+ * pg_ddl_command and transform it into some external, user-visible and/or
+ * -modifiable representation.
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Inhibit DDL command collection.
+ */
+void
+EventTriggerInhibitCommandCollection(void)
+{
+	if (!currentEventTriggerState)
+		return;
+
+	currentEventTriggerState->commandCollectionInhibited = true;
+}
+
+/*
+ * Re-establish DDL command collection.
+ */
+void
+EventTriggerUndoInhibitCommandCollection(void)
+{
+	if (!currentEventTriggerState)
+		return;
+
+	currentEventTriggerState->commandCollectionInhibited = false;
+}
+
+/*
+ * EventTriggerCollectSimpleCommand
+ *		Save data about a simple DDL command that was just executed
+ *
+ * address identifies the object being operated on.  secondaryObject is an
+ * object address that was related in some way to the executed command; its
+ * meaning is command-specific.
+ *
+ * For instance, for an ALTER obj SET SCHEMA command, objtype is the type of
+ * object being moved, objectId is its OID, and secondaryOid is the OID of the
+ * old schema.  (The destination schema OID can be obtained by catalog lookup
+ * of the object.)
+ */
+void
+EventTriggerCollectSimpleCommand(ObjectAddress address,
+								 ObjectAddress secondaryObject,
+								 Node *parsetree)
+{
+	MemoryContext oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+
+	command->type = SCT_Simple;
+	command->in_extension = creating_extension;
+
+	command->d.simple.address = address;
+	command->d.simple.secondaryObject = secondaryObject;
+	command->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->commandList = lappend(currentEventTriggerState->commandList,
+											  command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableStart
+ *		Prepare to receive data on an ALTER TABLE command about to be executed
+ *
+ * Note we don't collect the command immediately; instead we keep it in
+ * currentCommand, and only when we're done processing the subcommands we will
+ * add it to the command list.
+ *
+ * XXX -- this API isn't considering the possibility of an ALTER TABLE command
+ * being called reentrantly by an event trigger function.  Do we need stackable
+ * commands at this level?  Perhaps at least we should detect the condition and
+ * raise an error.
+ */
+void
+EventTriggerAlterTableStart(Node *parsetree)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+
+	command->type = SCT_AlterTable;
+	command->in_extension = creating_extension;
+
+	command->d.alterTable.classId = RelationRelationId;
+	command->d.alterTable.objectId = InvalidOid;
+	command->d.alterTable.subcmds = NIL;
+	command->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->currentCommand = command;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remember the OID of the object being affected by an ALTER TABLE.
+ *
+ * This is needed because in some cases we don't know the OID until later.
+ */
+void
+EventTriggerAlterTableRelid(Oid objectId)
+{
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	currentEventTriggerState->currentCommand->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerCollectAlterTableSubcmd
+ *		Save data about a single part of an ALTER TABLE.
+ *
+ * Several different commands go through this path, but apart from ALTER TABLE
+ * itself, they are all concerned with AlterTableCmd nodes that are generated
+ * internally, so that's all that this code needs to handle at the moment.
+ */
+void
+EventTriggerCollectAlterTableSubcmd(Node *subcmd, Oid relid,
+								  ObjectAddress address)
+{
+	MemoryContext	oldcxt;
+	CollectedATSubcmd *newsub;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId));
+
+	/*
+	 * If we receive a subcommand intended for a relation other than the one
+	 * we've started the ALTER TABLE for, ignore it.  This is chiefly concerned
+	 * with inheritance situations: in such cases, alter table would dispatch
+	 * multiple copies of the same command for child tables, but we're only
+	 * concerned with the one for the main table.
+	 */
+	if (relid != currentEventTriggerState->currentCommand->d.alterTable.objectId)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(CollectedATSubcmd));
+	newsub->address = address;
+	newsub->parsetree = copyObject(subcmd);
+
+	currentEventTriggerState->currentCommand->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableEnd
+ *		Finish up saving an ALTER TABLE command, and add it to command list
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerAlterTableEnd(void)
+{
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	/* If no subcommands, don't collect */
+	if (list_length(currentEventTriggerState->currentCommand->d.alterTable.subcmds) != 0)
+	{
+		currentEventTriggerState->commandList =
+			lappend(currentEventTriggerState->commandList,
+					currentEventTriggerState->currentCommand);
+	}
+	else
+		pfree(currentEventTriggerState->currentCommand);
+
+	currentEventTriggerState->currentCommand = NULL;
+}
+
+/*
+ * EventTriggerCollectGrant
+ *		Save data about a GRANT/REVOKE command being executed
+ *
+ * This function creates a copy of the InternalGrant, as the original might
+ * not have the right lifetime.
+ */
+void
+EventTriggerCollectGrant(InternalGrant *istmt)
+{
+	MemoryContext oldcxt;
+	CollectedCommand *command;
+	InternalGrant  *icopy;
+	ListCell	   *cell;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	/*
+	 * copying the node is moderately challenging ... 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)));
+
+	command = palloc(sizeof(CollectedCommand));
+	command->type = SCT_Grant;
+	command->in_extension = creating_extension;
+	command->d.grant.type = stringify_grantobjtype(istmt->objtype);
+	command->d.grant.istmt = icopy;
+	command->parsetree = NULL;
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterOpFam
+ *		Save data about an ALTER OPERATOR FAMILY ADD/DROP command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
+							List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+	command->type = SCT_AlterOpFamily;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.opfam.address,
+					 OperatorFamilyRelationId, opfamoid);
+	command->d.opfam.operators = operators;		/* XXX prolly need to copy */
+	command->d.opfam.procedures = procedures;	/* XXX ditto */
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectCreateOpClass
+ *		Save data about a CREATE OPERATOR CLASS command being executed
+ */
+void
+EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
+							   List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_CreateOpClass;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.createopc.address,
+					 OperatorClassRelationId, opcoid);
+	command->d.createopc.operators = operators;	/* XXX prolly need to copy */
+	command->d.createopc.procedures = procedures;	/* ditto */
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterTSConfig
+ *		Save data about an ALTER TEXT SEARCH CONFIGURATION command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
+								 Oid *dictIds, int ndicts)
+{
+	MemoryContext   oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_AlterTSConfig;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.atscfg.address,
+					 TSConfigRelationId, cfgId);
+	command->d.atscfg.dictIds = palloc(sizeof(Oid) * ndicts);
+	memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
+	command->d.atscfg.ndicts = ndicts;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterDefPrivs
+ *		Save data about an ALTER DEFAULT PRIVILEGES command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_AlterDefaultPrivileges;
+
+	switch (stmt->action->objtype)
+	{
+		case ACL_OBJECT_RELATION:
+			command->d.defprivs.objtype = "TABLES";
+			break;
+		case ACL_OBJECT_FUNCTION:
+			command->d.defprivs.objtype = "FUNCTIONS";
+			break;
+		case ACL_OBJECT_SEQUENCE:
+			command->d.defprivs.objtype = "SEQUENCES";
+			break;
+		case ACL_OBJECT_TYPE:
+			command->d.defprivs.objtype = "TYPES";
+			break;
+		default:
+			elog(ERROR, "unexpected object type %d", stmt->action->objtype);
+	}
+
+
+	command->in_extension = creating_extension;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * In a ddl_command_end event trigger, this functions reports the DDL commands
+ * that are being run.
+ */
+Datum
+pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_ddl_commands()")));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->commandList)
+	{
+		CollectedCommand *cmd = lfirst(lc);
+		Datum		values[9];
+		bool		nulls[9];
+		ObjectAddress addr;
+		int			i = 0;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid.  Don't return anything.
+		 *
+		 * One might think that a viable alternative would be to look up the
+		 * Oid of the existing object and run the deparse with that.  But since
+		 * the parse tree might be different from the one that created the
+		 * object in the first place, we might not end up in a consistent state
+		 * anyway.
+		 */
+		if (cmd->type == SCT_Simple &&
+			!OidIsValid(cmd->d.simple.address.objectId))
+			continue;
+
+		MemSet(nulls, 0, sizeof(nulls));
+
+		switch (cmd->type)
+		{
+			case SCT_Simple:
+			case SCT_AlterTable:
+			case SCT_AlterOpFamily:
+			case SCT_CreateOpClass:
+			case SCT_AlterTSConfig:
+				{
+					const char *tag;
+					char	   *identity;
+					char	   *type;
+					char	   *schema = NULL;
+
+					if (cmd->type == SCT_Simple)
+						addr = cmd->d.simple.address;
+					else if (cmd->type == SCT_AlterTable)
+						ObjectAddressSet(addr,
+										 cmd->d.alterTable.classId,
+										 cmd->d.alterTable.objectId);
+					else if (cmd->type == SCT_AlterOpFamily)
+						addr = cmd->d.opfam.address;
+					else if (cmd->type == SCT_CreateOpClass)
+						addr = cmd->d.createopc.address;
+					else if (cmd->type == SCT_AlterTSConfig)
+						addr = cmd->d.atscfg.address;
+
+					tag = CreateCommandTag(cmd->parsetree);
+
+					type = getObjectTypeDescription(&addr);
+					identity = getObjectIdentity(&addr);
+
+					/*
+					 * Obtain schema name, if any ("pg_temp" if a temp object).
+					 * If the object class is not in the supported list here,
+					 * we assume it's a schema-less object type, and thus
+					 * "schema" remains set to NULL.
+					 */
+					if (is_objectclass_supported(addr.classId))
+					{
+						AttrNumber	nspAttnum;
+
+						nspAttnum = get_object_attnum_namespace(addr.classId);
+						if (nspAttnum != InvalidAttrNumber)
+						{
+							Relation	catalog;
+							HeapTuple	objtup;
+							Oid			schema_oid;
+							bool		isnull;
+
+							catalog = heap_open(addr.classId, AccessShareLock);
+							objtup = get_catalog_object_by_oid(catalog,
+															   addr.objectId);
+							if (!HeapTupleIsValid(objtup))
+								elog(ERROR, "cache lookup failed for object %u/%u",
+									 addr.classId, addr.objectId);
+							schema_oid =
+								heap_getattr(objtup, nspAttnum,
+											 RelationGetDescr(catalog), &isnull);
+							if (isnull)
+								elog(ERROR,
+									 "invalid null namespace in object %u/%u/%d",
+									 addr.classId, addr.objectId, addr.objectSubId);
+							/* XXX not quite get_namespace_name_or_temp */
+							if (isAnyTempNamespace(schema_oid))
+								schema = pstrdup("pg_temp");
+							else
+								schema = get_namespace_name(schema_oid);
+
+							heap_close(catalog, AccessShareLock);
+						}
+					}
+
+					/* classid */
+					values[i++] = ObjectIdGetDatum(addr.classId);
+					/* objid */
+					values[i++] = ObjectIdGetDatum(addr.objectId);
+					/* objsubid */
+					values[i++] = Int32GetDatum(addr.objectSubId);
+					/* command tag */
+					values[i++] = CStringGetTextDatum(tag);
+					/* object_type */
+					values[i++] = CStringGetTextDatum(type);
+					/* schema */
+					if (schema == NULL)
+						nulls[i++] = true;
+					else
+						values[i++] = CStringGetTextDatum(schema);
+					/* identity */
+					values[i++] = CStringGetTextDatum(identity);
+					/* in_extension */
+					values[i++] = BoolGetDatum(cmd->in_extension);
+					/* command */
+					values[i++] = PointerGetDatum(cmd);
+				}
+				break;
+
+			case SCT_AlterDefaultPrivileges:
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree));
+				/* object_type */
+				values[i++] = CStringGetTextDatum(cmd->d.defprivs.objtype);
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = PointerGetDatum(cmd);
+				break;
+
+			case SCT_Grant:
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.istmt->is_grant ?
+												  "GRANT" : "REVOKE");
+				/* object_type */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.type);
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = PointerGetDatum(cmd);
+				break;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
+
+static const char *
+stringify_grantobjtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+			return "COLUMN";
+		case ACL_OBJECT_RELATION:
+			return "TABLE";
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case ACL_OBJECT_DATABASE:
+			return "DATABASE";
+		case ACL_OBJECT_DOMAIN:
+			return "DOMAIN";
+		case ACL_OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case ACL_OBJECT_FOREIGN_SERVER:
+			return "FOREIGN SERVER";
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTION";
+		case ACL_OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case ACL_OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case ACL_OBJECT_NAMESPACE:
+			return "SCHEMA";
+		case ACL_OBJECT_TABLESPACE:
+			return "TABLESPACE";
+		case ACL_OBJECT_TYPE:
+			return "TYPE";
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index c327cc0..99e372a 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -25,6 +25,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
+#include "catalog/opfam_internal.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_namespace.h"
@@ -35,6 +36,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
@@ -47,24 +49,12 @@
 #include "utils/tqual.h"
 
 
-/*
- * We use lists of this struct type to keep track of both operators and
- * procedures while building or adding to an opfamily.
- */
-typedef struct
-{
-	Oid			object;			/* operator or support proc's OID */
-	int			number;			/* strategy or support proc number */
-	Oid			lefttype;		/* lefttype */
-	Oid			righttype;		/* righttype */
-	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
-} OpFamilyMember;
-
-
-static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+				 List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items);
-static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+				  List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				  int maxOpNumber, int maxProcNumber,
 				  List *items);
 static void processTypesSpec(List *args, Oid *lefttype, Oid *righttype);
@@ -675,6 +665,9 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	storeProcedures(stmt->opfamilyname, amoid, opfamilyoid,
 					opclassoid, procedures, false);
 
+	/* let event triggers know what happened */
+	EventTriggerCollectCreateOpClass(stmt, opclassoid, operators, procedures);
+
 	/*
 	 * Create dependencies for the opclass proper.  Note: we do not create a
 	 * dependency link to the AM, because we don't currently support DROP
@@ -822,11 +815,11 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
 	 * ADD and DROP cases need separate code from here on down.
 	 */
 	if (stmt->isDrop)
-		AlterOpFamilyDrop(stmt->opfamilyname, amoid, opfamilyoid,
+		AlterOpFamilyDrop(stmt, stmt->opfamilyname, amoid, opfamilyoid,
 						  maxOpNumber, maxProcNumber,
 						  stmt->items);
 	else
-		AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid,
+		AlterOpFamilyAdd(stmt, stmt->opfamilyname, amoid, opfamilyoid,
 						 maxOpNumber, maxProcNumber,
 						 stmt->items);
 
@@ -837,7 +830,8 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
  * ADD part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+				 List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items)
 {
@@ -962,13 +956,18 @@ AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				   InvalidOid, operators, true);
 	storeProcedures(opfamilyname, amoid, opfamilyoid,
 					InvalidOid, procedures, true);
+
+	/* make information available to event triggers */
+	EventTriggerCollectAlterOpFam(stmt, opfamilyoid,
+								  operators, procedures);
 }
 
 /*
  * DROP part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+				  List *opfamilyname, Oid amoid, Oid opfamilyoid,
 				  int maxOpNumber, int maxProcNumber,
 				  List *items)
 {
@@ -1035,6 +1034,10 @@ AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 	 */
 	dropOperators(opfamilyname, amoid, opfamilyoid, operators);
 	dropProcedures(opfamilyname, amoid, opfamilyoid, procedures);
+
+	/* make information available to event triggers */
+	EventTriggerCollectAlterOpFam(stmt, opfamilyoid,
+								  operators, procedures);
 }
 
 
@@ -1673,7 +1676,7 @@ RemoveAmProcEntryById(Oid entryOid)
 	heap_close(rel, RowExclusiveLock);
 }
 
-static char *
+char *
 get_am_name(Oid amOid)
 {
 	HeapTuple	tup;
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index c090ed2..5a7beff 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -25,6 +25,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/schemacmds.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
@@ -52,6 +53,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	Oid			saved_uid;
 	int			save_sec_context;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
 
@@ -143,6 +145,16 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	PushOverrideSearchPath(overridePath);
 
 	/*
+	 * Report the new schema to possibly interested event triggers.  Note we
+	 * must do this here and not in ProcessUtilitySlow because otherwise the
+	 * objects created below are reported before the schema, which would be
+	 * wrong.
+	 */
+	ObjectAddressSet(address, NamespaceRelationId, namespaceId);
+	EventTriggerCollectSimpleCommand(address, InvalidObjectAddress,
+									 (Node *) stmt);
+
+	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
 	 * reorganize them into a sequentially executable order with no forward
 	 * references.  Note that the result is still a list of raw parsetrees ---
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 06e4332..78d5909 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2781,6 +2781,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerAlterTableRelid(relid);
+
 	ATController(NULL, rel, cmds, recurse, lockmode);
 }
 
@@ -3668,6 +3670,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	(void) address;
 
 	/*
+	 * Report the subcommand to interested event triggers.
+	 */
+	EventTriggerCollectAlterTableSubcmd((Node *) cmd, RelationGetRelid(rel),
+										address);
+
+	/*
 	 * Bump the command counter to ensure the next subcommand in the sequence
 	 * can see the changes so far
 	 */
@@ -9702,7 +9710,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		cmds = lappend(cmds, cmd);
 
+		EventTriggerAlterTableStart((Node *) stmt);
+		/* OID is set by AlterTableInternal */
 		AlterTableInternal(lfirst_oid(l), cmds, false);
+		EventTriggerAlterTableEnd();
 	}
 
 	return new_tablespaceoid;
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 4c404e7..ff90040 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_func.h"
@@ -1442,6 +1443,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 			}
 		}
 	}
+
+	EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict);
 }
 
 /*
@@ -1509,6 +1512,8 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
 
 		i++;
 	}
+
+	EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
 }
 
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 029761e..457735e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3879,6 +3879,7 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 {
 	AlterTSConfigurationStmt *newnode = makeNode(AlterTSConfigurationStmt);
 
+	COPY_SCALAR_FIELD(kind);
 	COPY_NODE_FIELD(cfgname);
 	COPY_NODE_FIELD(tokentype);
 	COPY_NODE_FIELD(dicts);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 190e50a..d830c4c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1993,6 +1993,7 @@ static bool
 _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 							   const AlterTSConfigurationStmt *b)
 {
+	COMPARE_SCALAR_FIELD(kind);
 	COMPARE_NODE_FIELD(cfgname);
 	COMPARE_NODE_FIELD(tokentype);
 	COMPARE_NODE_FIELD(dicts);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5818858..7da8fc5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8912,6 +8912,7 @@ AlterTSConfigurationStmt:
 			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_ADD_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = $11;
@@ -8922,6 +8923,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = $11;
@@ -8932,6 +8934,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name any_with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_REPLACE_DICT;
 					n->cfgname = $5;
 					n->tokentype = NIL;
 					n->dicts = list_make2($9,$11);
@@ -8942,6 +8945,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name any_with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = list_make2($11,$13);
@@ -8952,6 +8956,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING FOR name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_DROP_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->missing_ok = false;
@@ -8960,6 +8965,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING IF_P EXISTS FOR name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_DROP_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $11;
 					n->missing_ok = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index fd09d3a..06bfe35 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -887,7 +887,9 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	bool		commandCollected = false;
 	ObjectAddress address;
+	ObjectAddress secondaryObject = InvalidObjectAddress;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -906,6 +908,11 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * EventTriggerCollectSimpleCommand called by
+				 * CreateSchemaCommand
+				 */
+				commandCollected = true;
 				break;
 
 			case T_CreateStmt:
@@ -932,6 +939,9 @@ ProcessUtilitySlow(Node *parsetree,
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
 													 InvalidOid, NULL);
+							EventTriggerCollectSimpleCommand(address,
+															 secondaryObject,
+															 stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -964,10 +974,17 @@ ProcessUtilitySlow(Node *parsetree,
 													 InvalidOid, NULL);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
+							EventTriggerCollectSimpleCommand(address,
+															 secondaryObject,
+															 stmt);
 						}
 						else
 						{
-							/* Recurse for anything else */
+							/*
+							 * Recurse for anything else.  Note the recursive
+							 * call will stash the objects so created into our
+							 * event trigger context.
+							 */
 							ProcessUtility(stmt,
 										   queryString,
 										   PROCESS_UTILITY_SUBCOMMAND,
@@ -980,6 +997,12 @@ ProcessUtilitySlow(Node *parsetree,
 						if (lnext(l) != NULL)
 							CommandCounterIncrement();
 					}
+
+					/*
+					 * The multiple commands generated here are stashed
+					 * individually, so disable collection below.
+					 */
+					commandCollected = true;
 				}
 				break;
 
@@ -1006,6 +1029,10 @@ ProcessUtilitySlow(Node *parsetree,
 						stmts = transformAlterTableStmt(relid, atstmt,
 														queryString);
 
+						/* ... ensure we have an event trigger context ... */
+						EventTriggerAlterTableStart(parsetree);
+						EventTriggerAlterTableRelid(relid);
+
 						/* ... and do it */
 						foreach(l, stmts)
 						{
@@ -1019,25 +1046,41 @@ ProcessUtilitySlow(Node *parsetree,
 							}
 							else
 							{
-								/* Recurse for anything else */
+								/*
+								 * Recurse for anything else.  If we need to do
+								 * so, "close" the current complex-command set,
+								 * and start a new one at the bottom; this is
+								 * needed to ensure the ordering of queued
+								 * commands is consistent with the way they are
+								 * executed here.
+								 */
+								EventTriggerAlterTableEnd();
 								ProcessUtility(stmt,
 											   queryString,
 											   PROCESS_UTILITY_SUBCOMMAND,
 											   params,
 											   None_Receiver,
 											   NULL);
+								EventTriggerAlterTableStart(parsetree);
+								EventTriggerAlterTableRelid(relid);
 							}
 
 							/* Need CCI between commands */
 							if (lnext(l) != NULL)
 								CommandCounterIncrement();
 						}
+
+						/* done */
+						EventTriggerAlterTableEnd();
 					}
 					else
 						ereport(NOTICE,
 						  (errmsg("relation \"%s\" does not exist, skipping",
 								  atstmt->relation->relname)));
 				}
+
+				/* ALTER TABLE stashes commands internally */
+				commandCollected = true;
 				break;
 
 			case T_AlterDomainStmt:
@@ -1056,31 +1099,37 @@ ProcessUtilitySlow(Node *parsetree,
 							 * Recursively alter column default for table and,
 							 * if requested, for descendants
 							 */
-							AlterDomainDefault(stmt->typeName,
-											   stmt->def);
+							address =
+								AlterDomainDefault(stmt->typeName,
+												   stmt->def);
 							break;
 						case 'N':		/* ALTER DOMAIN DROP NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   false);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   false);
 							break;
 						case 'O':		/* ALTER DOMAIN SET NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   true);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   true);
 							break;
 						case 'C':		/* ADD CONSTRAINT */
-							AlterDomainAddConstraint(stmt->typeName,
-													 stmt->def,
-													 NULL);
+							address =
+								AlterDomainAddConstraint(stmt->typeName,
+														 stmt->def,
+														 &secondaryObject);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
-							AlterDomainDropConstraint(stmt->typeName,
-													  stmt->name,
-													  stmt->behavior,
-													  stmt->missing_ok);
+							address =
+								AlterDomainDropConstraint(stmt->typeName,
+														  stmt->name,
+														  stmt->behavior,
+														  stmt->missing_ok);
 							break;
 						case 'V':		/* VALIDATE CONSTRAINT */
-							AlterDomainValidateConstraint(stmt->typeName,
-														  stmt->name);
+							address =
+								AlterDomainValidateConstraint(stmt->typeName,
+															  stmt->name);
 							break;
 						default:		/* oops */
 							elog(ERROR, "unrecognized alter domain type: %d",
@@ -1100,41 +1149,46 @@ ProcessUtilitySlow(Node *parsetree,
 					switch (stmt->kind)
 					{
 						case OBJECT_AGGREGATE:
-							DefineAggregate(stmt->defnames, stmt->args,
-											stmt->oldstyle, stmt->definition,
-											queryString);
+							address =
+								DefineAggregate(stmt->defnames, stmt->args,
+												stmt->oldstyle,
+												stmt->definition, queryString);
 							break;
 						case OBJECT_OPERATOR:
 							Assert(stmt->args == NIL);
-							DefineOperator(stmt->defnames, stmt->definition);
+							address = DefineOperator(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TYPE:
 							Assert(stmt->args == NIL);
-							DefineType(stmt->defnames, stmt->definition);
+							address = DefineType(stmt->defnames,
+												  stmt->definition);
 							break;
 						case OBJECT_TSPARSER:
 							Assert(stmt->args == NIL);
-							DefineTSParser(stmt->defnames, stmt->definition);
+							address = DefineTSParser(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TSDICTIONARY:
 							Assert(stmt->args == NIL);
-							DefineTSDictionary(stmt->defnames,
-											   stmt->definition);
+							address = DefineTSDictionary(stmt->defnames,
+														  stmt->definition);
 							break;
 						case OBJECT_TSTEMPLATE:
 							Assert(stmt->args == NIL);
-							DefineTSTemplate(stmt->defnames,
-											 stmt->definition);
+							address = DefineTSTemplate(stmt->defnames,
+														stmt->definition);
 							break;
 						case OBJECT_TSCONFIGURATION:
 							Assert(stmt->args == NIL);
-							DefineTSConfiguration(stmt->defnames,
-												  stmt->definition,
-												  NULL);
+							address = DefineTSConfiguration(stmt->defnames,
+															 stmt->definition,
+															 &secondaryObject);
 							break;
 						case OBJECT_COLLATION:
 							Assert(stmt->args == NIL);
-							DefineCollation(stmt->defnames, stmt->definition);
+							address = DefineCollation(stmt->defnames,
+													   stmt->definition);
 							break;
 						default:
 							elog(ERROR, "unrecognized define stmt type: %d",
@@ -1175,204 +1229,258 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
-					DefineIndex(relid,	/* OID of heap relation */
-								stmt,
-								InvalidOid,		/* no predefined OID */
-								false,	/* is_alter_table */
-								true,	/* check_rights */
-								false,	/* skip_build */
-								false); /* quiet */
+					EventTriggerAlterTableStart(parsetree);
+					address =
+						DefineIndex(relid,	/* OID of heap relation */
+									stmt,
+									InvalidOid,		/* no predefined OID */
+									false,	/* is_alter_table */
+									true,	/* check_rights */
+									false,	/* skip_build */
+									false); /* quiet */
+					/*
+					 * Add the CREATE INDEX node itself to stash right away; if
+					 * there were any commands stashed in the ALTER TABLE code,
+					 * we need them to appear after this one.
+					 */
+					EventTriggerCollectSimpleCommand(address, secondaryObject,
+													 parsetree);
+					commandCollected = true;
+					EventTriggerAlterTableEnd();
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				address = CreateExtension((CreateExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				address = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
-											   NULL);
+				address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+														  &secondaryObject);
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				address = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
 				break;
 
 			case T_AlterFdwStmt:
-				AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				address = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				address = CreateForeignServer((CreateForeignServerStmt *) parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
-				AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				address = AlterForeignServer((AlterForeignServerStmt *) parsetree);
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				address = CreateUserMapping((CreateUserMappingStmt *) parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
-				AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				address = AlterUserMapping((AlterUserMappingStmt *) parsetree);
 				break;
 
 			case T_DropUserMappingStmt:
 				RemoveUserMapping((DropUserMappingStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_ImportForeignSchemaStmt:
 				ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+				/* commands are stashed inside ImportForeignSchema */
+				commandCollected = true;
 				break;
 
 			case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					address = DefineCompositeType(stmt->typevar,
+												  stmt->coldeflist);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				address = DefineEnum((CreateEnumStmt *) parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				address = DefineRange((CreateRangeStmt *) parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				address = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerAlterTableStart(parsetree);
+				address = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerCollectSimpleCommand(address, secondaryObject,
+												 parsetree);
+				/* stashed internally */
+				commandCollected = true;
+				EventTriggerAlterTableEnd();
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				address = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				address = AlterFunction((AlterFunctionStmt *) parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				address = DefineRule((RuleStmt *) parsetree, queryString);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				address = DefineSequence((CreateSeqStmt *) parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				address = AlterSequence((AlterSeqStmt *) parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
-								   queryString, params, completionTag);
+				/*
+				 * REFRSH CONCURRENTLY executes some DDL commands internally.
+				 * Inhibit DDL command collection here to avoid those commands
+				 * from showing up in the deparsed command queue.  The refresh
+				 * command itself is queued, which is enough.
+				 */
+				EventTriggerInhibitCommandCollection();
+				PG_TRY();
+				{
+					address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+												 queryString, params, completionTag);
+				}
+				PG_CATCH();
+				{
+					EventTriggerUndoInhibitCommandCollection();
+					PG_RE_THROW();
+				}
+				PG_END_TRY();
+				EventTriggerUndoInhibitCommandCollection();
 				break;
 
 			case T_CreateTrigStmt:
-				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
-									 InvalidOid, InvalidOid, InvalidOid,
-									 InvalidOid, false);
+				address = CreateTrigger((CreateTrigStmt *) parsetree,
+										 queryString, InvalidOid, InvalidOid,
+										 InvalidOid, InvalidOid, false);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				address = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				address = DefineDomain((CreateDomainStmt *) parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				address = CreateConversionCommand((CreateConversionStmt *) parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				address = CreateCast((CreateCastStmt *) parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				address = DefineOpClass((CreateOpClassStmt *) parsetree);
+				/* command is stashed in DefineOpClass */
+				commandCollected = true;
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				address = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
 				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				/* commands are stashed in AlterOpFamily */
+				commandCollected = true;
 				break;
 
 			case T_AlterTSDictionaryStmt:
-				AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				address = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
 				break;
 
 			case T_AlterTableMoveAllStmt:
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
+				/* commands are stashed in AlterTableMoveAll */
+				commandCollected = true;
 				break;
 
 			case T_DropStmt:
 				ExecDropStmt((DropStmt *) parsetree, isTopLevel);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree);
+				address = ExecRenameStmt((RenameStmt *) parsetree);
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
-										  NULL);
+				address =
+					ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
+											  &secondaryObject);
 				break;
 
 			case T_AlterOwnerStmt:
-				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
 			case T_CommentStmt:
-				CommentObject((CommentStmt *) parsetree);
+				address = CommentObject((CommentStmt *) parsetree);
 				break;
 
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
+				/* commands are stashed in ExecGrantStmt_oids */
+				commandCollected = true;
 				break;
 
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_AlterDefaultPrivilegesStmt:
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
+				EventTriggerCollectAlterDefPrivs((AlterDefaultPrivilegesStmt *) parsetree);
+				commandCollected = true;
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
-				CreatePolicy((CreatePolicyStmt *) parsetree);
+				address = CreatePolicy((CreatePolicyStmt *) parsetree);
 				break;
 
 			case T_AlterPolicyStmt:		/* ALTER POLICY */
-				AlterPolicy((AlterPolicyStmt *) parsetree);
+				address = AlterPolicy((AlterPolicyStmt *) parsetree);
 				break;
 
 			case T_SecLabelStmt:
-				ExecSecLabelStmt((SecLabelStmt *) parsetree);
+				address = ExecSecLabelStmt((SecLabelStmt *) parsetree);
 				break;
 
 			default:
@@ -1381,6 +1489,14 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 		}
 
+		/*
+		 * Remember the object so that ddl_command_end event triggers have
+		 * access to it.
+		 */
+		if (!commandCollected)
+			EventTriggerCollectSimpleCommand(address, secondaryObject,
+											 parsetree);
+
 		if (isCompleteQuery)
 		{
 			EventTriggerSQLDrop(parsetree);
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 2f0f0a1..9b674ce 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
 	return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
@@ -323,7 +326,6 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
-
 /*
  * Add typmod decoration to the basic type name
  */
@@ -338,7 +340,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,7 +352,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/include/catalog/opfam_internal.h b/src/include/catalog/opfam_internal.h
new file mode 100644
index 0000000..f01dcbe
--- /dev/null
+++ b/src/include/catalog/opfam_internal.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * opfam_internal.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/opfam_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OPFAM_INTERNAL_H
+#define OPFAM_INTERNAL_H
+
+/*
+ * We use lists of this struct type to keep track of both operators and
+ * procedures while building or adding to an opfamily.
+ */
+typedef struct
+{
+	Oid			object;			/* operator or support proc's OID */
+	int			number;			/* strategy or support proc number */
+	Oid			lefttype;		/* lefttype */
+	Oid			righttype;		/* righttype */
+	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
+} OpFamilyMember;
+
+#endif		/* OPFAM_INTERNAL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 62187fb..34b7325 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5126,6 +5126,8 @@ DATA(insert OID = 4566 (  pg_event_trigger_table_rewrite_oid	PGNSP PGUID 12 1 0
 DESCR("return Oid of the table getting rewritten");
 DATA(insert OID = 4567 (  pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ ));
 DESCR("return reason code for table getting rewritten");
+DATA(insert OID = 3590 (  pg_event_trigger_ddl_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,88}" "{o,o,o,o,o,o,o,o,o}" "{classid,objid,objsubid,command_tag,object_type,schema,identity,in_extension,command}" _null_ pg_event_trigger_ddl_commands _null_ _null_ _null_ ));
+DESCR("list DDL actions being executed by the current command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 0a900dd..b340f06 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -364,6 +364,11 @@ DATA(insert OID = 194 ( pg_node_tree	PGNSP PGUID -1 f b S f t \054 0 0 0 pg_node
 DESCR("string representing an internal node tree");
 #define PGNODETREEOID	194
 
+/* DATA(insert OID = 88 ( pg_ddl_command PGNSP PGUID -1 f b P f \054 0 0 0 pg_ddl_command_in pg_ddl_command_out pg_ddl_command_recv pg_ddl_command_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ )); */
+DATA(insert OID = 88 ( pg_ddl_command   PGNSP PGUID SIZEOF_POINTER t b P f t \054 0 0 0 - - - - - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("internal type for passing CollectedCommand");
+#define PGDDLCOMMANDOID 88
+
 /* OIDS 200 - 299 */
 
 DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 595f93f..89a7e49 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -87,6 +87,7 @@ extern void IsThereOpClassInNamespace(const char *opcname, Oid opcmethod,
 extern void IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod,
 						   Oid opfnamespace);
 extern Oid	get_am_oid(const char *amname, bool missing_ok);
+extern char *get_am_name(Oid amOid);
 extern Oid	get_opclass_oid(Oid amID, List *opclassname, bool missing_ok);
 extern Oid	get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok);
 
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 7eb2156..fc691b8 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -17,6 +17,8 @@
 #include "catalog/objectaddress.h"
 #include "catalog/pg_event_trigger.h"
 #include "nodes/parsenodes.h"
+#include "utils/aclchk_internal.h"
+#include "tcop/deparse_utility.h"
 
 typedef struct EventTriggerData
 {
@@ -60,4 +62,28 @@ extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 							 bool original, bool normal);
 
+extern void EventTriggerInhibitCommandCollection(void);
+extern void EventTriggerUndoInhibitCommandCollection(void);
+
+extern void EventTriggerCollectSimpleCommand(ObjectAddress address,
+								 ObjectAddress secondaryObject,
+								 Node *parsetree);
+
+extern void EventTriggerAlterTableStart(Node *parsetree);
+extern void EventTriggerAlterTableRelid(Oid objectId);
+extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd, Oid relid,
+								  ObjectAddress address);
+extern void EventTriggerAlterTableEnd(void);
+
+extern void EventTriggerCollectGrant(InternalGrant *istmt);
+extern void EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt,
+							  Oid opfamoid, List *operators,
+							  List *procedures);
+extern void EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt,
+								 Oid opcoid, List *operators,
+								 List *procedures);
+extern void EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt,
+								 Oid cfgId, Oid *dictIds, int ndicts);
+extern void EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 40ecea2..0423350 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -24,7 +24,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0e257ac..51a9c5b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2850,9 +2850,19 @@ typedef struct AlterTSDictionaryStmt
 /*
  * TS Configuration stmts: DefineStmt, RenameStmt and DropStmt are default
  */
+typedef enum AlterTSConfigType
+{
+	ALTER_TSCONFIG_ADD_MAPPING,
+	ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN,
+	ALTER_TSCONFIG_REPLACE_DICT,
+	ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN,
+	ALTER_TSCONFIG_DROP_MAPPING
+} AlterTSConfigType;
+
 typedef struct AlterTSConfigurationStmt
 {
 	NodeTag		type;
+	AlterTSConfigType	kind;	/* ALTER_TSCONFIG_ADD_MAPPING, etc */
 	List	   *cfgname;		/* qualified name (list of Value strings) */
 
 	/*
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..1172e57
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,106 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+#include "access/attnum.h"
+#include "catalog/objectaddress.h"
+#include "nodes/nodes.h"
+#include "utils/aclchk_internal.h"
+
+
+/*
+ * Support for keeping track of collected commands.
+ */
+typedef enum CollectedCommandType
+{
+	SCT_Simple,
+	SCT_AlterTable,
+	SCT_Grant,
+	SCT_AlterOpFamily,
+	SCT_AlterDefaultPrivileges,
+	SCT_CreateOpClass,
+	SCT_AlterTSConfig
+} CollectedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct CollectedATSubcmd
+{
+	ObjectAddress	address; /* affected column, constraint, index, ... */
+	Node		   *parsetree;
+} CollectedATSubcmd;
+
+typedef struct CollectedCommand
+{
+	CollectedCommandType type;
+	bool		in_extension;
+	Node	   *parsetree;
+
+	union
+	{
+		/* most commands */
+		struct
+		{
+			ObjectAddress address;
+			ObjectAddress secondaryObject;
+		} simple;
+
+		/* ALTER TABLE, and internal uses thereof */
+		struct
+		{
+			Oid		objectId;
+			Oid		classId;
+			List   *subcmds;
+		} alterTable;
+
+		/* GRANT / REVOKE */
+		struct
+		{
+			InternalGrant *istmt;
+			const char *type;
+		} grant;
+
+		/* ALTER OPERATOR FAMILY */
+		struct
+		{
+			ObjectAddress address;
+			List   *operators;
+			List   *procedures;
+		} opfam;
+
+		/* CREATE OPERATOR CLASS */
+		struct
+		{
+			ObjectAddress address;
+			List   *operators;
+			List   *procedures;
+		} createopc;
+
+		/* ALTER TEXT SEARCH CONFIGURATION ADD/ALTER/DROP MAPPING */
+		struct
+		{
+			ObjectAddress address;
+			Oid	   *dictIds;
+			int		ndicts;
+		} atscfg;
+
+		/* ALTER DEFAULT PRIVILEGES */
+		struct
+		{
+			char   *objtype;
+		} defprivs;
+	} d;
+} CollectedCommand;
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/aclchk_internal.h b/src/include/utils/aclchk_internal.h
new file mode 100644
index 0000000..bac2728
--- /dev/null
+++ b/src/include/utils/aclchk_internal.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * aclchk_internal.h
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/aclchk_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLCHK_INTERNAL_H
+#define ACLCHK_INTERNAL_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+/*
+ * The information about one Grant/Revoke statement, in internal format: object
+ * and grantees names have been turned into Oids, the privilege list is an
+ * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
+ * all_privs is true, 'privileges' will be internally set to the right kind of
+ * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
+ * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
+ */
+typedef struct
+{
+	bool		is_grant;
+	GrantObjectType objtype;
+	List	   *objects;
+	bool		all_privs;
+	AclMode		privileges;
+	List	   *col_privs;
+	List	   *grantees;
+	bool		grant_option;
+	DropBehavior behavior;
+} InternalGrant;
+
+
+#endif	/* ACLCHK_INTERNAL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 33a453f..dc884ad 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1218,6 +1218,7 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
-- 
2.1.4

0002-changes-to-core-to-support-the-deparse-extension.patchtext/x-diff; charset=us-asciiDownload
>From 7d474734391ac183fdfe3629a3dc2f39511a86a3 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 7 Apr 2015 15:01:06 -0300
Subject: [PATCH 2/3] changes to core to support the deparse extension

---
 src/backend/commands/seclabel.c     |   5 +
 src/backend/commands/sequence.c     |  34 +++
 src/backend/commands/tablecmds.c    |   3 +-
 src/backend/utils/adt/format_type.c | 127 +++++++++
 src/backend/utils/adt/regproc.c     | 101 +++++--
 src/backend/utils/adt/ruleutils.c   | 514 ++++++++++++++++++++++++++++++++----
 src/include/commands/sequence.h     |   1 +
 src/include/utils/builtins.h        |   4 +
 src/include/utils/ruleutils.h       |  21 +-
 9 files changed, 729 insertions(+), 81 deletions(-)

diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 1ef98ce..aa4de97 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -63,6 +63,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("must specify provider when multiple security label providers have been loaded")));
 		provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
 	}
 	else
 	{
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6d316d6..73cf4da 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1503,6 +1503,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/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 78d5909..49f6a9e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8131,7 +8131,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/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 9b674ce..7821f25 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -327,6 +327,133 @@ format_type_internal(Oid type_oid, int32 typemod,
 }
 
 /*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name in this case.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *typarray)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID ||
+		type_oid == TIMEOID ||
+		type_oid == TIMETZOID)
+	{
+		*typarray = false;
+
+peculiar_typmod:
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPTZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmod, so fall through */
+			case TIMESTAMPOID:
+				*typname = pstrdup("TIMESTAMP");
+				break;
+			case TIMETZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIME WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmode, so fall through */
+			case TIMEOID:
+				*typname = pstrdup("TIME");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		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;
+		*typarray = true;
+
+		/*
+		 * If it's an array of one of the types with special typmod rules,
+		 * have the element type be processed as above, but now with typarray
+		 * set to true.
+		 */
+		if (type_oid == INTERVALOID ||
+			type_oid == TIMESTAMPTZOID ||
+			type_oid == TIMESTAMPOID ||
+			type_oid == TIMETZOID ||
+			type_oid == TIMEOID)
+			goto peculiar_typmod;
+	}
+	else
+		*typarray = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
+
+/*
  * Add typmod decoration to the basic type name
  */
 static char *
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 11d663b..d70a5ce 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -42,7 +42,11 @@
 #include "utils/tqual.h"
 
 static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
+static char *format_procedure_internal(Oid procedure_oid, bool force_qualify,
+						  bool args_only);
+static void format_procedure_args_internal(Form_pg_proc procform,
+							   StringInfo buf, bool force_qualify);
+
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -363,13 +367,36 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_internal(procedure_oid, false, false);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_internal(procedure_oid, true, false);
+}
+
+/*
+ * format_procedure_args	- converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+	StringInfoData buf;
+	HeapTuple	proctup;
+	Form_pg_proc procform;
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+	initStringInfo(&buf);
+	format_procedure_args_internal(procform, &buf, force_qualify);
+
+	ReleaseSysCache(proctup);
+
+	return buf.data;
 }
 
 /*
@@ -380,7 +407,7 @@ format_procedure_qualified(Oid procedure_oid)
  * qualified if the function is not in path.
  */
 static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+format_procedure_internal(Oid procedure_oid, bool force_qualify, bool args_only)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -391,8 +418,6 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 	{
 		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
 		char	   *proname = NameStr(procform->proname);
-		int			nargs = procform->pronargs;
-		int			i;
 		char	   *nspname;
 		StringInfoData buf;
 
@@ -400,29 +425,24 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		initStringInfo(&buf);
 
-		/*
-		 * Would this proc be found (given the right args) by regprocedurein?
-		 * If not, or if caller requests it, we need to qualify it.
-		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
-			nspname = NULL;
-		else
-			nspname = get_namespace_name(procform->pronamespace);
-
-		appendStringInfo(&buf, "%s(",
-						 quote_qualified_identifier(nspname, proname));
-		for (i = 0; i < nargs; i++)
+		if (!args_only)
 		{
-			Oid			thisargtype = procform->proargtypes.values[i];
-
-			if (i > 0)
-				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf,
-								   force_qualify ?
-								   format_type_be_qualified(thisargtype) :
-								   format_type_be(thisargtype));
+			/*
+			 * Would this proc be found (given the right args) by
+			 * regprocedurein?  If not, or if caller requests it, we need to
+			 * qualify it.
+			 */
+			if (!force_qualify && FunctionIsVisible(procedure_oid))
+				nspname = NULL;
+			else
+				nspname = get_namespace_name(procform->pronamespace);
+
+			appendStringInfo(&buf, "%s",
+							 quote_qualified_identifier(nspname, proname));
 		}
-		appendStringInfoChar(&buf, ')');
+
+		/* add the attributes */
+		format_procedure_args_internal(procform, &buf, force_qualify);
 
 		result = buf.data;
 
@@ -439,6 +459,33 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 }
 
 /*
+ * Append the parenthised arguments of the given pg_proc row into the output
+ * buffer.  force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+							   bool force_qualify)
+{
+	int			i;
+	int			nargs = procform->pronargs;
+
+	appendStringInfoChar(buf, '(');
+	for (i = 0; i < nargs; i++)
+	{
+		Oid			thisargtype = procform->proargtypes.values[i];
+
+		if (i > 0)
+			appendStringInfoChar(buf, ',');
+		appendStringInfoString(buf,
+							   force_qualify ?
+							   format_type_be_qualified(thisargtype) :
+							   format_type_be(thisargtype));
+	}
+	appendStringInfoChar(buf, ')');
+}
+
+/*
  * Output a objname/objargs representation for the procedure with the
  * given OID.  If it doesn't exist, an error is thrown.
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 29b5b1b..4ed7f60 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -449,6 +449,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)
@@ -599,6 +670,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
  */
@@ -673,6 +751,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def	- Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * get_triggerdef			- Get the definition of a trigger
  * ----------
@@ -822,59 +915,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char	   *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.ctes = NIL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
@@ -915,6 +961,63 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char		relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+	initStringInfo(&buf);
+
+	relkind = get_rel_relkind(trigrec->tgrelid);
+
+	/* Build minimal OLD and NEW RTEs for the rel */
+	oldrte = makeNode(RangeTblEntry);
+	oldrte->rtekind = RTE_RELATION;
+	oldrte->relid = trigrec->tgrelid;
+	oldrte->relkind = relkind;
+	oldrte->alias = makeAlias("old", NIL);
+	oldrte->eref = oldrte->alias;
+	oldrte->lateral = false;
+	oldrte->inh = false;
+	oldrte->inFromCl = true;
+
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_RELATION;
+	newrte->relid = trigrec->tgrelid;
+	newrte->relkind = relkind;
+	newrte->alias = makeAlias("new", NIL);
+	newrte->eref = newrte->alias;
+	newrte->lateral = false;
+	newrte->inh = false;
+	newrte->inFromCl = true;
+
+	/* Build two-element rtable */
+	memset(&dpns, 0, sizeof(dpns));
+	dpns.rtable = list_make2(oldrte, newrte);
+	dpns.ctes = NIL;
+	set_rtable_names(&dpns, NIL, NULL);
+	set_simple_column_names(&dpns);
+
+	/* Set up context with one-deep namespace stack */
+	context.buf = &buf;
+	context.namespaces = list_make1(&dpns);
+	context.windowClause = NIL;
+	context.windowTList = NIL;
+	context.varprefix = true;
+	context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+	context.wrapColumn = WRAP_COLUMN_DEFAULT;
+	context.indentLevel = PRETTYINDENT_STD;
+
+	get_rule_expr(whenClause, &context, false);
+
+	return buf.data;
+}
+
 /* ----------
  * get_indexdef			- Get the definition of an index
  *
@@ -978,6 +1081,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,
@@ -1257,6 +1362,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
@@ -1291,9 +1635,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);
 }
 
 /*
@@ -9401,3 +9745,69 @@ 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;
+}
+
+/*
+ * Return the default value of a domain.
+ */
+char *
+DomainGetDefault(HeapTuple domTup)
+{
+	Datum	def;
+	Node   *defval;
+	char   *defstr;
+	bool	isnull;
+
+	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+							 &isnull);
+	if (isnull)
+		elog(ERROR, "domain \"%s\" does not have a default value",
+			 NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+	defval = stringToNode(TextDatumGetCString(def));
+	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
+									   false, false, 0, 0);
+
+	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/commands/sequence.h b/src/include/commands/sequence.h
index 44862bb..8d8e556 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -72,6 +72,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 ObjectAddress DefineSequence(CreateSeqStmt *stmt);
 extern ObjectAddress AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index dc884ad..5f18f36 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -644,6 +644,7 @@ extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
 					  List **objargs);
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
@@ -1087,6 +1088,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);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index fed9c7b..2c020e9 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,6 +13,7 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
@@ -20,8 +21,20 @@
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+						  Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+					   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
 
-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);
@@ -31,5 +44,11 @@ extern List *set_deparse_context_planstate(List *dpcontext,
 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);
+
+extern char *DomainGetDefault(HeapTuple domTup);
 
 #endif	/* RULEUTILS_H */
-- 
2.1.4

0003-ddl_deparse-extension.patchtext/x-diff; charset=us-asciiDownload
>From 58c23ac4a0db21efc9b3852f2ecbfd8c34bb3528 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 7 Apr 2015 15:42:07 -0300
Subject: [PATCH 3/3] ddl_deparse extension

---
 contrib/ddl_deparse/Makefile             |   20 +
 contrib/ddl_deparse/ddl_deparse--1.0.sql |   14 +
 contrib/ddl_deparse/ddl_deparse.c        | 7172 ++++++++++++++++++++++++++++++
 contrib/ddl_deparse/ddl_deparse.control  |    5 +
 contrib/ddl_deparse/ddl_deparse.h        |    8 +
 contrib/ddl_deparse/ddl_json.c           |  715 +++
 6 files changed, 7934 insertions(+)
 create mode 100644 contrib/ddl_deparse/Makefile
 create mode 100644 contrib/ddl_deparse/ddl_deparse--1.0.sql
 create mode 100644 contrib/ddl_deparse/ddl_deparse.c
 create mode 100644 contrib/ddl_deparse/ddl_deparse.control
 create mode 100644 contrib/ddl_deparse/ddl_deparse.h
 create mode 100644 contrib/ddl_deparse/ddl_json.c

diff --git a/contrib/ddl_deparse/Makefile b/contrib/ddl_deparse/Makefile
new file mode 100644
index 0000000..5e4755c
--- /dev/null
+++ b/contrib/ddl_deparse/Makefile
@@ -0,0 +1,20 @@
+# contrib/ddl_deparse/Makefile
+
+MODULE_big = ddl_deparse
+OBJS = ddl_deparse.o ddl_json.o
+
+EXTENSION = ddl_deparse
+DATA = ddl_deparse--1.0.sql
+
+PGFILEDESC = "ddl_deparse - deparsing support for DDL"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/ddl_deparse
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/ddl_deparse/ddl_deparse--1.0.sql b/contrib/ddl_deparse/ddl_deparse--1.0.sql
new file mode 100644
index 0000000..53ad02e
--- /dev/null
+++ b/contrib/ddl_deparse/ddl_deparse--1.0.sql
@@ -0,0 +1,14 @@
+/* contrib/ddl_deparse/ddl_deparse--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION ddl_deparse" to load this file. \quit
+
+CREATE FUNCTION ddl_deparse_to_json(pg_ddl_command)
+RETURNS json
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
+
+CREATE FUNCTION ddl_deparse_expand_command(json)
+RETURNS text
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT IMMUTABLE;
diff --git a/contrib/ddl_deparse/ddl_deparse.c b/contrib/ddl_deparse/ddl_deparse.c
new file mode 100644
index 0000000..75b80b5
--- /dev/null
+++ b/contrib/ddl_deparse/ddl_deparse.c
@@ -0,0 +1,7172 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_deparse.c
+ *	  Functions to convert utility commands to machine-parseable
+ *	  representation
+ *
+ * Portions Copyright (c) 1996-2015, 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
+ *	  contrib/ddl_deparse/ddl_deparse.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "ddl_deparse.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/heap.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "catalog/opfam_internal.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_cast.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_opclass.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_policy.h"
+#include "catalog/pg_proc.h"
+#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"
+#include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "foreign/foreign.h"
+#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"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/jsonb.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(ddl_deparse_to_json);
+
+/*
+ * Before they are turned into JSONB representation, each command is
+ * represented as an object tree, using the structs below.
+ */
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeInteger,
+	ObjTypeFloat,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjTree
+{
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+
+	union
+	{
+		bool		boolean;
+		char	   *string;
+		int64		integer;
+		float8		flt;
+		ObjTree	   *object;
+		List	   *array;
+	} value;
+	slist_node	node;
+} ObjElem;
+
+static ObjElem *new_null_object(void);
+static ObjElem *new_bool_object(bool value);
+static ObjElem *new_string_object(char *value);
+static ObjElem *new_object_object(ObjTree *value);
+static ObjElem *new_array_object(List *array);
+static ObjElem *new_integer_object(int64 value);
+static ObjElem *new_float_object(float8 value);
+static void append_null_object(ObjTree *tree, char *name);
+static void append_bool_object(ObjTree *tree, char *name, bool value);
+static void append_string_object(ObjTree *tree, char *name, char *value);
+static void append_object_object(ObjTree *tree, char *name, ObjTree *value);
+static void append_array_object(ObjTree *tree, char *name, List *array);
+static void append_integer_object(ObjTree *tree, char *name, int64 value);
+static void append_float_object(ObjTree *tree, char *name, float8 value);
+static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
+static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+static const char *stringify_objtype(ObjectType objtype);
+
+/*
+ * Allocate a new object tree to store parameter values.
+ */
+static ObjTree *
+new_objtree(void)
+{
+	ObjTree    *params;
+
+	params = palloc(sizeof(ObjTree));
+	params->numParams = 0;
+	slist_init(&params->params);
+
+	return params;
+}
+
+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one, a
+ * name (string), type (from the ObjType enum) and value must be supplied.  The
+ * value must match the type given; for instance, ObjTypeInteger requires an
+ * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of
+ * ObjElem), ObjTypeObject requires an ObjTree, and so on.  Each element type *
+ * must match the conversion specifier given in the format string, as described
+ * in ddl_deparse_expand_command, q.v.
+ *
+ * Note we don't have the luxury of sprintf-like compiler warnings for
+ * malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree();
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		char	   *name;
+		ObjType		type;
+		ObjElem	   *elem;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/*
+		 * 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:
+				elem = new_bool_object(va_arg(args, int));
+				break;
+			case ObjTypeString:
+				elem = new_string_object(va_arg(args, char *));
+				break;
+			case ObjTypeObject:
+				elem = new_object_object(va_arg(args, ObjTree *));
+				break;
+			case ObjTypeArray:
+				elem = new_array_object(va_arg(args, List *));
+				break;
+			case ObjTypeInteger:
+				elem = new_integer_object(va_arg(args, int64));
+				break;
+			case ObjTypeFloat:
+				elem = new_float_object(va_arg(args, double));
+				break;
+			case ObjTypeNull:
+				/* Null params don't have a value (obviously) */
+				elem = new_null_object();
+				break;
+			default:
+				elog(ERROR, "invalid ObjTree element type %d", type);
+		}
+
+		elem->name = name;
+		append_premade_object(tree, elem);
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/* Allocate a new parameter with a NULL value */
+static ObjElem *
+new_null_object(void)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = NULL;
+	param->objtype = ObjTypeNull;
+
+	return param;
+}
+
+/* Append a NULL object to a tree */
+static void
+append_null_object(ObjTree *tree, char *name)
+{
+	ObjElem    *param;
+
+	param = new_null_object();
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new boolean parameter */
+static ObjElem *
+new_bool_object(bool value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeBool;
+	param->value.boolean = value;
+
+	return param;
+}
+
+/* Append a boolean parameter to a tree */
+static void
+append_bool_object(ObjTree *tree, char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = new_bool_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new string object */
+static ObjElem *
+new_string_object(char *value)
+{
+	ObjElem    *param;
+
+	Assert(value);
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeString;
+	param->value.string = value;
+
+	return param;
+}
+
+/*
+ * Append a string parameter to a tree.
+ */
+static void
+append_string_object(ObjTree *tree, char *name, char *value)
+{
+	ObjElem	   *param;
+
+	Assert(name);
+	param = new_string_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+static ObjElem *
+new_integer_object(int64 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeInteger;
+	param->value.integer = value;
+
+	return param;
+}
+
+static ObjElem *
+new_float_object(float8 value)
+{
+	ObjElem	   *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeFloat;
+	param->value.flt = value;
+
+	return param;
+}
+
+/*
+ * Append an int64 parameter to a tree.
+ */
+static void
+append_integer_object(ObjTree *tree, char *name, int64 value)
+{
+	ObjElem	   *param;
+
+	param = new_integer_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/*
+ * Append a float8 parameter to a tree.
+ */
+static void
+append_float_object(ObjTree *tree, char *name, float8 value)
+{
+	ObjElem	   *param;
+
+	param = new_float_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new object parameter */
+static ObjElem *
+new_object_object(ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeObject;
+	param->value.object = value;
+
+	return param;
+}
+
+/* Append an object parameter to a tree */
+static void
+append_object_object(ObjTree *tree, char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	Assert(name);
+	param = new_object_object(value);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new array parameter */
+static ObjElem *
+new_array_object(List *array)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = NULL;
+	param->objtype = ObjTypeArray;
+	param->value.array = array;
+
+	return param;
+}
+
+/* Append an array parameter to a tree */
+static void
+append_array_object(ObjTree *tree, char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = new_array_object(array);
+	param->name = name;
+	append_premade_object(tree, param);
+}
+
+/* Append a preallocated parameter to a tree */
+static inline void
+append_premade_object(ObjTree *tree, ObjElem *elem)
+{
+	slist_push_head(&tree->params, &elem->node);
+	tree->numParams++;
+}
+
+/*
+ * Helper for objtree_to_jsonb: process an individual element from an object or
+ * an array into the output parse state.
+ */
+static void
+objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object,
+						 JsonbIteratorToken elem_token)
+{
+	ListCell   *cell;
+	JsonbValue	val;
+
+	switch (object->objtype)
+	{
+		case ObjTypeNull:
+			val.type = jbvNull;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeString:
+			val.type = jbvString;
+			val.val.string.len = strlen(object->value.string);
+			val.val.string.val = object->value.string;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeInteger:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeFloat:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(float8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeBool:
+			val.type = jbvBool;
+			val.val.boolean = object->value.boolean;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeObject:
+			/* recursively add the object into the existing parse state */
+			objtree_to_jsonb_rec(object->value.object, state);
+			break;
+
+		case ObjTypeArray:
+			pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+			foreach(cell, object->value.array)
+			{
+				ObjElem   *elem = lfirst(cell);
+
+				objtree_to_jsonb_element(state, elem, WJB_ELEM);
+			}
+			pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+			break;
+
+		default:
+			elog(ERROR, "unrecognized object type %d", object->objtype);
+			break;
+	}
+}
+
+/*
+ * Recursive helper for objtree_to_jsonb
+ */
+static JsonbValue *
+objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state)
+{
+	slist_iter	iter;
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	slist_foreach(iter, &tree->params)
+	{
+		ObjElem    *object = slist_container(ObjElem, node, iter.cur);
+		JsonbValue	key;
+
+		/* Push the key first */
+		key.type = jbvString;
+		key.val.string.len = strlen(object->name);
+		key.val.string.val = object->name;
+		pushJsonbValue(&state, WJB_KEY, &key);
+
+		/* Then process the value according to its type */
+		objtree_to_jsonb_element(state, object, WJB_VALUE);
+	}
+
+	return pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Create a JSONB representation from an ObjTree.
+ */
+static Jsonb *
+objtree_to_jsonb(ObjTree *tree)
+{
+	JsonbValue *value;
+
+	value = objtree_to_jsonb_rec(tree, NULL);
+	return JsonbValueToJsonb(value);
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{
+	ObjTree    *typeParam;
+	Oid			typnspid;
+	char	   *typnsp;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		typarray;
+
+	format_type_detailed(typeId, typmod,
+						 &typnspid, &typename, &typmodstr, &typarray);
+
+	if (!OidIsValid(typnspid))
+		typnsp = pstrdup("");
+	else if (isAnyTempNamespace(typnspid))
+		typnsp = pstrdup("pg_temp");
+	else
+		typnsp = get_namespace_name(typnspid);
+
+	/* We don't use new_objtree_VA here because types don't have a "fmt" */
+	typeParam = new_objtree();
+	append_string_object(typeParam, "schemaname", typnsp);
+	append_string_object(typeParam, "typename", typename);
+	append_string_object(typeParam, "typmod", typmodstr);
+	append_bool_object(typeParam, "typarray", typarray);
+
+	return typeParam;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements
+ *
+ * Elements "schemaname" and "objname" are set.  If the namespace OID
+ * corresponds to a temp schema, that's set to "pg_temp".
+ *
+ * The difference between those two element types is whether the objname will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static ObjTree *
+new_objtree_for_qualname(Oid nspid, char *name)
+{
+	ObjTree    *qualified;
+	char	   *namespace;
+
+	/*
+	 * We don't use new_objtree_VA here because these names don't have a "fmt"
+	 */
+	qualified = new_objtree();
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+	append_string_object(qualified, "schemaname", namespace);
+	append_string_object(qualified, "objname", pstrdup(name));
+
+	return qualified;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements, with the object specified
+ * by classId/objId
+ *
+ * Elements "schemaname" and "objname" are set.  If the object is a temporary
+ * object, the schema name is set to "pg_temp".
+ */
+static ObjTree *
+new_objtree_for_qualname_id(Oid classId, Oid objectId)
+{
+	ObjTree    *qualified;
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		objnsp;
+	Datum		objname;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	bool		isnull;
+
+	catalog = heap_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL namespace");
+	objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL name");
+
+	qualified = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+										 NameStr(*DatumGetName(objname)));
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+/*
+ * Helper routine for %{}R objects, with role specified by OID.  (ACL_ID_PUBLIC
+ * means to use "public").
+ */
+static ObjTree *
+new_objtree_for_role_id(Oid roleoid)
+{
+	ObjTree    *role;
+
+	role = new_objtree();
+	append_bool_object(role, "is_public", roleoid == ACL_ID_PUBLIC);
+
+	if (roleoid != ACL_ID_PUBLIC)
+	{
+		HeapTuple	roltup;
+		char	   *rolename;
+
+		roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleoid));
+		if (!HeapTupleIsValid(roltup))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("role with OID %u does not exist", roleoid)));
+
+		rolename = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname);
+		append_string_object(role, "rolename", pstrdup(rolename));
+		ReleaseSysCache(roltup);
+	}
+
+	return role;
+}
+
+/*
+ * Helper routine for %{}R objects, with role specified by name.
+ */
+static ObjTree *
+new_objtree_for_role(char *rolename)
+{
+	ObjTree	   *role;
+	bool		is_public;
+
+	role = new_objtree();
+	is_public = strcmp(rolename, "public") == 0;
+	append_bool_object(role, "is_public", is_public);
+	if (!is_public)
+		append_string_object(role, "rolename", rolename);
+
+	return role;
+}
+
+/*
+ * Helper routine for %{}R objects, with role specified by RoleSpec node.
+ * Special values such as ROLESPEC_CURRENT_USER are expanded to their final
+ * names.
+ */
+static ObjTree *
+new_objtree_for_rolespec(RoleSpec *spec)
+{
+	ObjTree	   *role;
+
+	role = new_objtree();
+	append_bool_object(role, "is_public",
+					   spec->roletype == ROLESPEC_PUBLIC);
+	if (spec->roletype != ROLESPEC_PUBLIC)
+		append_string_object(role, "rolename", get_rolespec_name((Node *) spec));
+
+	return role;
+}
+
+/*
+ * 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:
+			elog(ERROR, "unexpected persistence marking %c", persistence);
+			return "";		/* make compiler happy */
+	}
+}
+
+/*
+ * Form an ObjElem to be used as a single argument in an aggregate argument
+ * list
+ */
+static ObjElem *
+form_agg_argument(int idx, char *modes, char **names, Oid *types)
+{
+	ObjTree	   *arg;
+
+	arg = new_objtree_VA("%{mode}s %{name}s %{type}T", 0);
+
+	append_string_object(arg, "mode",
+						 (modes && modes[idx] == 'v') ? "VARIADIC" : "");
+	append_string_object(arg, "name", names ? names[idx] : "");
+	append_object_object(arg, "type",
+						 new_objtree_for_type(types[idx], -1));
+
+	return new_object_object(arg);
+}
+
+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;
+	Form_pg_operator op;
+	HeapTuple	tup;
+
+	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)));
+
+	/*
+	 * Add the argument list.  There are three cases to consider:
+	 *
+	 * 1. no arguments, in which case the signature is (*).
+	 *
+	 * 2. Not an ordered-set aggregate.  In this case there's one or more
+	 * arguments.
+	 *
+	 * 3. Ordered-set aggregates. These have zero or more direct arguments, and
+	 * one or more ordered arguments.
+	 *
+	 * We don't need to consider default values or table parameters, and the
+	 * only mode that we need to consider is VARIADIC.
+	 */
+
+	if (proc->pronargs == 0)
+	{
+		append_string_object(stmt, "agg type", "noargs");
+		append_string_object(stmt, "types", "*");
+	}
+	else if (!AGGKIND_IS_ORDERED_SET(agg->aggkind))
+	{
+		int			i;
+		int			nargs;
+		Oid		   *types;
+		char	   *modes;
+		char	  **names;
+
+		nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+		/* only direct arguments in this case */
+		list = NIL;
+		for (i = 0; i < nargs; i++)
+		{
+			list = lappend(list, form_agg_argument(i, modes, names, types));
+		}
+
+		tmp = new_objtree_VA("%{direct:, }s", 1,
+							 "direct", ObjTypeArray, list);
+		append_object_object(stmt, "types", tmp);
+		append_string_object(stmt, "agg type", "plain");
+	}
+	else
+	{
+		int			i;
+		int			nargs;
+		Oid		   *types;
+		char	   *modes;
+		char	  **names;
+
+		nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+		tmp = new_objtree_VA("%{direct:, }s ORDER BY %{aggregated:, }s", 0);
+
+		/* direct arguments ... */
+		list = NIL;
+		for (i = 0; i < agg->aggnumdirectargs; i++)
+		{
+			list = lappend(list, form_agg_argument(i, modes, names, types));
+		}
+		append_array_object(tmp, "direct", list);
+
+		/*
+		 * ... and aggregated arguments.  If the last direct argument is
+		 * VARIADIC, we need to repeat it here rather than searching for more
+		 * arguments.  (See aggr_args production in gram.y for an explanation.)
+		 */
+		if (modes && modes[agg->aggnumdirectargs - 1] == 'v')
+		{
+			list = list_make1(form_agg_argument(agg->aggnumdirectargs - 1,
+												modes, names, types));
+		}
+		else
+		{
+			list = NIL;
+			for (i = agg->aggnumdirectargs; i < nargs; i++)
+			{
+				list = lappend(list, form_agg_argument(i, modes, names, types));
+			}
+		}
+		append_array_object(tmp, "aggregated", list);
+
+		append_object_object(stmt, "types", tmp);
+		append_string_object(stmt, "agg type", "ordered-set");
+	}
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* SFUNC */
+	tmp = new_objtree_VA("SFUNC=%{procedure}D", 1,
+						 "clause", ObjTypeString, "sfunc");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 agg->aggtransfn));
+	list = lappend(list, new_object_object(tmp));
+
+	/* STYPE */
+	tmp = new_objtree_VA("STYPE=%{type}T", 1,
+						 "clause", ObjTypeString, "stype");
+	append_object_object(tmp, "type",
+						 new_objtree_for_type(agg->aggtranstype, -1));
+	list = lappend(list, new_object_object(tmp));
+
+	/* SSPACE */
+	tmp = new_objtree_VA("SSPACE=%{space}n", 1,
+						 "clause", ObjTypeString, "sspace");
+	if (agg->aggtransspace != 0)
+		append_integer_object(tmp, "space", agg->aggtransspace);
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* FINALFUNC */
+	tmp = new_objtree_VA("FINALFUNC=%{procedure}D", 1,
+						 "clause", ObjTypeString, "finalfunc");
+	if (OidIsValid(agg->aggfinalfn)) append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggfinalfn));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* FINALFUNC_EXTRA */
+	tmp = new_objtree_VA("FINALFUNC_EXTRA=%{value}s", 1,
+						 "clause", ObjTypeString, "finalfunc_extra");
+	if (agg->aggfinalextra)
+		append_string_object(tmp, "value", "true");
+	else
+		append_string_object(tmp, "value", "false");
+	list = lappend(list, new_object_object(tmp));
+
+	/* INITCOND */
+	initval = SysCacheGetAttr(AGGFNOID, aggTup,
+							  Anum_pg_aggregate_agginitval,
+							  &isnull);
+	tmp = new_objtree_VA("INITCOND=%{initval}L", 1,
+						 "clause", ObjTypeString, "initcond");
+	if (!isnull)
+		append_string_object(tmp, "initval",
+							 TextDatumGetCString(initval));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MSFUNC */
+	tmp = new_objtree_VA("MSFUNC=%{procedure}D", 1,
+						 "clause", ObjTypeString, "msfunc");
+	if (OidIsValid(agg->aggmtransfn))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggmtransfn));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MSTYPE */
+	tmp = new_objtree_VA("MSTYPE=%{type}T", 1,
+						 "clause", ObjTypeString, "mstype");
+	if (OidIsValid(agg->aggmtranstype))
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(agg->aggmtranstype, -1));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MSSPACE */
+	tmp = new_objtree_VA("MSSPACE=%{space}n", 1,
+						 "clause", ObjTypeString, "msspace");
+	if (agg->aggmtransspace != 0)
+		append_integer_object(tmp, "space", agg->aggmtransspace);
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MINVFUNC */
+	tmp = new_objtree_VA("MINVFUNC=%{procedure}D", 1,
+						 "clause", ObjTypeString, "minvfunc");
+	if (OidIsValid(agg->aggminvtransfn))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggminvtransfn));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MFINALFUNC */
+	tmp = new_objtree_VA("MFINALFUNC=%{procedure}D", 1,
+						 "clause", ObjTypeString, "mfinalfunc");
+	if (OidIsValid(agg->aggmfinalfn))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggmfinalfn));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MFINALFUNC_EXTRA */
+	tmp = new_objtree_VA("MFINALFUNC_EXTRA=%{value}s", 1,
+						 "clause", ObjTypeString, "mfinalfunc_extra");
+	if (agg->aggmfinalextra)
+		append_string_object(tmp, "value", "true");
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MINITCOND */
+	initval = SysCacheGetAttr(AGGFNOID, aggTup,
+							  Anum_pg_aggregate_aggminitval,
+							  &isnull);
+	tmp = new_objtree_VA("MINITCOND=%{initval}L", 1,
+						 "clause", ObjTypeString, "minitcond");
+	if (!isnull)
+		append_string_object(tmp, "initval",
+							 TextDatumGetCString(initval));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* HYPOTHETICAL */
+	tmp = new_objtree_VA("HYPOTHETICAL=%{value}s", 1,
+						 "clause", ObjTypeString, "hypothetical");
+	if (agg->aggkind == AGGKIND_HYPOTHETICAL)
+		append_string_object(tmp, "value", "true");
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* SORTOP */
+	tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(agg->aggsortop));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for operator with OID %u", agg->aggsortop);
+	op = (Form_pg_operator) GETSTRUCT(tup);
+
+	tmp = new_objtree_VA("SORTOP=%{operator}D", 1,
+						 "clause", ObjTypeString, "sortop");
+	if (OidIsValid(agg->aggsortop))
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname(op->oprnamespace,
+													  NameStr(op->oprname)));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	ReleaseSysCache(tup);
+
+	/* Done with the definition clause */
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(procTup);
+	ReleaseSysCache(aggTup);
+
+	return stmt;
+}
+
+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}D (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;
+	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)));
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* PROCEDURE */
+	tmp = new_objtree_VA("PROCEDURE=%{procedure}D", 1,
+						 "clause", ObjTypeString, "procedure");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 oprForm->oprcode));
+	list = lappend(list, new_object_object(tmp));
+
+	/* LEFTARG */
+	tmp = new_objtree_VA("LEFTARG=%{type}T", 1,
+						 "clause", ObjTypeString, "leftarg");
+	if (OidIsValid(oprForm->oprleft))
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(oprForm->oprleft, -1));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* RIGHTARG */
+	tmp = new_objtree_VA("RIGHTARG=%{type}T", 1,
+						 "clause", ObjTypeString, "rightarg");
+	if (OidIsValid(oprForm->oprright))
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(oprForm->oprright, -1));
+	append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* COMMUTATOR */
+	tmp = new_objtree_VA("COMMUTATOR=%{oper}D", 1,
+						 "clause", ObjTypeString, "commutator");
+	if (OidIsValid(oprForm->oprcom))
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprcom));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* NEGATOR */
+	tmp = new_objtree_VA("NEGATOR=%{oper}D", 1,
+						 "clause", ObjTypeString, "negator");
+	if (OidIsValid(oprForm->oprnegate))
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprnegate));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* RESTRICT */
+	tmp = new_objtree_VA("RESTRICT=%{procedure}D", 1,
+						 "clause", ObjTypeString, "restrict");
+	if (OidIsValid(oprForm->oprrest))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 oprForm->oprrest));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* JOIN */
+	tmp = new_objtree_VA("JOIN=%{procedure}D", 1,
+						 "clause", ObjTypeString, "join");
+	if (OidIsValid(oprForm->oprjoin))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 oprForm->oprjoin));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* MERGES */
+	tmp = new_objtree_VA("MERGES", 1,
+						 "clause", ObjTypeString, "merges");
+	if (!oprForm->oprcanmerge)
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* HASHES */
+	tmp = new_objtree_VA("HASHES", 1,
+						 "clause", ObjTypeString, "hashes");
+	if (!oprForm->oprcanhash)
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(oprTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define,
+							ObjectAddress copied)
+{
+	HeapTuple   tscTup;
+	HeapTuple   tspTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	Form_pg_ts_config tscForm;
+	Form_pg_ts_parser tspForm;
+	List	   *list;
+
+	tscTup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tscTup))
+		elog(ERROR, "cache lookup failed for text search configuration %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 %u",
+			 tscForm->cfgparser);
+	tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup);
+
+	stmt = new_objtree_VA("CREATE TEXT SEARCH CONFIGURATION %{identity}D (%{elems:, }s)",
+						  0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(tscForm->cfgnamespace,
+												  NameStr(tscForm->cfgname)));
+
+	/*
+	 * Add the definition clause.  If we have COPY'ed an existing config, add
+	 * a COPY clause; otherwise add a PARSER clause.
+	 */
+	list = NIL;
+	/* COPY */
+	tmp = new_objtree_VA("COPY=%{tsconfig}D", 1,
+						 "clause", ObjTypeString, "copy");
+	if (copied.objectId != InvalidOid)
+		append_object_object(tmp, "tsconfig",
+							 new_objtree_for_qualname_id(TSConfigRelationId,
+														 copied.objectId));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* PARSER */
+	tmp = new_objtree_VA("PARSER=%{parser}D", 1,
+						 "clause", ObjTypeString, "parser");
+	if (copied.objectId == InvalidOid)
+		append_object_object(tmp, "parser",
+							 new_objtree_for_qualname(tspForm->prsnamespace,
+													  NameStr(tspForm->prsname)));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+	ReleaseSysCache(tscTup);
+
+	return stmt;
+}
+
+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)));
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* START */
+	tmp = new_objtree_VA("START=%{procedure}D", 1,
+						 "clause", ObjTypeString, "start");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prsstart));
+	list = lappend(list, new_object_object(tmp));
+
+	/* GETTOKEN */
+	tmp = new_objtree_VA("GETTOKEN=%{procedure}D", 1,
+						 "clause", ObjTypeString, "gettoken");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prstoken));
+	list = lappend(list, new_object_object(tmp));
+
+	/* END */
+	tmp = new_objtree_VA("END=%{procedure}D", 1,
+						 "clause", ObjTypeString, "end");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prsend));
+	list = lappend(list, new_object_object(tmp));
+
+	/* LEXTYPES */
+	tmp = new_objtree_VA("LEXTYPES=%{procedure}D", 1,
+						 "clause", ObjTypeString, "lextypes");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prslextype));
+	list = lappend(list, new_object_object(tmp));
+
+	/* HEADLINE */
+	tmp = new_objtree_VA("HEADLINE=%{procedure}D", 1,
+						 "clause", ObjTypeString, "headline");
+	if (OidIsValid(tspForm->prsheadline))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 tspForm->prsheadline));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+
+	return stmt;
+}
+
+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)));
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* TEMPLATE */
+	tmp = new_objtree_VA("TEMPLATE=%{template}D", 1,
+						 "clause", ObjTypeString, "template");
+	append_object_object(tmp, "template",
+						 new_objtree_for_qualname_id(TSTemplateRelationId,
+													 tsdForm->dicttemplate));
+	list = lappend(list, new_object_object(tmp));
+
+	/*
+	 * options.  XXX this is a pretty useless representation, but we can't do
+	 * better without more ts_cache.c cooperation ...
+	 */
+	options = SysCacheGetAttr(TSDICTOID, tsdTup,
+							  Anum_pg_ts_dict_dictinitoption,
+							  &isnull);
+	tmp = new_objtree_VA("%{options}s", 0);
+	if (!isnull)
+		append_string_object(tmp, "options", TextDatumGetCString(options));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tsdTup);
+
+	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)));
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* INIT */
+	tmp = new_objtree_VA("INIT=%{procedure}D", 1,
+						 "clause", ObjTypeString, "init");
+	if (OidIsValid(tstForm->tmplinit))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 tstForm->tmplinit));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* LEXIZE */
+	tmp = new_objtree_VA("LEXIZE=%{procedure}D", 1,
+						 "clause", ObjTypeString, "lexize");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tstForm->tmpllexize));
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tstTup);
+
+	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", 1,
+							  "is_shell", ObjTypeBool, true);
+		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)", 1,
+						  "is_shell", ObjTypeBool, false);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(typForm->typnamespace,
+												  NameStr(typForm->typname)));
+
+	/* Add the definition clause */
+	list = NIL;
+
+	/* INPUT */
+	tmp = new_objtree_VA("INPUT=%{procedure}D", 1,
+						 "clause", ObjTypeString, "input");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 typForm->typinput));
+	list = lappend(list, new_object_object(tmp));
+
+	/* OUTPUT */
+	tmp = new_objtree_VA("OUTPUT=%{procedure}D", 1,
+						 "clause", ObjTypeString, "output");
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 typForm->typoutput));
+	list = lappend(list, new_object_object(tmp));
+
+	/* RECEIVE */
+	tmp = new_objtree_VA("RECEIVE=%{procedure}D", 1,
+						 "clause", ObjTypeString, "receive");
+	if (OidIsValid(typForm->typreceive))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typreceive));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* SEND */
+	tmp = new_objtree_VA("SEND=%{procedure}D", 1,
+						 "clause", ObjTypeString, "send");
+	if (OidIsValid(typForm->typsend))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typsend));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* TYPMOD_IN */
+	tmp = new_objtree_VA("TYPMOD_IN=%{procedure}D", 1,
+						 "clause", ObjTypeString, "typmod_in");
+	if (OidIsValid(typForm->typmodin))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typmodin));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* TYPMOD_OUT */
+	tmp = new_objtree_VA("TYPMOD_OUT=%{procedure}D", 1,
+						 "clause", ObjTypeString, "typmod_out");
+	if (OidIsValid(typForm->typmodout))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typmodout));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* ANALYZE */
+	tmp = new_objtree_VA("ANALYZE=%{procedure}D", 1,
+						 "clause", ObjTypeString, "analyze");
+	if (OidIsValid(typForm->typanalyze))
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typanalyze));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* INTERNALLENGTH */
+	if (typForm->typlen == -1)
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=VARIABLE", 0);
+	}
+	else
+	{
+		tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}n", 1,
+							 "typlen", ObjTypeInteger, typForm->typlen);
+	}
+	append_string_object(tmp, "clause", "internallength");
+	list = lappend(list, new_object_object(tmp));
+
+	/* PASSEDBYVALUE */
+	tmp = new_objtree_VA("PASSEDBYVALUE", 1,
+						 "clause", ObjTypeString, "passedbyvalue");
+	if (!typForm->typbyval)
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* ALIGNMENT */
+	tmp = new_objtree_VA("ALIGNMENT=%{align}s", 1,
+						 "clause", ObjTypeString, "alignment");
+	/* XXX it's odd to represent alignment with schema-qualified type names */
+	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(tmp));
+
+	tmp = new_objtree_VA("STORAGE=%{storage}s", 1,
+						 "clause", ObjTypeString, "storage");
+	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(tmp));
+
+	/* CATEGORY */
+	tmp = new_objtree_VA("CATEGORY=%{category}L", 1,
+						 "clause", ObjTypeString, "category");
+	append_string_object(tmp, "category",
+						 psprintf("%c", typForm->typcategory));
+	list = lappend(list, new_object_object(tmp));
+
+	/* PREFERRED */
+	tmp = new_objtree_VA("PREFERRED=true", 1,
+						 "clause", ObjTypeString, "preferred");
+	if (!typForm->typispreferred)
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* DEFAULT */
+	dflt = SysCacheGetAttr(TYPEOID, typTup,
+						   Anum_pg_type_typdefault,
+						   &isnull);
+	tmp = new_objtree_VA("DEFAULT=%{default}L", 1,
+						 "clause", ObjTypeString, "default");
+	if (!isnull)
+		append_string_object(tmp, "default", TextDatumGetCString(dflt));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* ELEMENT */
+	tmp = new_objtree_VA("ELEMENT=%{elem}T", 1,
+						 "clause", ObjTypeString, "element");
+	if (OidIsValid(typForm->typelem))
+		append_object_object(tmp, "elem",
+							 new_objtree_for_type(typForm->typelem, -1));
+	else
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	/* DELIMITER */
+	tmp = new_objtree_VA("DELIMITER=%{delim}L", 1,
+						 "clause", ObjTypeString, "delimiter");
+	append_string_object(tmp, "delim",
+						 psprintf("%c", typForm->typdelim));
+	list = lappend(list, new_object_object(tmp));
+
+	/* COLLATABLE */
+	tmp = new_objtree_VA("COLLATABLE=true", 1,
+						 "clause", ObjTypeString, "collatable");
+	if (!OidIsValid(typForm->typcollation))
+		append_bool_object(tmp, "present", false);
+	list = lappend(list, new_object_object(tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(typTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt(Oid objectId, Node *parsetree, ObjectAddress secondaryObj)
+{
+	DefineStmt *define = (DefineStmt *) parsetree;
+	ObjTree	   *defStmt;
+
+	switch (define->kind)
+	{
+		case OBJECT_AGGREGATE:
+			defStmt = deparse_DefineStmt_Aggregate(objectId, define);
+			break;
+
+		case OBJECT_COLLATION:
+			defStmt = deparse_DefineStmt_Collation(objectId, define);
+			break;
+
+		case OBJECT_OPERATOR:
+			defStmt = deparse_DefineStmt_Operator(objectId, define);
+			break;
+
+		case OBJECT_TSCONFIGURATION:
+			defStmt = deparse_DefineStmt_TSConfig(objectId, define,
+												  secondaryObj);
+			break;
+
+		case OBJECT_TSPARSER:
+			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;
+
+		case OBJECT_TYPE:
+			defStmt = deparse_DefineStmt_Type(objectId, define);
+			break;
+
+		default:
+			elog(ERROR, "unsupported object kind");
+			return NULL;
+	}
+
+	return defStmt;
+}
+
+static ObjTree *
+deparse_AlterTSConfigurationStmt(CollectedCommand *cmd)
+{
+	AlterTSConfigurationStmt *node = (AlterTSConfigurationStmt *) cmd->parsetree;
+	ObjTree *config;
+	ObjTree *tmp;
+	const char *const fmtcommon = "ALTER TEXT SEARCH CONFIGURATION %{identity}D";
+	char	   *fmtrest;
+	char	   *type;
+	List	   *list;
+	ListCell   *cell;
+	int			i;
+
+	/* determine the format string appropriate to each subcommand */
+	switch (node->kind)
+	{
+		case ALTER_TSCONFIG_ADD_MAPPING:
+			fmtrest = "ADD MAPPING FOR %{tokentype:, }I WITH %{dictionaries:, }D";
+			type = "add mapping for token";
+			break;
+
+		case ALTER_TSCONFIG_DROP_MAPPING:
+			fmtrest = "DROP MAPPING %{if_exists}s FOR %{tokentype}I";
+			type = "drop mapping for token";
+			break;
+
+		case ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN:
+			fmtrest = "ALTER MAPPING FOR %{tokentype:, }I WITH %{dictionaries:, }D";
+			type = "alter mapping for token";
+			break;
+
+		case ALTER_TSCONFIG_REPLACE_DICT:
+			fmtrest = "ALTER MAPPING REPLACE %{old_dictionary}D WITH %{new_dictionary}D";
+			type = "alter mapping replace";
+			break;
+
+		case ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN:
+			fmtrest = "ALTER MAPPING FOR %{tokentype:, }I REPLACE %{old_dictionary}D WITH %{new_dictionary}D";
+			type = "alter mapping replace for token";
+			break;
+	}
+	config = new_objtree_VA(psprintf("%s %s", fmtcommon, fmtrest),
+							2, "identity",
+							ObjTypeObject,
+							new_objtree_for_qualname_id(cmd->d.atscfg.address.classId,
+														cmd->d.atscfg.address.objectId),
+							"type", ObjTypeString, type);
+
+	/* Add the affected token list, for subcommands that have one */
+	if (node->kind == ALTER_TSCONFIG_ADD_MAPPING ||
+		node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN ||
+		node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN ||
+		node->kind == ALTER_TSCONFIG_DROP_MAPPING)
+	{
+		list = NIL;
+		foreach(cell, node->tokentype)
+			list = lappend(list, new_string_object(strVal(lfirst(cell))));
+		append_array_object(config, "tokentype", list);
+	}
+
+	/* add further subcommand-specific elements */
+	if (node->kind == ALTER_TSCONFIG_ADD_MAPPING ||
+		node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN)
+	{
+		/* ADD MAPPING and ALTER MAPPING FOR need a list of dictionaries */
+		list = NIL;
+		for (i = 0; i < cmd->d.atscfg.ndicts; i++)
+		{
+			ObjTree	*dictobj;
+
+			dictobj = new_objtree_for_qualname_id(TSDictionaryRelationId,
+												  cmd->d.atscfg.dictIds[i]);
+			list = lappend(list,
+						   new_object_object(dictobj));
+		}
+		append_array_object(config, "dictionaries", list);
+	}
+	else if (node->kind == ALTER_TSCONFIG_REPLACE_DICT ||
+			 node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN)
+	{
+		/* the REPLACE forms want old and new dictionaries */
+		Assert(cmd->d.atscfg.ndicts == 2);
+		append_object_object(config, "old_dictionary",
+							 new_objtree_for_qualname_id(TSDictionaryRelationId,
+														 cmd->d.atscfg.dictIds[0]));
+		append_object_object(config, "new_dictionary",
+							 new_objtree_for_qualname_id(TSDictionaryRelationId,
+														 cmd->d.atscfg.dictIds[1]));
+	}
+	else
+	{
+		/* DROP wants the IF EXISTS clause */
+		Assert(node->kind == ALTER_TSCONFIG_DROP_MAPPING);
+		tmp = new_objtree_VA("IF EXISTS", 0);
+		append_bool_object(tmp, "present", node->missing_ok);
+		append_object_object(config, "if_exists", tmp);
+	}
+
+	return config;
+}
+
+/*
+ * 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 ObjTree *
+deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
+{
+	CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *extStmt;
+	ObjTree	   *tmp;
+	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);
+
+	/* IF NOT EXISTS */
+	tmp = new_objtree_VA("IF NOT EXISTS", 0);
+	append_bool_object(tmp, "present", node->if_not_exists);
+	append_object_object(extStmt, "if_not_exists", tmp);
+
+	/* List of options */
+	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(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(tmp));
+		}
+		else
+			elog(ERROR, "unsupported option %s", opt->defname);
+	}
+
+	/* Add the SCHEMA option */
+	tmp = new_objtree_VA("SCHEMA %{schema}I",
+						 2, "type", ObjTypeString, "schema",
+						 "schema", ObjTypeString,
+						 get_namespace_name(extForm->extnamespace));
+	list = lappend(list, new_object_object(tmp));
+
+	/* done */
+	append_array_object(extStmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return extStmt;
+}
+
+static ObjTree *
+deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
+{
+	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list = NIL;
+	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 %{options: }s", 1,
+						  "identity", ObjTypeString,
+						  NameStr(extForm->extname));
+
+	foreach(cell, node->options)
+	{
+		DefElem *opt = (DefElem *) lfirst(cell);
+
+		if (strcmp(opt->defname, "new_version") == 0)
+		{
+			tmp = new_objtree_VA("TO %{version}L", 2,
+								 "type", ObjTypeString, "version",
+								 "version", ObjTypeString, defGetString(opt));
+			list = lappend(list, new_object_object(tmp));
+		}
+		else
+			elog(ERROR, "unsupported option %s", opt->defname);
+	}
+
+	append_array_object(stmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return stmt;
+}
+
+/*
+ * ALTER EXTENSION ext ADD/DROP object
+ */
+static ObjTree *
+deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree,
+								   ObjectAddress objectAddress)
+{
+	AlterExtensionContentsStmt *node = (AlterExtensionContentsStmt *) parsetree;
+	ObjTree	   *stmt;
+	char	   *fmt;
+
+	Assert(node->action == +1 || node->action == -1);
+
+	fmt = psprintf("ALTER EXTENSION %%{extidentity}I %s %s %%{objidentity}s",
+				   node->action == +1 ? "ADD" : "DROP",
+				   stringify_objtype(node->objtype));
+
+	stmt = new_objtree_VA(fmt, 2, "extidentity", ObjTypeString, node->extname,
+						  "objidentity", ObjTypeString,
+						  getObjectIdentity(&objectAddress));
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_AlterDomainStmt(Oid objectId, Node *parsetree,
+						ObjectAddress constraintAddr)
+{
+	AlterDomainStmt *node = (AlterDomainStmt *) parsetree;
+	HeapTuple	domTup;
+	Form_pg_type domForm;
+	ObjTree	   *alterDom;
+	char	   *fmt;
+	const char *type;
+
+	/* ALTER DOMAIN DROP CONSTRAINT is handled by the DROP support code */
+	if (node->subtype == 'X')
+		return NULL;
+
+	domTup = SearchSysCache1(TYPEOID, objectId);
+	if (!HeapTupleIsValid(domTup))
+		elog(ERROR, "cache lookup failed for domain with OID %u",
+			 objectId);
+	domForm = (Form_pg_type) GETSTRUCT(domTup);
+
+	switch (node->subtype)
+	{
+		case 'T':
+			/* SET DEFAULT / DROP DEFAULT */
+			if (node->def == NULL)
+			{
+				fmt = "ALTER DOMAIN %{identity}D DROP DEFAULT";
+				type = "drop default";
+			}
+			else
+			{
+				fmt = "ALTER DOMAIN %{identity}D SET DEFAULT %{default}s";
+				type = "set default";
+			}
+			break;
+		case 'N':
+			/* DROP NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D DROP NOT NULL";
+			type = "drop not null";
+			break;
+		case 'O':
+			/* SET NOT NULL */
+			fmt = "ALTER DOMAIN %{identity}D SET NOT NULL";
+			type = "set not null";
+			break;
+		case 'C':
+			/* ADD CONSTRAINT.  Only CHECK constraints are supported by domains */
+			fmt = "ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}I %{definition}s";
+			type = "add constraint";
+			break;
+		case 'V':
+			/* VALIDATE CONSTRAINT */
+			fmt = "ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}I";
+			type = "validate constraint";
+			break;
+		default:
+			elog(ERROR, "invalid subtype %c", node->subtype);
+	}
+
+	alterDom = new_objtree_VA(fmt, 1, "type", ObjTypeString, type);
+	append_object_object(alterDom, "identity",
+						 new_objtree_for_qualname(domForm->typnamespace,
+												  NameStr(domForm->typname)));
+
+	/*
+	 * Process subtype-specific options.  Validating a constraint
+	 * requires its name ...
+	 */
+	if (node->subtype == 'V')
+		append_string_object(alterDom, "constraint_name", node->name);
+
+	/* ... a new constraint has a name and definition ... */
+	if (node->subtype == 'C')
+	{
+		append_string_object(alterDom, "definition",
+							 pg_get_constraintdef_string(constraintAddr.objectId,
+														 false));
+		/* can't rely on node->name here; might not be defined */
+		append_string_object(alterDom, "constraint_name",
+							 get_constraint_name(constraintAddr.objectId));
+	}
+
+	/* ... and setting a default has a definition only. */
+	if (node->subtype == 'T' && node->def != NULL)
+		append_string_object(alterDom, "default", DomainGetDefault(domTup));
+
+	/* done */
+	ReleaseSysCache(domTup);
+
+	return alterDom;
+}
+
+/*
+ * If a column name is specified, add an element for it; otherwise it's a
+ * table-level option.
+ */
+static ObjTree *
+deparse_FdwOptions(List *options, char *colname)
+{
+	ObjTree	   *tmp;
+
+	if (colname)
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I OPTIONS (%{option:, }s)",
+							 1, "column", ObjTypeString, colname);
+	else
+		tmp = new_objtree_VA("OPTIONS (%{option:, }s)", 0);
+
+	if (options != NIL)
+	{
+		List	   *optout = NIL;
+		ListCell   *cell;
+
+		foreach(cell, options)
+		{
+			DefElem	   *elem;
+			ObjTree	   *opt;
+
+			elem = (DefElem *) lfirst(cell);
+
+			switch (elem->defaction)
+			{
+				case DEFELEM_UNSPEC:
+					opt = new_objtree_VA("%{label}I %{value}L", 0);
+					break;
+				case DEFELEM_SET:
+					opt = new_objtree_VA("SET %{label}I %{value}L", 0);
+					break;
+				case DEFELEM_ADD:
+					opt = new_objtree_VA("ADD %{label}I %{value}L", 0);
+					break;
+				case DEFELEM_DROP:
+					opt = new_objtree_VA("DROP %{label}I", 0);
+					break;
+				default:
+					elog(ERROR, "invalid def action %d", elem->defaction);
+					opt = NULL;
+			}
+
+			append_string_object(opt, "label", elem->defname);
+			append_string_object(opt, "value",
+								 elem->arg ? defGetString(elem) :
+								 defGetBoolean(elem) ? "TRUE" : "FALSE");
+
+			optout = lappend(optout, new_object_object(opt));
+		}
+
+		append_array_object(tmp, "option", optout);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+
+	return tmp;
+}
+
+static ObjTree *
+deparse_CreateFdwStmt(Oid objectId, Node *parsetree)
+{
+	CreateFdwStmt *node = (CreateFdwStmt *) parsetree;
+	HeapTuple		fdwTup;
+	Form_pg_foreign_data_wrapper fdwForm;
+	Relation	rel;
+
+	ObjTree	   *createStmt;
+	ObjTree	   *tmp;
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+							 ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(fdwTup))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", objectId);
+
+	fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup);
+
+	createStmt = new_objtree_VA("CREATE FOREIGN DATA WRAPPER %{identity}I"
+								" %{handler}s %{validator}s %{generic_options}s", 1,
+								"identity", ObjTypeString, NameStr(fdwForm->fdwname));
+
+	/* add HANDLER clause */
+	if (fdwForm->fdwhandler == InvalidOid)
+		tmp = new_objtree_VA("NO HANDLER", 0);
+	else
+	{
+		tmp = new_objtree_VA("HANDLER %{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 fdwForm->fdwhandler));
+	}
+	append_object_object(createStmt, "handler", tmp);
+
+	/* add VALIDATOR clause */
+	if (fdwForm->fdwvalidator == InvalidOid)
+		tmp = new_objtree_VA("NO VALIDATOR", 0);
+	else
+	{
+		tmp = new_objtree_VA("VALIDATOR %{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 fdwForm->fdwvalidator));
+	}
+	append_object_object(createStmt, "validator", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(fdwTup);
+	heap_close(rel, RowExclusiveLock);
+
+	return createStmt;
+}
+
+static ObjTree *
+deparse_AlterFdwStmt(Oid objectId, Node *parsetree)
+{
+	AlterFdwStmt *node = (AlterFdwStmt *) parsetree;
+	HeapTuple		fdwTup;
+	Form_pg_foreign_data_wrapper fdwForm;
+	Relation	rel;
+	ObjTree	   *alterStmt;
+	List	   *fdw_options = NIL;
+	ListCell   *cell;
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+							 ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(fdwTup))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", objectId);
+
+	fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup);
+
+	alterStmt = new_objtree_VA("ALTER FOREIGN DATA WRAPPER %{identity}I"
+							   " %{fdw_options: }s %{generic_options}s", 1,
+							   "identity", ObjTypeString, NameStr(fdwForm->fdwname));
+
+	/*
+	 * Iterate through options, to see what changed, but use catalog as basis
+	 * for new values.
+	 */
+	foreach(cell, node->func_options)
+	{
+		DefElem	   *elem;
+		ObjTree	   *tmp;
+
+		elem = lfirst(cell);
+
+		if (pg_strcasecmp(elem->defname, "handler") == 0)
+		{
+			/* add HANDLER clause */
+			if (fdwForm->fdwhandler == InvalidOid)
+				tmp = new_objtree_VA("NO HANDLER", 0);
+			else
+			{
+				tmp = new_objtree_VA("HANDLER %{procedure}D", 0);
+				append_object_object(tmp, "procedure",
+									 new_objtree_for_qualname_id(ProcedureRelationId,
+																 fdwForm->fdwhandler));
+			}
+			fdw_options = lappend(fdw_options, new_object_object(tmp));
+		}
+		else if (pg_strcasecmp(elem->defname, "validator") == 0)
+		{
+			/* add VALIDATOR clause */
+			if (fdwForm->fdwvalidator == InvalidOid)
+				tmp = new_objtree_VA("NO VALIDATOR", 0);
+			else
+			{
+				tmp = new_objtree_VA("VALIDATOR %{procedure}D", 0);
+				append_object_object(tmp, "procedure",
+									 new_objtree_for_qualname_id(ProcedureRelationId,
+																 fdwForm->fdwvalidator));
+			}
+			fdw_options = lappend(fdw_options, new_object_object(tmp));
+		}
+	}
+
+	/* Add HANDLER/VALIDATOR if specified */
+	append_array_object(alterStmt, "fdw_options", fdw_options);
+
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(fdwTup);
+	heap_close(rel, RowExclusiveLock);
+
+	return alterStmt;
+}
+
+static ObjTree *
+deparse_CreateForeignServerStmt(Oid objectId, Node *parsetree)
+{
+	CreateForeignServerStmt *node = (CreateForeignServerStmt *) parsetree;
+	ObjTree	   *createServer;
+	ObjTree	   *tmp;
+
+	createServer = new_objtree_VA("CREATE SERVER %{identity}I %{type}s %{version}s "
+								  "FOREIGN DATA WRAPPER %{fdw}I %{generic_options}s", 2,
+								  "identity", ObjTypeString, node->servername,
+								  "fdw",  ObjTypeString, node->fdwname);
+
+	/* add a TYPE clause, if any */
+	tmp = new_objtree_VA("TYPE %{type}L", 0);
+	if (node->servertype)
+		append_string_object(tmp, "type", node->servertype);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "type", tmp);
+
+	/* add a VERSION clause, if any */
+	tmp = new_objtree_VA("VERSION %{version}L", 0);
+	if (node->version)
+		append_string_object(tmp, "version", node->version);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createServer, "version", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createServer, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	return createServer;
+}
+
+static ObjTree *
+deparse_AlterForeignServerStmt(Oid objectId, Node *parsetree)
+{
+	AlterForeignServerStmt *node = (AlterForeignServerStmt *) parsetree;
+	ObjTree	   *alterServer;
+	ObjTree	   *tmp;
+
+	alterServer = new_objtree_VA("ALTER SERVER %{identity}I %{version}s "
+								  "%{generic_options}s", 1,
+								  "identity", ObjTypeString, node->servername);
+
+	/* add a VERSION clause, if any */
+	tmp = new_objtree_VA("VERSION %{version}s", 0);
+	if (node->has_version && node->version)
+		append_string_object(tmp, "version", quote_literal_cstr(node->version));
+	else if (node->has_version)
+		append_string_object(tmp, "version", "NULL");
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterServer, "version", tmp);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterServer, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	return alterServer;
+}
+
+static ObjTree *
+deparse_CreateUserMappingStmt(Oid objectId, Node *parsetree)
+{
+	CreateUserMappingStmt *node = (CreateUserMappingStmt *) parsetree;
+	ObjTree	   *createStmt;
+	Relation	rel;
+	HeapTuple	tp;
+	Form_pg_user_mapping form;
+	ForeignServer *server;
+
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	/*
+	 * Lookup up object in the catalog, so we don't have to deal with
+	 * current_user and such.
+	 */
+	tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for user mapping %u", objectId);
+
+	form = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+	server = GetForeignServer(form->umserver);
+
+	createStmt = new_objtree_VA("CREATE USER MAPPING FOR %{role}R SERVER %{server}I "
+								"%{generic_options}s", 1,
+								"server", ObjTypeString, server->servername);
+
+	append_object_object(createStmt, "role", new_objtree_for_role_id(form->umuser));
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(tp);
+	heap_close(rel, RowExclusiveLock);
+	return createStmt;
+}
+
+static ObjTree *
+deparse_AlterUserMappingStmt(Oid objectId, Node *parsetree)
+{
+	AlterUserMappingStmt *node = (AlterUserMappingStmt *) parsetree;
+	ObjTree	   *alterStmt;
+	Relation	rel;
+	HeapTuple	tp;
+	Form_pg_user_mapping form;
+	ForeignServer *server;
+
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	/*
+	 * Lookup up object in the catalog, so we don't have to deal with
+	 * current_user and such.
+	 */
+
+	tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for user mapping %u", objectId);
+
+	form = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+	server = GetForeignServer(form->umserver);
+
+	alterStmt = new_objtree_VA("ALTER USER MAPPING FOR %{role}R SERVER %{server}I "
+								"%{generic_options}s", 1,
+								"server", ObjTypeString, server->servername);
+
+	append_object_object(alterStmt, "role", new_objtree_for_role_id(form->umuser));
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(alterStmt, "generic_options",
+						 deparse_FdwOptions(node->options, NULL));
+
+	ReleaseSysCache(tp);
+	heap_close(rel, RowExclusiveLock);
+	return alterStmt;
+}
+
+/*
+ * deparse_ViewStmt
+ *		deparse a ViewStmt
+ *
+ * Given a view OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_ViewStmt(Oid objectId, Node *parsetree)
+{
+	ViewStmt   *node = (ViewStmt *) parsetree;
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	Relation	relation;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s",
+							  2,
+							  "or_replace", ObjTypeString,
+							  node->replace ? "OR REPLACE" : "",
+							  "persistence", ObjTypeString,
+					  get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(viewStmt, "identity", tmp);
+
+	append_string_object(viewStmt, "query",
+						 pg_get_viewdef_internal(objectId));
+
+	relation_close(relation, AccessShareLock);
+
+	return viewStmt;
+}
+
+/*
+ * deparse_CreateTrigStmt
+ *		Deparse a CreateTrigStmt (CREATE TRIGGER)
+ *
+ * Given a trigger OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
+{
+	CreateTrigStmt *node = (CreateTrigStmt *) parsetree;
+	Relation	pg_trigger;
+	HeapTuple	trigTup;
+	Form_pg_trigger trigForm;
+	ObjTree	   *trigger;
+	ObjTree	   *tmp;
+	int			tgnargs;
+	List	   *list;
+	List	   *events;
+
+	pg_trigger = heap_open(TriggerRelationId, AccessShareLock);
+
+	trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+	trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+	/*
+	 * Some of the elements only make sense for CONSTRAINT TRIGGERs, but it
+	 * seems simpler to use a single fmt string for both kinds of triggers.
+	 */
+	trigger =
+		new_objtree_VA("CREATE %{constraint}s TRIGGER %{name}I %{time}s %{events: OR }s "
+					   "ON %{relation}D %{from_table}s %{constraint_attrs: }s "
+					   "FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE %{function}s",
+					   2,
+					   "name", ObjTypeString, node->trigname,
+					   "constraint", ObjTypeString,
+					   node->isconstraint ? "CONSTRAINT" : "");
+
+	if (node->timing == TRIGGER_TYPE_BEFORE)
+		append_string_object(trigger, "time", "BEFORE");
+	else if (node->timing == TRIGGER_TYPE_AFTER)
+		append_string_object(trigger, "time", "AFTER");
+	else if (node->timing == TRIGGER_TYPE_INSTEAD)
+		append_string_object(trigger, "time", "INSTEAD OF");
+	else
+		elog(ERROR, "unrecognized trigger timing value %d", node->timing);
+
+	/*
+	 * Decode the events that the trigger fires for.  The output is a list;
+	 * in most cases it will just be a string with the even name, but when
+	 * there's an UPDATE with a list of columns, we return a JSON object.
+	 */
+	events = NIL;
+	if (node->events & TRIGGER_TYPE_INSERT)
+		events = lappend(events, new_string_object("INSERT"));
+	if (node->events & TRIGGER_TYPE_DELETE)
+		events = lappend(events, new_string_object("DELETE"));
+	if (node->events & TRIGGER_TYPE_TRUNCATE)
+		events = lappend(events, new_string_object("TRUNCATE"));
+	if (node->events & TRIGGER_TYPE_UPDATE)
+	{
+		if (node->columns == NIL)
+		{
+			events = lappend(events, new_string_object("UPDATE"));
+		}
+		else
+		{
+			ObjTree	   *update;
+			ListCell   *cell;
+			List	   *cols = NIL;
+
+			/*
+			 * Currently only UPDATE OF can be objects in the output JSON, but
+			 * we add a "kind" element so that user code can distinguish
+			 * possible future new event types.
+			 */
+			update = new_objtree_VA("UPDATE OF %{columns:, }I",
+									1, "kind", ObjTypeString, "update_of");
+
+			foreach(cell, node->columns)
+			{
+				char   *colname = strVal(lfirst(cell));
+
+				cols = lappend(cols,
+							   new_string_object(colname));
+			}
+
+			append_array_object(update, "columns", cols);
+
+			events = lappend(events,
+							 new_object_object(update));
+		}
+	}
+	append_array_object(trigger, "events", events);
+
+	tmp = new_objtree_for_qualname_id(RelationRelationId,
+									  trigForm->tgrelid);
+	append_object_object(trigger, "relation", tmp);
+
+	tmp = new_objtree_VA("FROM %{relation}D", 0);
+	if (trigForm->tgconstrrelid)
+	{
+		ObjTree	   *rel;
+
+		rel = new_objtree_for_qualname_id(RelationRelationId,
+										  trigForm->tgconstrrelid);
+		append_object_object(tmp, "relation", rel);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "from_table", tmp);
+
+	list = NIL;
+	if (node->deferrable)
+		list = lappend(list,
+					   new_string_object("DEFERRABLE"));
+	if (node->initdeferred)
+		list = lappend(list,
+					   new_string_object("INITIALLY DEFERRED"));
+	append_array_object(trigger, "constraint_attrs", list);
+
+	append_string_object(trigger, "for_each",
+						 node->row ? "ROW" : "STATEMENT");
+
+	tmp = new_objtree_VA("WHEN (%{clause}s)", 0);
+	if (node->whenClause)
+	{
+		Node	   *whenClause;
+		Datum		value;
+		bool		isnull;
+
+		value = fastgetattr(trigTup, Anum_pg_trigger_tgqual,
+							RelationGetDescr(pg_trigger), &isnull);
+		if (isnull)
+			elog(ERROR, "bogus NULL tgqual");
+
+		whenClause = stringToNode(TextDatumGetCString(value));
+		append_string_object(tmp, "clause",
+							 pg_get_trigger_whenclause(trigForm,
+													   whenClause,
+													   false));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(trigger, "when", tmp);
+
+	tmp = new_objtree_VA("%{funcname}D(%{args:, }L)",
+						 1, "funcname", ObjTypeObject,
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 trigForm->tgfoid));
+	list = NIL;
+	tgnargs = trigForm->tgnargs;
+	if (tgnargs > 0)
+	{
+		bytea  *tgargs;
+		char   *argstr;
+		bool	isnull;
+		int		findx;
+		int		lentgargs;
+		char   *p;
+
+		tgargs = DatumGetByteaP(fastgetattr(trigTup,
+											Anum_pg_trigger_tgargs,
+											RelationGetDescr(pg_trigger),
+											&isnull));
+		if (isnull)
+			elog(ERROR, "invalid NULL tgargs");
+		argstr = (char *) VARDATA(tgargs);
+		lentgargs = VARSIZE_ANY_EXHDR(tgargs);
+
+		p = argstr;
+		for (findx = 0; findx < tgnargs; findx++)
+		{
+			size_t	tlen;
+
+			/* verify that the argument encoding is correct */
+			tlen = strlen(p);
+			if (p + tlen >= argstr + lentgargs)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid argument string (%s) for trigger \"%s\"",
+								argstr, NameStr(trigForm->tgname))));
+
+			list = lappend(list, new_string_object(p));
+
+			p += tlen + 1;
+		}
+	}
+
+	append_array_object(tmp, "args", list);		/* might be NIL */
+
+	append_object_object(trigger, "function", tmp);
+
+	heap_close(pg_trigger, AccessShareLock);
+
+	return trigger;
+}
+
+static ObjTree *
+deparse_RefreshMatViewStmt(Oid objectId, Node *parsetree)
+{
+	RefreshMatViewStmt *node = (RefreshMatViewStmt *) parsetree;
+	ObjTree	   *refreshStmt;
+	ObjTree	   *tmp;
+
+	refreshStmt = new_objtree_VA("REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D "
+								 "%{with_no_data}s", 0);
+	/* add a CONCURRENTLY clause */
+	append_string_object(refreshStmt, "concurrently",
+						 node->concurrent ? "CONCURRENTLY" : "");
+	/* add the matview name */
+	append_object_object(refreshStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->skipData ? true : false);
+	append_object_object(refreshStmt, "with_no_data", tmp);
+
+	return refreshStmt;
+}
+
+/*
+ * 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(Relation relation, List *dpcontext, bool composite,
+				  ColumnDef *coldef, bool is_alter)
+{
+	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);
+
+	/* Composite types use a slightly simpler format string */
+	if (composite)
+		column = new_objtree_VA("%{name}I %{coltype}T %{collation}s",
+								3,
+								"type", ObjTypeString, "column",
+								"name", ObjTypeString, coldef->colname,
+								"coltype", ObjTypeObject,
+								new_objtree_for_type(typid, typmod));
+	else
+		column = new_objtree_VA("%{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(typid, typmod));
+
+	tmp = new_objtree_VA("COLLATE %{name}D", 0);
+	if (OidIsValid(typcollation))
+	{
+		ObjTree *collname;
+
+		collname = new_objtree_for_qualname_id(CollationRelationId,
+											   typcollation);
+		append_object_object(tmp, "name", collname);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(column, "collation", tmp);
+
+	if (!composite)
+	{
+		/*
+		 * 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.  Therefore,
+		 * we scan the list of constraints attached to this column to determine
+		 * whether we need to emit anything.
+		 * (Fortunately, NOT NULL constraints cannot be table constraints.)
+		 *
+		 * In the ALTER TABLE cases, we also add a NOT NULL if the colDef is
+		 * marked is_not_null.
+		 */
+		saw_notnull = false;
+		foreach(cell, coldef->constraints)
+		{
+			Constraint *constr = (Constraint *) lfirst(cell);
+
+			if (constr->contype == CONSTR_NOTNULL)
+				saw_notnull = true;
+		}
+		if (is_alter && coldef->is_not_null)
+			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("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(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.
+	 * (This routine is never used for ALTER cases.)
+	 */
+	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("%{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(Relation relation, List *tableElements, List *dpcontext,
+					 bool typed, bool composite)
+{
+	List	   *elements = NIL;
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					ObjTree	   *tree;
+
+					tree = typed ?
+						deparse_ColumnDef_typed(relation, dpcontext,
+												(ColumnDef *) elt) :
+						deparse_ColumnDef(relation, dpcontext,
+										  composite, (ColumnDef *) elt,
+										  false);
+					if (tree != NULL)
+					{
+						ObjElem    *column;
+
+						column = new_object_object(tree);
+						elements = lappend(elements, column);
+					}
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+
+	return elements;
+}
+
+/*
+ * obtainConstraints
+ *		Subroutine for CREATE TABLE/CREATE DOMAIN deparsing
+ *
+ * Given a table OID or domain OID, obtain its constraints and append them to
+ * the given elements list.  The updated list is returned.
+ *
+ * This works for typed tables, regular tables, and domains.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static List *
+obtainConstraints(List *elements, Oid relationId, Oid domainId)
+{
+	Relation	conRel;
+	ScanKeyData key;
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	ObjTree    *tmp;
+
+	/* only one may be valid */
+	Assert(OidIsValid(relationId) ^ OidIsValid(domainId));
+
+	/*
+	 * scan pg_constraint to fetch all constraints linked to the given
+	 * relation.
+	 */
+	conRel = heap_open(ConstraintRelationId, AccessShareLock);
+	if (OidIsValid(relationId))
+	{
+		ScanKeyInit(&key,
+					Anum_pg_constraint_conrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relationId));
+		scan = systable_beginscan(conRel, ConstraintRelidIndexId,
+								  true, NULL, 1, &key);
+	}
+	else
+	{
+		Assert(OidIsValid(domainId));
+		ScanKeyInit(&key,
+					Anum_pg_constraint_contypid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(domainId));
+		scan = systable_beginscan(conRel, ConstraintTypidIndexId,
+								  true, NULL, 1, &key);
+	}
+
+	/*
+	 * 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:
+				continue;	/* not here */
+			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("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, new_object_object(tmp));
+	}
+
+	systable_endscan(scan);
+	heap_close(conRel, AccessShareLock);
+
+	return elements;
+}
+
+/*
+ * deparse the ON COMMMIT ... clause for CREATE ... TEMPORARY ...
+ */
+static ObjTree *
+deparse_OnCommitClause(OnCommitAction option)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0);
+	switch (option)
+	{
+		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;
+	}
+
+	return tmp;
+}
+
+/*
+ * Deparse DefElems, as used e.g. by ALTER COLUMN ... SET, into a list of SET
+ * (...)  or RESET (...) contents.
+ */
+static ObjTree *
+deparse_DefElem(DefElem *elem, bool is_reset)
+{
+	ObjTree	   *set;
+	ObjTree	   *optname;
+
+	if (elem->defnamespace != NULL)
+		optname = new_objtree_VA("%{schema}I.%{label}I", 1,
+								 "schema", ObjTypeString, elem->defnamespace);
+	else
+		optname = new_objtree_VA("%{label}I", 0);
+
+	append_string_object(optname, "label", elem->defname);
+
+	if (is_reset)
+		set = new_objtree_VA("%{label}s", 0);
+	else
+		set = new_objtree_VA("%{label}s = %{value}L", 1,
+							 "value", ObjTypeString,
+							 elem->arg ? defGetString(elem) :
+							 defGetBoolean(elem) ? "TRUE" : "FALSE");
+
+	append_object_object(set, "label", optname);
+	return set;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ */
+static ObjTree *
+deparse_ColumnSetOptions(AlterTableCmd *subcmd)
+{
+	List	   *sets = NIL;
+	ListCell   *cell;
+	ObjTree    *tmp;
+	bool		is_reset = subcmd->subtype == AT_ResetOptions;
+
+	if (is_reset)
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I RESET (%{options:, }s)", 0);
+	else
+		tmp = new_objtree_VA("ALTER COLUMN %{column}I SET (%{options:, }s)", 0);
+
+	append_string_object(tmp, "column", subcmd->name);
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem	   *elem;
+		ObjTree	   *set;
+
+		elem = (DefElem *) lfirst(cell);
+		set = deparse_DefElem(elem, is_reset);
+		sets = lappend(sets, new_object_object(set));
+	}
+
+	append_array_object(tmp, "options", sets);
+
+	return tmp;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ */
+static ObjTree *
+deparse_RelSetOptions(AlterTableCmd *subcmd)
+{
+	List	   *sets = NIL;
+	ListCell   *cell;
+	ObjTree    *tmp;
+	bool		is_reset = subcmd->subtype == AT_ResetRelOptions;
+
+	if (is_reset)
+		tmp = new_objtree_VA("RESET (%{options:, }s)", 0);
+	else
+		tmp = new_objtree_VA("SET (%{options:, }s)", 0);
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem	   *elem;
+		ObjTree	   *set;
+
+		elem = (DefElem *) lfirst(cell);
+		set = deparse_DefElem(elem, is_reset);
+		sets = lappend(sets, new_object_object(set));
+	}
+
+	append_array_object(tmp, "options", sets);
+
+	return tmp;
+}
+
+/*
+ * deparse_CreateStmt
+ *		Deparse a CreateStmt (CREATE TABLE)
+ *
+ * Given a table OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *list;
+	ListCell   *cell;
+	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 %{if_not_exists}s %{identity}D "
+			"OF %{of_type}T %{table_elements}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) %{inherits}s "
+			"WITH (%{with:, }s) %{on_commit}s %{tablespace}s";
+
+	createStmt =
+		new_objtree_VA(fmtstr, 1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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(relation->rd_rel->reloftype, -1);
+		append_object_object(createStmt, "of_type", tmp);
+
+		tableelts = deparseTableElements(relation, node->tableElts, dpcontext,
+										 true,		/* typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+		if (tableelts == NIL)
+			tmp = new_objtree_VA("", 1,
+								 "present", ObjTypeBool, false);
+		else
+			tmp = new_objtree_VA("(%{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(relation, node->tableElts, dpcontext,
+										 false,		/* not typed table */
+										 false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+		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("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(RelationRelationId,
+													 formInh->inhparent);
+				parents = lappend(parents, new_object_object(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("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);
+
+	append_object_object(createStmt, "on_commit",
+						 deparse_OnCommitClause(node->oncommit));
+
+	/*
+	 * WITH clause.  We always emit one, containing at least the OIDS option.
+	 * That way we don't depend on the default value for default_with_oids.
+	 * We can skip emitting other options if there don't appear in the parse
+	 * node.
+	 */
+	tmp = new_objtree_VA("oids=%{value}s", 2,
+						 "option", ObjTypeString, "oids",
+						 "value", ObjTypeString,
+						 relation->rd_rel->relhasoids ? "ON" : "OFF");
+	list = list_make1(new_object_object(tmp));
+
+	foreach(cell, node->options)
+	{
+		DefElem	*opt = (DefElem *) lfirst(cell);
+
+		/* already handled above */
+		if (strcmp(opt->defname, "oids") == 0)
+			continue;
+
+		tmp = deparse_DefElem(opt, false);
+		list = lappend(list, new_object_object(tmp));
+	}
+	append_array_object(createStmt, "with", list);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
+static ObjTree *
+deparse_CreateForeignTableStmt(Oid objectId, Node *parsetree)
+{
+	CreateForeignTableStmt *stmt = (CreateForeignTableStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *tableelts = NIL;
+
+	createStmt = new_objtree_VA(
+			"CREATE FOREIGN TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) SERVER %{server}I "
+			"%{generic_options}s", 0);
+
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(createStmt, "identity", tmp);
+
+	append_string_object(createStmt, "if_not_exists",
+						 stmt->base.if_not_exists ? "IF NOT EXISTS" : "");
+
+	append_string_object(createStmt, "server", stmt->servername);
+
+	dpcontext = deparse_context_for(RelationGetRelationName(relation),
+									objectId);
+
+	tableelts = deparseTableElements(relation, stmt->base.tableElts, dpcontext,
+									 false,		/* not typed table */
+									 false);	/* not composite */
+	tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+	append_array_object(createStmt, "table_elements", tableelts);
+
+	/* add an OPTIONS clause, if any */
+	append_object_object(createStmt, "generic_options",
+						 deparse_FdwOptions(stmt->options, NULL));
+
+	relation_close(relation, AccessShareLock);
+	return createStmt;
+}
+
+
+/*
+ * deparse_CompositeTypeStmt
+ *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
+{
+	CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree;
+	ObjTree	   *composite;
+	HeapTuple	typtup;
+	Form_pg_type typform;
+	Relation	typerel;
+	List	   *dpcontext;
+	List	   *tableelts = NIL;
+
+	/* Find the pg_type entry and open the corresponding relation */
+	typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(typtup))
+		elog(ERROR, "cache lookup failed for type %u", objectId);
+	typform = (Form_pg_type) GETSTRUCT(typtup);
+	typerel = relation_open(typform->typrelid, AccessShareLock);
+
+	dpcontext = deparse_context_for(RelationGetRelationName(typerel),
+									RelationGetRelid(typerel));
+
+	composite = new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)",
+							   0);
+	append_object_object(composite, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+
+	tableelts = deparseTableElements(typerel, node->coldeflist, dpcontext,
+									 false,		/* not typed */
+									 true);		/* composite type */
+
+	append_array_object(composite, "columns", tableelts);
+
+	heap_close(typerel, AccessShareLock);
+	ReleaseSysCache(typtup);
+
+	return composite;
+}
+
+/*
+ * deparse_CreateEnumStmt
+ *		Deparse a CreateEnumStmt (CREATE TYPE AS ENUM)
+ *
+ * Given a type OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
+{
+	CreateEnumStmt *node = (CreateEnumStmt *) parsetree;
+	ObjTree	   *enumtype;
+	List	   *values;
+	ListCell   *cell;
+
+	enumtype = new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)",
+							  0);
+	append_object_object(enumtype, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	values = NIL;
+	foreach(cell, node->vals)
+	{
+		Value   *val = (Value *) lfirst(cell);
+
+		values = lappend(values, new_string_object(strVal(val)));
+	}
+	append_array_object(enumtype, "values", values);
+
+	return enumtype;
+}
+
+static ObjTree *
+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;
+
+	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(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(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(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(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(tmp));
+	}
+
+	append_array_object(range, "definition", definition);
+
+	systable_endscan(scan);
+	heap_close(pg_range, RowExclusiveLock);
+
+	return range;
+}
+
+static ObjTree *
+deparse_CreatePLangStmt(Oid objectId, Node *parsetree)
+{
+	CreatePLangStmt *node = (CreatePLangStmt *) parsetree;
+	ObjTree	   *createLang;
+	ObjTree	   *tmp;
+	HeapTuple	langTup;
+	Form_pg_language langForm;
+
+
+	langTup = SearchSysCache1(LANGOID,
+							  ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(langTup))
+		elog(ERROR, "cache lookup failed for language %u", objectId);
+	langForm = (Form_pg_language) GETSTRUCT(langTup);
+
+	if (node->plhandler == NIL)
+		createLang =
+			new_objtree_VA("CREATE %{or_replace}s %{trusted}s PROCEDURAL LANGUAGE %{identity}I", 0);
+	else
+		createLang =
+			new_objtree_VA("CREATE %{or_replace}s %{trusted}s PROCEDURAL LANGUAGE %{identity}I "
+						   "HANDLER %{handler}D %{inline}s %{validator}s", 0);
+
+	append_string_object(createLang, "or_replace",
+						 node->replace ? "OR REPLACE" : "");
+	append_string_object(createLang, "identity", node->plname);
+	append_string_object(createLang, "trusted",
+						 langForm->lanpltrusted ? "TRUSTED" : "");
+
+	if (node->plhandler != NIL)
+	{
+		/* Add the HANDLER clause */
+		append_object_object(createLang, "handler",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 langForm->lanplcallfoid));
+
+		/* Add the INLINE clause, if any */
+		tmp = new_objtree_VA("INLINE %{handler_name}D", 0);
+		if (OidIsValid(langForm->laninline))
+		{
+			append_object_object(tmp, "handler_name",
+								 new_objtree_for_qualname_id(ProcedureRelationId,
+															 langForm->laninline));
+		}
+		else
+			append_bool_object(tmp, "present", false);
+		append_object_object(createLang, "inline", tmp);
+
+		/* Add the VALIDATOR clause, if any */
+		tmp = new_objtree_VA("VALIDATOR %{handler_name}D", 0);
+		if (OidIsValid(langForm->lanvalidator))
+		{
+			append_object_object(tmp, "handler_name",
+								 new_objtree_for_qualname_id(ProcedureRelationId,
+															 langForm->lanvalidator));
+		}
+		else
+			append_bool_object(tmp, "present", false);
+		append_object_object(createLang, "validator", tmp);
+	}
+
+	ReleaseSysCache(langTup);
+
+	return createLang;
+}
+
+static ObjTree *
+deparse_CreateDomain(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *createDomain;
+	ObjTree	   *tmp;
+	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}T %{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_type(typForm->typbasetype, typForm->typtypmod));
+
+	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);
+
+	return createDomain;
+}
+
+static ObjTree *
+deparse_FunctionSet(VariableSetKind kind, char *name, char *value)
+{
+	ObjTree	   *r;
+
+	if (kind == VAR_RESET_ALL)
+	{
+		r = new_objtree_VA("RESET ALL", 0);
+	}
+	else if (value != NULL)
+	{
+		/*
+		 * Some GUC variable names are 'LIST' type and hence must not be
+		 * quoted. FIXME: shouldn't this and pg_get_functiondef() rather use
+		 * guc.c to check for GUC_LIST?
+		 */
+		if (pg_strcasecmp(name, "DateStyle") == 0
+			|| pg_strcasecmp(name, "search_path") == 0)
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}s", 0);
+		else
+			r = new_objtree_VA("SET %{set_name}I TO %{set_value}L", 0);
+
+		append_string_object(r, "set_name", name);
+		append_string_object(r, "set_value", value);
+	}
+	else
+	{
+		r = new_objtree_VA("RESET %{set_name}I", 0);
+		append_string_object(r, "set_name", name);
+	}
+
+	return r;
+}
+
+/*
+ * 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.
+ */
+static ObjTree *
+deparse_CreateFunction(Oid objectId, Node *parsetree)
+{
+	CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree;
+	ObjTree	   *createFunc;
+	ObjTree	   *sign;
+	ObjTree	   *tmp;
+	Datum		tmpdatum;
+	char	   *fmt;
+	char	   *definition;
+	char	   *source;
+	char	   *probin;
+	List	   *params;
+	List	   *defaults;
+	List	   *sets = NIL;
+	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)
+			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(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(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}n", 1,
+										"cost", ObjTypeFloat,
+										procForm->procost));
+
+	tmp = new_objtree_VA("ROWS %{rows}n", 0);
+	if (procForm->prorows == 0)
+		append_bool_object(tmp, "present", false);
+	else
+		append_float_object(tmp, "rows", procForm->prorows);
+	append_object_object(createFunc, "rows", tmp);
+
+	foreach(cell, node->options)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+			sets = lappend(sets, new_object_object(tmp));
+		}
+	}
+	append_array_object(createFunc, "set_options", sets);
+
+	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);
+
+	return createFunc;
+}
+
+/*
+ * deparse_AlterFunctionStmt
+ *		Deparse a AlterFunctionStmt (ALTER FUNCTION)
+ *
+ * Given a function OID and the parsetree that created it, return the JSON
+ * blob representing the alter command.
+ */
+static ObjTree *
+deparse_AlterFunction(Oid objectId, Node *parsetree)
+{
+	AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree;
+	ObjTree	   *alterFunc;
+	ObjTree	   *sign;
+	HeapTuple	procTup;
+	Form_pg_proc procForm;
+	List	   *params;
+	List	   *elems = NIL;
+	ListCell   *cell;
+	int			i;
+
+	/* 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);
+
+	alterFunc = new_objtree_VA("ALTER FUNCTION %{signature}s %{definition: }s", 0);
+
+	sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0);
+
+	params = NIL;
+
+	/*
+	 * ALTER FUNCTION does not change signature so we can use catalog
+	 * to get input type Oids.
+	 */
+	for (i = 0; i < procForm->pronargs; i++)
+	{
+		ObjTree	   *tmp = new_objtree_VA("%{type}T", 0);
+
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(procForm->proargtypes.values[i], -1));
+		params = lappend(params, new_object_object(tmp));
+	}
+
+	append_array_object(sign, "arguments", params);
+	append_object_object(sign, "identity",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 objectId));
+	append_object_object(alterFunc, "signature", sign);
+
+	foreach(cell, node->actions)
+	{
+		DefElem	*defel = (DefElem *) lfirst(cell);
+		ObjTree	   *tmp = NULL;
+
+		if (strcmp(defel->defname, "volatility") == 0)
+		{
+			tmp = new_objtree_VA(strVal(defel->arg), 0);
+		}
+		else if (strcmp(defel->defname, "strict") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "RETURNS NULL ON NULL INPUT" :
+								 "CALLED ON NULL INPUT", 0);
+		}
+		else if (strcmp(defel->defname, "security") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "SECURITY DEFINER" : "SECURITY INVOKER", 0);
+		}
+		else if (strcmp(defel->defname, "leakproof") == 0)
+		{
+			tmp = new_objtree_VA(intVal(defel->arg) ?
+								 "LEAKPROOF" : "NOT LEAKPROOF", 0);
+		}
+		else if (strcmp(defel->defname, "cost") == 0)
+		{
+			tmp = new_objtree_VA("COST %{cost}n", 1,
+								 "cost", ObjTypeFloat,
+								 defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "rows") == 0)
+		{
+			tmp = new_objtree_VA("ROWS %{rows}n", 0);
+			if (defGetNumeric(defel) == 0)
+				append_bool_object(tmp, "present", false);
+			else
+				append_float_object(tmp, "rows",
+									defGetNumeric(defel));
+		}
+		else if (strcmp(defel->defname, "set") == 0)
+		{
+			VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+			char *value = ExtractSetVariableArgs(sstmt);
+
+			tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+		}
+
+		elems = lappend(elems, new_object_object(tmp));
+	}
+
+	append_array_object(alterFunc, "definition", elems);
+
+	ReleaseSysCache(procTup);
+
+	return alterFunc;
+}
+
+/*
+ * Return the given object type as a string.
+ */
+static const char *
+stringify_objtype(ObjectType objtype)
+{
+	switch (objtype)
+	{
+		case OBJECT_AGGREGATE:
+			return "AGGREGATE";
+		case OBJECT_CAST:
+			return "CAST";
+		case OBJECT_COLUMN:
+			return "COLUMN";
+		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 "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_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_USER_MAPPING:
+			return "USER MAPPING";
+		case OBJECT_VIEW:
+			return "VIEW";
+
+		default:
+			elog(ERROR, "unsupported object type %d", objtype);
+	}
+}
+
+static ObjTree *
+deparse_RenameStmt(ObjectAddress address, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree	   *renameStmt;
+	char	   *fmtstr;
+	Relation	relation;
+	Oid			schemaId;
+
+	/*
+	 * 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(address.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_ATTRIBUTE:
+		case OBJECT_COLUMN:
+			relation = relation_open(address.objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			if (node->renameType == OBJECT_ATTRIBUTE)
+			{
+				fmtstr = "ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I TO %{newname}I %{cascade}s";
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_object_object(renameStmt, "cascade",
+									 new_objtree_VA("CASCADE", 1,
+													"present", ObjTypeBool,
+													node->behavior == DROP_CASCADE));
+			}
+			else
+			{
+				fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME COLUMN %%{colname}I TO %%{newname}I",
+								  stringify_objtype(node->relationType));
+				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);
+
+			/* composite types do not support IF EXISTS */
+			if (node->relationType != OBJECT_TYPE)
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+
+			break;
+
+		case OBJECT_SCHEMA:
+			{
+				renameStmt =
+					new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I",
+								   0);
+				append_string_object(renameStmt, "identity", node->subname);
+			}
+			break;
+
+		case OBJECT_FDW:
+		case OBJECT_LANGUAGE:
+		case OBJECT_FOREIGN_SERVER:
+			{
+				fmtstr = psprintf("ALTER %s %%{identity}I RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				append_string_object(renameStmt, "identity",
+									 strVal(linitial(node->object)));
+			}
+			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:
+			{
+				HeapTuple	objTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				AttrNumber	Anum_namespace;
+				ObjTree	   *ident;
+
+				/* obtain object tuple */
+				catalog = relation_open(address.classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, address.objectId);
+
+				/* obtain namespace */
+				Anum_namespace = get_object_attnum_namespace(address.classId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				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(DatumGetObjectId(objnsp),
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+
+				relation_close(catalog, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_OPCLASS:
+		case OBJECT_OPFAMILY:
+			{
+				HeapTuple	objTup;
+				HeapTuple	amTup;
+				Relation	catalog;
+				Datum		objnsp;
+				bool		isnull;
+				AttrNumber	Anum_namespace;
+				Oid			amoid;
+				ObjTree	   *ident;
+
+				/* obtain object tuple */
+				catalog = relation_open(address.classId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, address.objectId);
+
+				/* obtain namespace */
+				Anum_namespace = get_object_attnum_namespace(address.classId);
+				objnsp = heap_getattr(objTup, Anum_namespace,
+									  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid NULL namespace");
+
+				/* obtain AM tuple */
+				if (node->renameType == OBJECT_OPCLASS)
+					amoid = ((Form_pg_opclass) GETSTRUCT(objTup))->opcmethod;
+				else
+					amoid = ((Form_pg_opfamily) GETSTRUCT(objTup))->opfmethod;
+				amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+				if (!HeapTupleIsValid(amTup))
+					elog(ERROR, "cache lookup failed for access method %u", amoid);
+
+
+				fmtstr = psprintf("ALTER %s %%{identity}D USING %%{amname}I RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+
+				/* add the identity clauses */
+				ident = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+				append_string_object(renameStmt, "amname",
+									 pstrdup(NameStr(((Form_pg_am) GETSTRUCT(amTup))->amname)));
+
+				ReleaseSysCache(amTup);
+				relation_close(catalog, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			{
+				char	*ident;
+				HeapTuple proctup;
+				Form_pg_proc procform;
+
+				proctup = SearchSysCache1(PROCOID,
+										  ObjectIdGetDatum(address.objectId));
+				if (!HeapTupleIsValid(proctup))
+					elog(ERROR, "cache lookup failed for procedure %u",
+						 address.objectId);
+				procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+				/* XXX does this work for ordered-set aggregates? */
+				ident = psprintf("%s%s",
+								 quote_qualified_identifier(get_namespace_name(procform->pronamespace),
+															strVal(llast(node->object))),
+								 format_procedure_args(address.objectId, true));
+
+				fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 1,
+											"identity", ObjTypeString, ident);
+
+				ReleaseSysCache(proctup);
+			}
+			break;
+
+		case OBJECT_TABCONSTRAINT:
+		case OBJECT_DOMCONSTRAINT:
+			{
+				HeapTuple		conTup;
+				Form_pg_constraint	constrForm;
+				ObjTree		   *ident;
+
+				conTup = SearchSysCache1(CONSTROID, address.objectId);
+				constrForm = (Form_pg_constraint) GETSTRUCT(conTup);
+
+				if (node->renameType == OBJECT_TABCONSTRAINT)
+				{
+					fmtstr = "ALTER TABLE %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(RelationRelationId,
+														constrForm->conrelid);
+				}
+				else
+				{
+					fmtstr = "ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I";
+					ident = new_objtree_for_qualname_id(TypeRelationId,
+														constrForm->contypid);
+				}
+				renameStmt = new_objtree_VA(fmtstr, 2,
+											"conname", ObjTypeString, node->subname,
+											"identity", ObjTypeObject, ident);
+				ReleaseSysCache(conTup);
+			}
+			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, address.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, address.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;
+
+		case OBJECT_POLICY:
+			{
+				HeapTuple	polTup;
+				Form_pg_policy polForm;
+				Relation	pg_policy;
+				ScanKeyData	key;
+				SysScanDesc	scan;
+
+				pg_policy = relation_open(PolicyRelationId, AccessShareLock);
+				ScanKeyInit(&key, ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(address.objectId));
+				scan = systable_beginscan(pg_policy, PolicyOidIndexId, true,
+										  NULL, 1, &key);
+				polTup = systable_getnext(scan);
+				if (!HeapTupleIsValid(polTup))
+					elog(ERROR, "cache lookup failed for policy %u", address.objectId);
+				polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+				renameStmt = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I on %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "policyname", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 polForm->polrelid));
+				append_string_object(renameStmt, "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+				systable_endscan(scan);
+				relation_close(pg_policy, AccessShareLock);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	append_string_object(renameStmt, "newname", node->newname);
+
+	return renameStmt;
+}
+
+/*
+ * deparse an ALTER ... SET SCHEMA command.
+ */
+static ObjTree *
+deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree,
+							  ObjectAddress oldschema)
+{
+	AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree;
+	ObjTree	   *alterStmt;
+	char	   *fmt;
+	char	   *identity;
+	char	   *newschema;
+	char	   *oldschname;
+	char	   *ident;
+
+	newschema = node->newschema;
+
+	fmt = psprintf("ALTER %s %%{identity}s SET SCHEMA %%{newschema}I",
+				   stringify_objtype(node->objectType));
+	alterStmt = new_objtree_VA(fmt, 0);
+	append_string_object(alterStmt, "newschema", newschema);
+
+	/*
+	 * Since the command has already taken place from the point of view of
+	 * catalogs, getObjectIdentity returns the object name with the already
+	 * changed schema.  The output of our deparsing must return the original
+	 * schema name however, so we chop the schema name off the identity string
+	 * and then prepend the quoted schema name.
+	 *
+	 * XXX This is pretty clunky. Can we do better?
+	 */
+	identity = getObjectIdentity(&address);
+	oldschname = get_namespace_name(oldschema.objectId);
+	if (!oldschname)
+		elog(ERROR, "cache lookup failed for schema with OID %u", oldschema.objectId);
+	ident = psprintf("%s%s",
+					 quote_identifier(oldschname),
+					 identity + strlen(quote_identifier(newschema)));
+	append_string_object(alterStmt, "identity", ident);
+
+	return alterStmt;
+}
+
+static ObjTree *
+deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+	ObjTree	   *ownerStmt;
+	char	   *fmt;
+
+	fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newowner}I",
+				   stringify_objtype(node->objectType));
+	ownerStmt = new_objtree_VA(fmt, 0);
+	append_string_object(ownerStmt, "newowner",
+						 get_rolespec_name(node->newowner));
+
+	append_string_object(ownerStmt, "identity",
+						 getObjectIdentity(&address));
+
+	return ownerStmt;
+}
+
+static inline ObjElem *
+deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->cache_value);
+	tmp = new_objtree_VA("CACHE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "cache",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Cycle(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA("%{no}s CYCLE",
+						 2,
+						 "clause", ObjTypeString, "cycle",
+						 "no", ObjTypeString,
+						 seqdata->is_cycled ? "" : "NO");
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_IncrementBy(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->increment_by);
+	tmp = new_objtree_VA("INCREMENT BY %{value}s",
+						 2,
+						 "clause", ObjTypeString, "increment_by",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Minvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->min_value);
+	tmp = new_objtree_VA("MINVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "minvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Maxvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->max_value);
+	tmp = new_objtree_VA("MAXVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "maxvalue",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Startwith(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->start_value);
+	tmp = new_objtree_VA("START WITH %{value}s",
+						 2,
+						 "clause", ObjTypeString, "start",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static inline ObjElem *
+deparse_Seq_Restart(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf(INT64_FORMAT, seqdata->last_value);
+	tmp = new_objtree_VA("RESTART %{value}s",
+						 2,
+						 "clause", ObjTypeString, "restart",
+						 "value", ObjTypeString, tmpstr);
+	return new_object_object(tmp);
+}
+
+static ObjElem *
+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(RelationRelationId, ownerId);
+		append_string_object(tmp, "attrname", colname);
+		ownedby = new_objtree_VA("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("OWNED BY %{owner}D",
+								 3,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeNull,
+								 "present", ObjTypeBool, false);
+	return new_object_object(ownedby);
+}
+
+/*
+ * deparse_CreateSeqStmt
+ *		deparse a CreateSeqStmt
+ *
+ * Given a sequence OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ */
+static ObjTree *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *createSeq;
+	ObjTree    *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+
+	seqdata = get_sequence_values(objectId);
+
+	createSeq =
+		new_objtree_VA("CREATE %{persistence}s SEQUENCE %{identity}D "
+					   "%{definition: }s",
+					   1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(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);
+
+	relation_close(relation, AccessShareLock);
+
+	return createSeq;
+}
+
+/*
+ * deparse_AlterSeqStmt
+ *		deparse an AlterSeqStmt
+ *
+ * Given a sequence OID and a parsetree that modified it, return an ObjTree
+ * representing the alter command.
+ */
+static ObjTree *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *alterSeq;
+	ObjTree	   *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+	ListCell   *cell;
+
+	seqdata = get_sequence_values(objectId);
+
+	alterSeq =
+		new_objtree_VA("ALTER SEQUENCE %{identity}D %{definition: }s", 0);
+	tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(alterSeq, "identity", tmp);
+
+	foreach(cell, ((AlterSeqStmt *) parsetree)->options)
+	{
+		DefElem *elem = (DefElem *) lfirst(cell);
+		ObjElem *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") == 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);
+
+	relation_close(relation, AccessShareLock);
+
+	return alterSeq;
+}
+
+/*
+ * deparse_IndexStmt
+ *		deparse an IndexStmt
+ *
+ * Given an index OID and the parsetree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ */
+static ObjTree *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+	IndexStmt  *node = (IndexStmt *) parsetree;
+	ObjTree    *indexStmt;
+	ObjTree    *tmp;
+	Relation	idxrel;
+	Relation	heaprel;
+	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);
+
+	pg_get_indexdef_detailed(objectId,
+							 &index_am, &definition, &reloptions,
+							 &tablespace, &whereClause);
+
+	indexStmt =
+		new_objtree_VA("CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   6,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "if_not_exists", ObjTypeString, node->if_not_exists ? "IF NOT EXISTS" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(heaprel->rd_rel->relnamespace,
+								   RelationGetRelationName(heaprel));
+	append_object_object(indexStmt, "table", tmp);
+
+	/* reloptions */
+	tmp = new_objtree_VA("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("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("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);
+
+	heap_close(idxrel, AccessShareLock);
+	heap_close(heaprel, AccessShareLock);
+
+	return indexStmt;
+}
+
+static ObjTree *
+deparse_RuleStmt(Oid objectId, Node *parsetree)
+{
+	RuleStmt *node = (RuleStmt *) parsetree;
+	ObjTree	   *ruleStmt;
+	ObjTree	   *tmp;
+	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(action));
+	}
+	append_array_object(ruleStmt, "actions", list);
+
+	systable_endscan(scan);
+	heap_close(pg_rewrite, AccessShareLock);
+
+	return ruleStmt;
+}
+
+static ObjTree *
+deparse_CreateTableAsStmt(Oid objectId, Node *parsetree)
+{
+	CreateTableAsStmt *node = (CreateTableAsStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	ObjTree	   *createStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	char	   *fmt;
+	List	   *list;
+	ListCell   *cell;
+
+	/*
+	 * Reject unsupported case right away.
+	 */
+	if (((Query *) (node->query))->commandType == CMD_UTILITY)
+		elog(ERROR, "unimplemented deparse of CREATE TABLE AS EXECUTE");
+
+	/*
+	 * Note that INSERT INTO is deparsed as CREATE TABLE AS.  They are
+	 * functionally equivalent synonyms so there is no harm from this.
+	 */
+	if (node->relkind == OBJECT_MATVIEW)
+		fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with_clause}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+	else
+		fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s "
+			"%{identity}D %{columns}s %{with_clause}s %{on_commit}s %{tablespace}s "
+			"AS %{query}s %{with_no_data}s";
+
+	createStmt =
+		new_objtree_VA(fmt, 2,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(node->into->rel->relpersistence),
+					   "if_not_exists", ObjTypeString,
+					   node->if_not_exists ? "IF NOT EXISTS" : "");
+	append_object_object(createStmt, "identity",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 objectId));
+
+	/* Add an ON COMMIT clause.  CREATE MATERIALIZED VIEW doesn't have one */
+	if (node->relkind == OBJECT_TABLE)
+	{
+		append_object_object(createStmt, "on_commit",
+							 deparse_OnCommitClause(node->into->onCommit));
+	}
+
+	/* add a TABLESPACE clause */
+	tmp = new_objtree_VA("TABLESPACE %{tablespace}I", 0);
+	if (node->into->tableSpaceName)
+		append_string_object(tmp, "tablespace", node->into->tableSpaceName);
+	else
+	{
+		append_null_object(tmp, "tablespace");
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(createStmt, "tablespace", tmp);
+
+	/* add a WITH NO DATA clause */
+	tmp = new_objtree_VA("WITH NO DATA", 1,
+						 "present", ObjTypeBool,
+						 node->into->skipData ? true : false);
+	append_object_object(createStmt, "with_no_data", tmp);
+
+	/* add the column list, if any */
+	if (node->into->colNames == NIL)
+		tmp = new_objtree_VA("", 1,
+							 "present", ObjTypeBool, false);
+	else
+	{
+		ListCell *cell;
+
+		list = NIL;
+		foreach (cell, node->into->colNames)
+			list = lappend(list, new_string_object(strVal(lfirst(cell))));
+
+		tmp = new_objtree_VA("(%{columns:, }I)", 1,
+							 "columns", ObjTypeArray, list);
+	}
+	append_object_object(createStmt, "columns", tmp);
+
+	/* add the query */
+	Assert(IsA(node->query, Query));
+	append_string_object(createStmt, "query",
+						 pg_get_createtableas_def((Query *) node->query));
+
+
+	/*
+	 * WITH clause.  In CREATE TABLE AS, like for CREATE TABLE, we always emit
+	 * one, containing at least the OIDS option; CREATE MATERIALIZED VIEW
+	 * doesn't support the oids option, so we have to make the clause reduce to
+	 * empty if no other options are specified.
+	 */
+	tmp = new_objtree_VA("WITH (%{with:, }s)", 0);
+	if (node->relkind == OBJECT_MATVIEW && node->into->options == NIL)
+		append_bool_object(tmp, "present", false);
+	else
+	{
+		if (node->relkind == OBJECT_TABLE)
+		{
+			tmp2 = new_objtree_VA("oids=%{value}s", 2,
+								  "option", ObjTypeString, "oids",
+								  "value", ObjTypeString,
+								  relation->rd_rel->relhasoids ? "ON" : "OFF");
+			list = list_make1(new_object_object(tmp2));
+		}
+		else
+			list = NIL;
+
+		foreach (cell, node->into->options)
+		{
+			DefElem	*opt = (DefElem *) lfirst(cell);
+
+			if (strcmp(opt->defname, "oids") == 0)
+				continue;
+
+			tmp2 = deparse_DefElem(opt, false);
+			list = lappend(list, new_object_object(tmp2));
+		}
+		append_array_object(tmp, "with", list);
+	}
+	append_object_object(createStmt, "with_clause", tmp);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
+/*
+ * deparse_CreateSchemaStmt
+ *		deparse a CreateSchemaStmt
+ *
+ * Given a schema OID and the parsetree that created it, return an ObjTree
+ * 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 ObjTree *
+deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
+{
+	CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
+	ObjTree    *createSchema;
+	ObjTree    *auth;
+
+	createSchema =
+		new_objtree_VA("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("AUTHORIZATION %{authorization_role}I", 0);
+	if (node->authrole)
+		append_string_object(auth, "authorization_role",
+							 get_rolespec_name(node->authrole));
+	else
+	{
+		append_null_object(auth, "authorization_role");
+		append_bool_object(auth, "present", false);
+	}
+	append_object_object(createSchema, "authorization", auth);
+
+	return createSchema;
+}
+
+static ObjTree *
+deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
+{
+	AlterEnumStmt *node = (AlterEnumStmt *) parsetree;
+	ObjTree	   *alterEnum;
+	ObjTree	   *tmp;
+
+	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);
+
+	return alterEnum;
+}
+
+/*
+ * Append a NULL-or-quoted-literal clause.  Useful for COMMENT and SECURITY
+ * LABEL.
+ */
+static void
+append_literal_or_null(ObjTree *mainobj, char *elemname, char *value)
+{
+	ObjTree	*top;
+	ObjTree *part;
+
+	top = new_objtree_VA("%{null}s%{literal}s", 0);
+	part = new_objtree_VA("NULL", 1,
+						  "present", ObjTypeBool,
+						  !value);
+	append_object_object(top, "null", part);
+	part = new_objtree_VA("%{value}L", 1,
+						  "present", ObjTypeBool,
+						  !!value);
+	if (value)
+		append_string_object(part, "value", value);
+	append_object_object(top, "literal", part);
+
+	append_object_object(mainobj, elemname, top);
+}
+
+/*
+ * Deparse a CommentStmt when it pertains to a constraint.
+ */
+static ObjTree *
+deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	HeapTuple	constrTup;
+	Form_pg_constraint constrForm;
+	char	   *fmt;
+	ObjectAddress addr;
+
+	Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
+
+	constrTup = SearchSysCache1(CONSTROID, objectId);
+	if (!HeapTupleIsValid(constrTup))
+		elog(ERROR, "cache lookup failed for constraint %u", objectId);
+	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
+
+	if (OidIsValid(constrForm->conrelid))
+		ObjectAddressSet(addr, RelationRelationId, constrForm->conrelid);
+	else
+		ObjectAddressSet(addr, TypeRelationId, constrForm->contypid);
+
+	fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}s",
+				   node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN ");
+	comment = new_objtree_VA(fmt, 0);
+
+	/* Add the comment clause */
+	append_literal_or_null(comment, "comment", node->comment);
+
+	append_string_object(comment, "identity", pstrdup(NameStr(constrForm->conname)));
+
+	append_string_object(comment, "parentobj",
+						 getObjectIdentity(&addr));
+
+	ReleaseSysCache(constrTup);
+
+	return comment;
+}
+
+static ObjTree *
+deparse_CommentStmt(ObjectAddress address, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	char	   *fmt;
+	char	   *identity;
+
+	/*
+	 * Constraints are sufficiently different that it is easier to handle them
+	 * separately.
+	 */
+	if (node->objtype == OBJECT_DOMCONSTRAINT ||
+		node->objtype == OBJECT_TABCONSTRAINT)
+	{
+		Assert(address.classId == ConstraintRelationId);
+		return deparse_CommentOnConstraintSmt(address.objectId, parsetree);
+	}
+
+	fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}s",
+				   stringify_objtype(node->objtype));
+	comment = new_objtree_VA(fmt, 0);
+
+	/* Add the comment clause; can be either NULL or a quoted literal.  */
+	append_literal_or_null(comment, "comment", node->comment);
+
+	/*
+	 * Add the object identity clause.  For zero argument aggregates we need to
+	 * add the (*) bit; in all other cases we can just use getObjectIdentity.
+	 *
+	 * XXX shouldn't we instead fix the object identities for zero-argument
+	 * aggregates?
+	 */
+	if (node->objtype == OBJECT_AGGREGATE)
+	{
+		HeapTuple		procTup;
+		Form_pg_proc	procForm;
+
+		procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(address.objectId));
+		if (!HeapTupleIsValid(procTup))
+			elog(ERROR, "cache lookup failed for procedure %u", address.objectId);
+		procForm = (Form_pg_proc) GETSTRUCT(procTup);
+		if (procForm->pronargs == 0)
+			identity = psprintf("%s(*)",
+								quote_qualified_identifier(get_namespace_name(procForm->pronamespace),
+														   NameStr(procForm->proname)));
+		else
+			identity = getObjectIdentity(&address);
+		ReleaseSysCache(procTup);
+	}
+	else
+		identity = getObjectIdentity(&address);
+
+	append_string_object(comment, "identity", identity);
+
+	return comment;
+}
+
+/*
+ * Add common clauses to CreatePolicy or AlterPolicy deparse objects
+ */
+static void
+add_policy_clauses(ObjTree *policyStmt, Oid policyOid, List *roles,
+				   bool do_qual, bool do_with_check)
+{
+	Relation	polRel = heap_open(PolicyRelationId, AccessShareLock);
+	HeapTuple	polTup = get_catalog_object_by_oid(polRel, policyOid);
+	ObjTree	   *tmp;
+	Form_pg_policy polForm;
+
+	if (!HeapTupleIsValid(polTup))
+		elog(ERROR, "cache lookup failed for policy %u", policyOid);
+
+	polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+	/* add the "ON table" clause */
+	append_object_object(policyStmt, "table",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 polForm->polrelid));
+
+	/*
+	 * Add the "TO role" clause, if any.  In the CREATE case, it always
+	 * contains at least PUBLIC, but in the ALTER case it might be empty.
+	 */
+	tmp = new_objtree_VA("TO %{role:, }R", 0);
+	if (roles)
+	{
+		List   *list = NIL;
+		ListCell *cell;
+
+		foreach (cell, roles)
+		{
+			RoleSpec   *spec = (RoleSpec *) lfirst(cell);
+
+			list = lappend(list,
+						   new_object_object(new_objtree_for_rolespec(spec)));
+		}
+		append_array_object(tmp, "role", list);
+	}
+	else
+	{
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(policyStmt, "to_role", tmp);
+
+	/* add the USING clause, if any */
+	tmp = new_objtree_VA("USING (%{expression}s)", 0);
+	if (do_qual)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polqual expression in policy %u", policyOid);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policyStmt, "using", tmp);
+
+	/* add the WITH CHECK clause, if any */
+	tmp = new_objtree_VA("WITH CHECK (%{expression}s)", 0);
+	if (do_with_check)
+	{
+		Datum	deparsed;
+		Datum	storedexpr;
+		bool	isnull;
+
+		storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck,
+								  RelationGetDescr(polRel), &isnull);
+		if (isnull)
+			elog(ERROR, "invalid NULL polwithcheck expression in policy %u", policyOid);
+		deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+		append_string_object(tmp, "expression",
+							 TextDatumGetCString(deparsed));
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(policyStmt, "with_check", tmp);
+
+	relation_close(polRel, AccessShareLock);
+}
+
+static ObjTree *
+deparse_CreatePolicyStmt(Oid objectId, Node *parsetree)
+{
+	CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree;
+	ObjTree	   *policy;
+
+	policy = new_objtree_VA("CREATE POLICY %{identity}I ON %{table}D %{for_command}s "
+							"%{to_role}s %{using}s %{with_check}s", 2,
+							"identity", ObjTypeString, node->policy_name,
+							"for_command", ObjTypeObject,
+							new_objtree_VA("FOR %{command}s", 1,
+										   "command", ObjTypeString, node->cmd));
+
+	/* add the rest of the stuff */
+	add_policy_clauses(policy, objectId, node->roles, !!node->qual,
+					   !!node->with_check);
+
+	return policy;
+}
+
+static ObjTree *
+deparse_AlterPolicyStmt(Oid objectId, Node *parsetree)
+{
+	AlterPolicyStmt *node = (AlterPolicyStmt *) parsetree;
+	ObjTree	   *policy;
+
+	policy = new_objtree_VA("ALTER POLICY %{identity}I ON %{table}D "
+							"%{to_role}s %{using}s %{with_check}s", 1,
+							"identity", ObjTypeString, node->policy_name);
+
+	/* add the rest of the stuff */
+	add_policy_clauses(policy, objectId, node->roles, !!node->qual,
+					   !!node->with_check);
+
+	return policy;
+}
+
+static ObjTree *
+deparse_SecLabelStmt(ObjectAddress address, Node *parsetree)
+{
+	SecLabelStmt *node = (SecLabelStmt *) parsetree;
+	ObjTree	   *label;
+	char	   *fmt;
+
+	Assert(node->provider);
+
+	fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}s",
+				   stringify_objtype(node->objtype));
+	label = new_objtree_VA(fmt, 0);
+
+	/* Add the label clause; can be either NULL or a quoted literal. */
+	append_literal_or_null(label, "label", node->label);
+
+	/* Add the security provider clause */
+	append_string_object(label, "provider", node->provider);
+
+	/* Add the object identity clause */
+	append_string_object(label, "identity",
+						 getObjectIdentity(&address));
+
+	return label;
+}
+
+static ObjTree *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+	HeapTuple   conTup;
+	Relation	convrel;
+	Form_pg_conversion conForm;
+	ObjTree	   *ccStmt;
+
+	convrel = heap_open(ConversionRelationId, AccessShareLock);
+	conTup = get_catalog_object_by_oid(convrel, 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));
+
+	heap_close(convrel, AccessShareLock);
+
+	return ccStmt;
+}
+
+static ObjTree *
+deparse_CreateCastStmt(Oid objectId, Node *parsetree)
+{
+	CreateCastStmt *node = (CreateCastStmt *) parsetree;
+	Relation	castrel;
+	HeapTuple	castTup;
+	Form_pg_cast castForm;
+	ObjTree	   *createCast;
+	char	   *context;
+
+	castrel = heap_open(CastRelationId, AccessShareLock);
+	castTup = get_catalog_object_by_oid(castrel, objectId);
+	if (!HeapTupleIsValid(castTup))
+		elog(ERROR, "cache lookup failed for cast with OID %u", objectId);
+	castForm = (Form_pg_cast) GETSTRUCT(castTup);
+
+	createCast = new_objtree_VA("CREATE CAST (%{sourcetype}T AS %{targettype}T) %{mechanism}s %{context}s",
+								2, "sourcetype", ObjTypeObject,
+								new_objtree_for_type(castForm->castsource, -1),
+								"targettype", ObjTypeObject,
+								new_objtree_for_type(castForm->casttarget, -1));
+
+	if (node->inout)
+		append_string_object(createCast, "mechanism", "WITH INOUT");
+	else if (node->func == NULL)
+		append_string_object(createCast, "mechanism", "WITHOUT FUNCTION");
+	else
+	{
+		ObjTree	   *tmp;
+		StringInfoData func;
+		HeapTuple	funcTup;
+		Form_pg_proc funcForm;
+		int			i;
+
+		funcTup = SearchSysCache1(PROCOID, castForm->castfunc);
+		funcForm = (Form_pg_proc) GETSTRUCT(funcTup);
+
+		initStringInfo(&func);
+		appendStringInfo(&func, "%s(",
+						quote_qualified_identifier(get_namespace_name(funcForm->pronamespace),
+												   NameStr(funcForm->proname)));
+		for (i = 0; i < funcForm->pronargs; i++)
+			appendStringInfoString(&func,
+								   format_type_be_qualified(funcForm->proargtypes.values[i]));
+		appendStringInfoChar(&func, ')');
+
+		tmp = new_objtree_VA("WITH FUNCTION %{castfunction}s", 1,
+							 "castfunction", ObjTypeString, func.data);
+		append_object_object(createCast, "mechanism", tmp);
+
+		ReleaseSysCache(funcTup);
+	}
+
+	switch (node->context)
+	{
+		case COERCION_IMPLICIT:
+			context = "AS IMPLICIT";
+			break;
+		case COERCION_ASSIGNMENT:
+			context = "AS ASSIGNMENT";
+			break;
+		case COERCION_EXPLICIT:
+			context = "";
+			break;
+		default:
+			elog(ERROR, "invalid coercion code %c", node->context);
+			return NULL;	/* keep compiler quiet */
+	}
+	append_string_object(createCast, "context", context);
+
+	heap_close(castrel, AccessShareLock);
+
+	return createCast;
+}
+
+static ObjTree *
+deparse_CreateOpClassStmt(CollectedCommand *cmd)
+{
+	Oid			opcoid = cmd->d.createopc.address.objectId;
+	HeapTuple   opcTup;
+	HeapTuple   opfTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_opclass opcForm;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	ListCell   *cell;
+
+	opcTup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opcoid));
+	if (!HeapTupleIsValid(opcTup))
+		elog(ERROR, "cache lookup failed for opclass with OID %u", opcoid);
+	opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
+
+	opfTup = SearchSysCache1(OPFAMILYOID, opcForm->opcfamily);
+	if (!HeapTupleIsValid(opfTup))
+		elog(ERROR, "cache lookup failed for operator family with OID %u", opcForm->opcfamily);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+	stmt = new_objtree_VA("CREATE OPERATOR CLASS %{identity}D %{default}s "
+						  "FOR TYPE %{type}T USING %{amname}I %{opfamily}s "
+						  "AS %{items:, }s", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(opcForm->opcnamespace,
+												  NameStr(opcForm->opcname)));
+
+	/*
+	 * Add the FAMILY clause; but if it has the same name and namespace as the
+	 * opclass, then have it expand to empty, because it would cause a failure
+	 * if the opfamily was created internally.
+	 */
+	tmp = new_objtree_VA("FAMILY %{opfamily}D", 1,
+						 "opfamily", ObjTypeObject,
+						 new_objtree_for_qualname(opfForm->opfnamespace,
+												  NameStr(opfForm->opfname)));
+	if (strcmp(NameStr(opfForm->opfname), NameStr(opcForm->opcname)) == 0 &&
+		opfForm->opfnamespace == opcForm->opcnamespace)
+		append_bool_object(tmp, "present", false);
+	append_object_object(stmt, "opfamily",  tmp);
+
+	/* Add the DEFAULT clause */
+	append_string_object(stmt, "default",
+						 opcForm->opcdefault ? "DEFAULT" : "");
+
+	/* Add the FOR TYPE clause */
+	append_object_object(stmt, "type",
+						 new_objtree_for_type(opcForm->opcintype, -1));
+
+	/* Add the USING clause */
+	append_string_object(stmt, "amname", get_am_name(opcForm->opcmethod));
+
+	/*
+	 * Add the initial item list.  Note we always add the STORAGE clause, even
+	 * when it is implicit in the original command.
+	 */
+	tmp = new_objtree_VA("STORAGE %{type}T", 0);
+	append_object_object(tmp, "type",
+						 new_objtree_for_type(opcForm->opckeytype != InvalidOid ?
+											  opcForm->opckeytype : opcForm->opcintype,
+											  -1));
+	list = list_make1(new_object_object(tmp));
+
+	/* Add the declared operators */
+	/* XXX this duplicates code in deparse_AlterOpFamily */
+	foreach(cell, cmd->d.createopc.operators)
+	{
+		OpFamilyMember *oper = lfirst(cell);
+		ObjTree	   *tmp;
+
+		tmp = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s",
+							 0);
+		append_integer_object(tmp, "num", oper->number);
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oper->object));
+		/* add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(oper->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(oper->righttype, -1));
+		/* Add the FOR SEARCH / FOR ORDER BY clause */
+		if (oper->sortfamily == InvalidOid)
+			append_string_object(tmp, "purpose", "FOR SEARCH");
+		else
+		{
+			ObjTree	   *tmp2;
+
+			tmp2 = new_objtree_VA("FOR ORDER BY %{opfamily}D", 0);
+			append_object_object(tmp2, "opfamily",
+								 new_objtree_for_qualname_id(OperatorFamilyRelationId,
+															 oper->sortfamily));
+			append_object_object(tmp, "purpose", tmp2);
+		}
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	/* Add the declared support functions */
+	foreach(cell, cmd->d.createopc.procedures)
+	{
+		OpFamilyMember *proc = lfirst(cell);
+		ObjTree	   *tmp;
+		HeapTuple	procTup;
+		Form_pg_proc procForm;
+		Oid		   *proargtypes;
+		List	   *arglist;
+		int			i;
+
+		tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D(%{argtypes:, }T)", 0);
+		append_integer_object(tmp, "num", proc->number);
+		procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object));
+		if (!HeapTupleIsValid(procTup))
+			elog(ERROR, "cache lookup failed for procedure %u", proc->object);
+		procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+		append_object_object(tmp, "function",
+							 new_objtree_for_qualname(procForm->pronamespace,
+													  NameStr(procForm->proname)));
+		proargtypes = procForm->proargtypes.values;
+		arglist = NIL;
+		for (i = 0; i < procForm->pronargs; i++)
+		{
+			ObjTree	   *arg;
+
+			arg = new_objtree_for_type(proargtypes[i], -1);
+			arglist = lappend(arglist, new_object_object(arg));
+		}
+		append_array_object(tmp, "argtypes", arglist);
+
+		ReleaseSysCache(procTup);
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(proc->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(proc->righttype, -1));
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	append_array_object(stmt, "items", list);
+
+	ReleaseSysCache(opfTup);
+	ReleaseSysCache(opcTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_CreateOpFamily(Oid objectId, Node *parsetree)
+{
+	HeapTuple   opfTup;
+	HeapTuple   amTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_am  amForm;
+	ObjTree	   *copfStmt;
+	ObjTree	   *tmp;
+
+	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}I",
+							  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));
+
+	ReleaseSysCache(amTup);
+	ReleaseSysCache(opfTup);
+
+	return copfStmt;
+}
+
+static ObjTree *
+deparse_GrantStmt(CollectedCommand *cmd)
+{
+	InternalGrant *istmt;
+	ObjTree	   *grantStmt;
+	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 = "FOREIGN 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:, }R %%{grant_option}s",
+					   objtype);
+	else
+		fmt = psprintf("REVOKE %%{grant_option}s %%{privileges:, }s ON %s %%{privtarget:, }s "
+					   "FROM %%{grantees:, }R %%{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(tmp));
+	}
+	else
+	{
+		list = NIL;
+
+		if (istmt->privileges & ACL_INSERT)
+			list = lappend(list, new_string_object("INSERT"));
+		if (istmt->privileges & ACL_SELECT)
+			list = lappend(list, new_string_object("SELECT"));
+		if (istmt->privileges & ACL_UPDATE)
+			list = lappend(list, new_string_object("UPDATE"));
+		if (istmt->privileges & ACL_DELETE)
+			list = lappend(list, new_string_object("DELETE"));
+		if (istmt->privileges & ACL_TRUNCATE)
+			list = lappend(list, new_string_object("TRUNCATE"));
+		if (istmt->privileges & ACL_REFERENCES)
+			list = lappend(list, new_string_object("REFERENCES"));
+		if (istmt->privileges & ACL_TRIGGER)
+			list = lappend(list, new_string_object("TRIGGER"));
+		if (istmt->privileges & ACL_EXECUTE)
+			list = lappend(list, new_string_object("EXECUTE"));
+		if (istmt->privileges & ACL_USAGE)
+			list = lappend(list, new_string_object("USAGE"));
+		if (istmt->privileges & ACL_CREATE)
+			list = lappend(list, new_string_object("CREATE"));
+		if (istmt->privileges & ACL_CREATE_TEMP)
+			list = lappend(list, new_string_object("TEMPORARY"));
+		if (istmt->privileges & ACL_CONNECT)
+			list = lappend(list, new_string_object("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(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(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(tmp));
+	}
+	append_array_object(grantStmt, "privtarget", list);
+
+	/* list of grantees */
+	list = NIL;
+	foreach(cell, istmt->grantees)
+	{
+		Oid		grantee = lfirst_oid(cell);
+
+		tmp = new_objtree_for_role_id(grantee);
+		list = lappend(list, new_object_object(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", "");
+	}
+
+	return grantStmt;
+}
+
+/*
+ * Deparse an ALTER OPERATOR FAMILY ADD/DROP command.
+ */
+static ObjTree *
+deparse_AlterOpFamily(CollectedCommand *cmd)
+{
+	ObjTree	   *alterOpFam;
+	AlterOpFamilyStmt *stmt = (AlterOpFamilyStmt *) cmd->parsetree;
+	HeapTuple	ftp;
+	Form_pg_opfamily opfForm;
+	List	   *list;
+	ListCell   *cell;
+
+	ftp = SearchSysCache1(OPFAMILYOID,
+						  ObjectIdGetDatum(cmd->d.opfam.address.objectId));
+	if (!HeapTupleIsValid(ftp))
+		elog(ERROR, "cache lookup failed for operator family %u",
+			 cmd->d.opfam.address.objectId);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(ftp);
+
+	if (!stmt->isDrop)
+		alterOpFam = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D "
+									"USING %{amname}I ADD %{items:, }s", 0);
+	else
+		alterOpFam = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D "
+									"USING %{amname}I DROP %{items:, }s", 0);
+	append_object_object(alterOpFam, "identity",
+						 new_objtree_for_qualname(opfForm->opfnamespace,
+												  NameStr(opfForm->opfname)));
+	append_string_object(alterOpFam, "amname", stmt->amname);
+
+	list = NIL;
+	foreach(cell, cmd->d.opfam.operators)
+	{
+		OpFamilyMember *oper = lfirst(cell);
+		ObjTree	   *tmp;
+
+		if (!stmt->isDrop)
+			tmp = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s",
+								 0);
+		else
+			tmp = new_objtree_VA("OPERATOR %{num}n (%{ltype}T, %{rtype}T)",
+								 0);
+		append_integer_object(tmp, "num", oper->number);
+
+		/* Add the operator name; the DROP case doesn't have this */
+		if (!stmt->isDrop)
+		{
+			append_object_object(tmp, "operator",
+								 new_objtree_for_qualname_id(OperatorRelationId,
+															 oper->object));
+		}
+
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(oper->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(oper->righttype, -1));
+
+		/* Add the FOR SEARCH / FOR ORDER BY clause; not in the DROP case */
+		if (!stmt->isDrop)
+		{
+			if (oper->sortfamily == InvalidOid)
+				append_string_object(tmp, "purpose", "FOR SEARCH");
+			else
+			{
+				ObjTree	   *tmp2;
+
+				tmp2 = new_objtree_VA("FOR ORDER BY %{opfamily}D", 0);
+				append_object_object(tmp2, "opfamily",
+									 new_objtree_for_qualname_id(OperatorFamilyRelationId,
+																 oper->sortfamily));
+				append_object_object(tmp, "purpose", tmp2);
+			}
+		}
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	foreach(cell, cmd->d.opfam.procedures)
+	{
+		OpFamilyMember *proc = lfirst(cell);
+		ObjTree	   *tmp;
+
+		if (!stmt->isDrop)
+			tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D(%{argtypes:, }T)", 0);
+		else
+			tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T)", 0);
+		append_integer_object(tmp, "num", proc->number);
+
+		/* Add the function name and arg types; the DROP case doesn't have this */
+		if (!stmt->isDrop)
+		{
+			HeapTuple	procTup;
+			Form_pg_proc procForm;
+			Oid		   *proargtypes;
+			List	   *arglist;
+			int			i;
+
+			procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object));
+			if (!HeapTupleIsValid(procTup))
+				elog(ERROR, "cache lookup failed for procedure %u", proc->object);
+			procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+			append_object_object(tmp, "function",
+								 new_objtree_for_qualname(procForm->pronamespace,
+														  NameStr(procForm->proname)));
+			proargtypes = procForm->proargtypes.values;
+			arglist = NIL;
+			for (i = 0; i < procForm->pronargs; i++)
+			{
+				ObjTree	   *arg;
+
+				arg = new_objtree_for_type(proargtypes[i], -1);
+				arglist = lappend(arglist, new_object_object(arg));
+			}
+			append_array_object(tmp, "argtypes", arglist);
+
+			ReleaseSysCache(procTup);
+		}
+
+		/* Add the types */
+		append_object_object(tmp, "ltype",
+							 new_objtree_for_type(proc->lefttype, -1));
+		append_object_object(tmp, "rtype",
+							 new_objtree_for_type(proc->righttype, -1));
+
+		list = lappend(list, new_object_object(tmp));
+	}
+
+	append_array_object(alterOpFam, "items", list);
+
+	ReleaseSysCache(ftp);
+
+	return alterOpFam;
+}
+
+static ObjTree *
+deparse_AlterDefaultPrivilegesStmt(CollectedCommand *cmd)
+{
+	ObjTree	   *alterStmt;
+	AlterDefaultPrivilegesStmt *stmt = (AlterDefaultPrivilegesStmt *) cmd->parsetree;
+	List	   *roles = NIL;
+	List	   *schemas = NIL;
+	List	   *grantees;
+	List	   *privs;
+	ListCell   *cell;
+	ObjTree	   *tmp;
+	ObjTree	   *grant;
+
+	alterStmt = new_objtree_VA("ALTER DEFAULT PRIVILEGES %{in_schema}s "
+							   "%{for_roles}s %{grant}s", 0);
+
+	/* Scan the parse node to dig out the FOR ROLE and IN SCHEMA clauses */
+	foreach(cell, stmt->options)
+	{
+		DefElem	   *opt = (DefElem *) lfirst(cell);
+		ListCell   *cell2;
+
+		Assert(IsA(opt, DefElem));
+		Assert(IsA(opt->arg, List));
+		if (strcmp(opt->defname, "roles") == 0)
+		{
+			foreach(cell2, (List *) opt->arg)
+			{
+				Value  *val = lfirst(cell2);
+				ObjTree *obj = new_objtree_for_role(strVal(val));
+
+				roles = lappend(roles, new_object_object(obj));
+			}
+		}
+		else if (strcmp(opt->defname, "schemas") == 0)
+		{
+			foreach(cell2, (List *) opt->arg)
+			{
+				Value  *val = lfirst(cell2);
+
+				schemas = lappend(schemas,
+								  new_string_object(strVal(val)));
+			}
+		}
+	}
+
+	/* Add the FOR ROLE clause, if any */
+	tmp = new_objtree_VA("FOR ROLE %{roles:, }R", 0);
+	append_array_object(tmp, "roles", roles);
+	if (roles == NIL)
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterStmt, "for_roles", tmp);
+
+	/* Add the IN SCHEMA clause, if any */
+	tmp = new_objtree_VA("IN SCHEMA %{schemas:, }I", 0);
+	append_array_object(tmp, "schemas", schemas);
+	if (schemas == NIL)
+		append_bool_object(tmp, "present", false);
+	append_object_object(alterStmt, "in_schema", tmp);
+
+	/* Add the GRANT subcommand */
+	if (stmt->action->is_grant)
+		grant = new_objtree_VA("GRANT %{privileges:, }s ON %{target}s "
+							   "TO %{grantees:, }R %{grant_option}s", 0);
+	else
+		grant = new_objtree_VA("REVOKE %{grant_option}s %{privileges:, }s "
+							   "ON %{target}s FROM %{grantees:, }R", 0);
+
+	/* add the GRANT OPTION clause */
+	tmp = new_objtree_VA(stmt->action->is_grant ?
+						   "WITH GRANT OPTION" : "GRANT OPTION FOR",
+						   1, "present", ObjTypeBool,
+						   stmt->action->grant_option);
+	append_object_object(grant, "grant_option", tmp);
+
+	/* add the target object type */
+	append_string_object(grant, "target", cmd->d.defprivs.objtype);
+
+	/* add the grantee list */
+	grantees = NIL;
+	foreach(cell, stmt->action->grantees)
+	{
+		RoleSpec   *spec = (RoleSpec *) lfirst(cell);
+		ObjTree	   *obj = new_objtree_for_rolespec(spec);
+
+		grantees = lappend(grantees, new_object_object(obj));
+	}
+	append_array_object(grant, "grantees", grantees);
+
+	/*
+	 * Add the privileges list.  This uses the parser struct, as opposed to the
+	 * InternalGrant format used by GRANT.  There are enough other differences
+	 * that this doesn't seem worth improving.
+	 */
+	if (stmt->action->privileges == NIL)
+		privs = list_make1(new_string_object("ALL PRIVILEGES"));
+	else
+	{
+		privs = NIL;
+
+		foreach(cell, stmt->action->privileges)
+		{
+			AccessPriv *priv = lfirst(cell);
+
+			Assert(priv->cols == NIL);
+			privs = lappend(privs,
+							new_string_object(priv->priv_name));
+		}
+	}
+
+	append_array_object(grant, "privileges", privs);
+
+	append_object_object(alterStmt, "grant", grant);
+
+	return alterStmt;
+}
+
+static ObjTree *
+deparse_AlterTableStmt(CollectedCommand *cmd)
+{
+	ObjTree	   *alterTableStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	List	   *dpcontext;
+	Relation	rel;
+	List	   *subcmds = NIL;
+	ListCell   *cell;
+	char	   *fmtstr;
+	const char *reltype;
+	bool		istype = false;
+
+	Assert(cmd->type == SCT_AlterTable);
+
+	rel = relation_open(cmd->d.alterTable.objectId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									cmd->d.alterTable.objectId);
+
+	switch (rel->rd_rel->relkind)
+	{
+		case RELKIND_RELATION:
+			reltype = "TABLE";
+			break;
+		case RELKIND_INDEX:
+			reltype = "INDEX";
+			break;
+		case RELKIND_VIEW:
+			reltype = "VIEW";
+			break;
+		case RELKIND_COMPOSITE_TYPE:
+			reltype = "TYPE";
+			istype = true;
+			break;
+		case RELKIND_FOREIGN_TABLE:
+			reltype = "FOREIGN TABLE";
+			break;
+
+		default:
+			elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind);
+			reltype = NULL;;
+	}
+
+	fmtstr = psprintf("ALTER %s %%{identity}D %%{subcmds:, }s", reltype);
+	alterTableStmt = new_objtree_VA(fmtstr, 0);
+
+	tmp = new_objtree_for_qualname(rel->rd_rel->relnamespace,
+								   RelationGetRelationName(rel));
+	append_object_object(alterTableStmt, "identity", tmp);
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
+		AlterTableCmd	*subcmd = (AlterTableCmd *) sub->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, true);
+				fmtstr = psprintf("ADD %s %%{definition}s",
+								  istype ? "ATTRIBUTE" : "COLUMN");
+				tmp = new_objtree_VA(fmtstr, 2,
+									 "type", ObjTypeString, "add column",
+									 "definition", ObjTypeObject, tree);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			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(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(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(tmp));
+				break;
+
+			case AT_SetStatistics:
+				{
+					Assert(IsA(subcmd->def, Integer));
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n",
+										 3, "type", ObjTypeString, "set statistics",
+										 "column", ObjTypeString, subcmd->name,
+										 "statistics", ObjTypeInteger,
+										 intVal((Value *) subcmd->def));
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_SetOptions:
+			case AT_ResetOptions:
+				subcmds = lappend(subcmds, new_object_object(
+									  deparse_ColumnSetOptions(subcmd)));
+				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(tmp));
+				break;
+
+			case AT_DropColumnRecurse:
+			case AT_DropColumn:
+				fmtstr = psprintf("DROP %s %%{column}I %%{cascade}s",
+								  istype ? "ATTRIBUTE" : "COLUMN");
+				tmp = new_objtree_VA(fmtstr, 2,
+									 "type", ObjTypeString, "drop column",
+									 "column", ObjTypeString, subcmd->name);
+				tmp2 = new_objtree_VA("CASCADE", 1,
+									  "present", ObjTypeBool, subcmd->behavior);
+				append_object_object(tmp, "cascade", tmp2);
+
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddIndex:
+				{
+					Oid			idxOid = sub->address.objectId;
+					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(tmp));
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;
+
+			case AT_AddConstraint:
+			case AT_AddConstraintRecurse:
+				{
+					/* XXX need to set the "recurse" bit somewhere? */
+					Oid			constrOid = sub->address.objectId;
+
+					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(tmp));
+				}
+				break;
+
+			case AT_AlterConstraint:
+				{
+					Oid		constrOid = sub->address.objectId;
+					Constraint *c = (Constraint *) subcmd->def;
+
+					/* if no constraint was altered, silently skip it */
+					if (!OidIsValid(constrOid))
+						break;
+
+					Assert(IsA(c, Constraint));
+					tmp = new_objtree_VA("ALTER CONSTRAINT %{name}I %{deferrable}s %{init_deferred}s",
+										 2, "type", ObjTypeString, "alter constraint",
+										 "name", ObjTypeString, get_constraint_name(constrOid));
+					append_string_object(tmp, "deferrable", c->deferrable ?
+										 "DEFERRABLE" : "NOT DEFERRABLE");
+					append_string_object(tmp, "init_deferred", c->initdeferred ?
+										 "INITIALLY DEFERRED" : "INITIALLY IMMEDIATE");
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_ValidateConstraintRecurse:
+			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(tmp));
+				break;
+
+			case AT_DropConstraintRecurse:
+			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(tmp));
+				break;
+
+			case AT_AlterColumnType:
+				{
+					TupleDesc tupdesc = RelationGetDescr(rel);
+					Form_pg_attribute att;
+					ColumnDef	   *def;
+
+					att = tupdesc->attrs[sub->address.objectSubId - 1];
+					def = (ColumnDef *) subcmd->def;
+					Assert(IsA(def, ColumnDef));
+
+					fmtstr = psprintf("ALTER %s %%{column}I SET DATA TYPE %%{datatype}T %%{collation}s %s",
+									  istype ? "ATTRIBUTE" : "COLUMN",
+									  istype ? "%{cascade}s" : "%{using}s");
+
+					tmp = new_objtree_VA(fmtstr, 2,
+										 "type", ObjTypeString, "alter column type",
+										 "column", ObjTypeString, subcmd->name);
+					/* add the TYPE clause */
+					append_object_object(tmp, "datatype",
+										 new_objtree_for_type(att->atttypid,
+															  att->atttypmod));
+
+					/* add a COLLATE clause, if needed */
+					tmp2 = new_objtree_VA("COLLATE %{name}D", 0);
+					if (OidIsValid(att->attcollation))
+					{
+						ObjTree *collname;
+
+						collname = new_objtree_for_qualname_id(CollationRelationId,
+															   att->attcollation);
+						append_object_object(tmp2, "name", collname);
+					}
+					else
+						append_bool_object(tmp2, "present", false);
+					append_object_object(tmp, "collation", tmp2);
+
+					/* if not a composite type, add the USING clause */
+					if (!istype)
+					{
+						/*
+						 * If there's a USING clause, transformAlterTableStmt
+						 * ran it through transformExpr and stored the
+						 * resulting node in cooked_default, which we can use
+						 * here.
+						 */
+						tmp2 = new_objtree_VA("USING %{expression}s", 0);
+						if (def->raw_default)
+						{
+							Datum	deparsed;
+							char   *defexpr;
+
+							defexpr = nodeToString(def->cooked_default);
+							deparsed = DirectFunctionCall2(pg_get_expr,
+														   CStringGetTextDatum(defexpr),
+														   RelationGetRelid(rel));
+							append_string_object(tmp2, "expression",
+												 TextDatumGetCString(deparsed));
+						}
+						else
+							append_bool_object(tmp2, "present", false);
+						append_object_object(tmp, "using", tmp2);
+					}
+
+					/* if it's a composite type, add the CASCADE clause */
+					if (istype)
+					{
+						tmp2 = new_objtree_VA("CASCADE", 0);
+						if (subcmd->behavior != DROP_CASCADE)
+							append_bool_object(tmp2, "present", false);
+						append_object_object(tmp, "cascade", tmp2);
+					}
+
+					subcmds = lappend(subcmds, new_object_object(tmp));
+				}
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				tmp = deparse_FdwOptions((List *) subcmd->def,
+										 subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_ChangeOwner:
+				tmp = new_objtree_VA("OWNER TO %{owner}I",
+									 2, "type", ObjTypeString, "change owner",
+									 "owner",  ObjTypeString,
+									 get_rolespec_name(subcmd->newowner));
+				subcmds = lappend(subcmds, new_object_object(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(tmp));
+				break;
+
+			case AT_DropCluster:
+				tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+									 "type", ObjTypeString, "set without cluster");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetLogged:
+				tmp = new_objtree_VA("SET LOGGED", 1,
+									 "type", ObjTypeString, "set logged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_SetUnLogged:
+				tmp = new_objtree_VA("SET UNLOGGED", 1,
+									 "type", ObjTypeString, "set unlogged");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOidsRecurse:
+			case AT_AddOids:
+				tmp = new_objtree_VA("SET WITH OIDS", 1,
+									 "type", ObjTypeString, "set with oids");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOids:
+				tmp = new_objtree_VA("SET WITHOUT OIDS", 1,
+									 "type", ObjTypeString, "set without oids");
+				subcmds = lappend(subcmds, new_object_object(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(tmp));
+				break;
+
+			case AT_SetRelOptions:
+			case AT_ResetRelOptions:
+				subcmds = lappend(subcmds, new_object_object(
+									  deparse_RelSetOptions(subcmd)));
+				break;
+
+			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(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(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(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(tmp));
+				break;
+
+			case AT_EnableTrigAll:
+				tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "enable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigAll:
+				tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "disable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableTrigUser:
+				tmp = new_objtree_VA("ENABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "enable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableTrigUser:
+				tmp = new_objtree_VA("DISABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "disable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableRule:
+				tmp = new_objtree_VA("ENABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableAlwaysRule:
+				tmp = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable always rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_EnableReplicaRule:
+				tmp = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2,
+									 "type", ObjTypeString, "enable replica rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRule:
+				tmp = new_objtree_VA("DISABLE RULE %{rule}I", 2,
+									 "type", ObjTypeString, "disable rule",
+									 "rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddInherit:
+				tmp = new_objtree_VA("INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 sub->address.objectId));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropInherit:
+				tmp = new_objtree_VA("NO INHERIT %{parent}D",
+									 2, "type", ObjTypeString, "drop inherit",
+									 "parent", ObjTypeObject,
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 sub->address.objectId));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AddOf:
+				tmp = new_objtree_VA("OF %{type_of}T",
+									 2, "type", ObjTypeString, "add of",
+									 "type_of", ObjTypeObject,
+									 new_objtree_for_type(sub->address.objectId, -1));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DropOf:
+				tmp = new_objtree_VA("NOT OF",
+									 1, "type", ObjTypeString, "not of");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				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(tmp));
+				break;
+
+			case AT_EnableRowSecurity:
+				tmp = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "enable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_DisableRowSecurity:
+				tmp = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1,
+									 "type", ObjTypeString, "disable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_GenericOptions:
+				tmp = deparse_FdwOptions((List *) subcmd->def, NULL);
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+	}
+
+	heap_close(rel, AccessShareLock);
+
+	if (list_length(subcmds) == 0)
+		return NULL;
+
+	append_array_object(alterTableStmt, "subcmds", subcmds);
+	return alterTableStmt;
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function contains a large switch that mirrors that in
+ * ProcessUtilitySlow.  All cases covered there should also be covered here.
+ */
+static ObjTree *
+deparse_simple_command(CollectedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+	ObjTree	   *command;
+
+	Assert(cmd->type == SCT_Simple);
+
+	parsetree = cmd->parsetree;
+	objectId = cmd->d.simple.address.objectId;
+
+	/* This switch needs to handle everything that ProcessUtilitySlow does */
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			command = deparse_CreateSchemaStmt(objectId, parsetree);
+			break;
+
+		case T_CreateStmt:
+			command = deparse_CreateStmt(objectId, parsetree);
+			break;
+
+		case T_CreateForeignTableStmt:
+			command = deparse_CreateForeignTableStmt(objectId, parsetree);
+			break;
+
+		case T_AlterTableStmt:
+		case T_AlterTableMoveAllStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterDomainStmt:
+			command = deparse_AlterDomainStmt(objectId, parsetree,
+											  cmd->d.simple.secondaryObject);
+			break;
+
+			/* other local objects */
+		case T_DefineStmt:
+			command = deparse_DefineStmt(objectId, parsetree,
+										 cmd->d.simple.secondaryObject);
+			break;
+
+		case T_IndexStmt:
+			command = deparse_IndexStmt(objectId, parsetree);
+			break;
+
+		case T_CreateExtensionStmt:
+			command = deparse_CreateExtensionStmt(objectId, parsetree);
+			break;
+
+		case T_AlterExtensionStmt:
+			command = deparse_AlterExtensionStmt(objectId, parsetree);
+			break;
+
+		case T_AlterExtensionContentsStmt:
+			command = deparse_AlterExtensionContentsStmt(objectId, parsetree,
+														 cmd->d.simple.secondaryObject);
+			break;
+
+		case T_CreateFdwStmt:
+			command = deparse_CreateFdwStmt(objectId, parsetree);
+			break;
+
+		case T_AlterFdwStmt:
+			command = deparse_AlterFdwStmt(objectId, parsetree);
+			break;
+
+		case T_CreateForeignServerStmt:
+			command = deparse_CreateForeignServerStmt(objectId, parsetree);
+			break;
+
+		case T_AlterForeignServerStmt:
+			command = deparse_AlterForeignServerStmt(objectId, parsetree);
+			break;
+
+		case T_CreateUserMappingStmt:
+			command = deparse_CreateUserMappingStmt(objectId, parsetree);
+			break;
+
+		case T_AlterUserMappingStmt:
+			command = deparse_AlterUserMappingStmt(objectId, parsetree);
+			break;
+
+		case T_DropUserMappingStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_ImportForeignSchemaStmt:
+			/* generated commands are stashed individually */
+			elog(ERROR, "unexpected command %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
+			command = deparse_CompositeTypeStmt(objectId, parsetree);
+			break;
+
+		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
+			command = deparse_CreateEnumStmt(objectId, parsetree);
+			break;
+
+		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+			command = deparse_CreateRangeStmt(objectId, parsetree);
+			break;
+
+		case T_AlterEnumStmt:
+			command = deparse_AlterEnumStmt(objectId, parsetree);
+			break;
+
+		case T_ViewStmt:		/* CREATE VIEW */
+			command = deparse_ViewStmt(objectId, parsetree);
+			break;
+
+		case T_CreateFunctionStmt:
+			command = deparse_CreateFunction(objectId, parsetree);
+			break;
+
+		case T_AlterFunctionStmt:
+			command = deparse_AlterFunction(objectId, parsetree);
+			break;
+
+		case T_RuleStmt:
+			command = deparse_RuleStmt(objectId, parsetree);
+			break;
+
+		case T_CreateSeqStmt:
+			command = deparse_CreateSeqStmt(objectId, parsetree);
+			break;
+
+		case T_AlterSeqStmt:
+			command = deparse_AlterSeqStmt(objectId, parsetree);
+			break;
+
+		case T_CreateTableAsStmt:
+			command = deparse_CreateTableAsStmt(objectId, parsetree);
+			break;
+
+		case T_RefreshMatViewStmt:
+			command = deparse_RefreshMatViewStmt(objectId, parsetree);
+			break;
+
+		case T_CreateTrigStmt:
+			command = deparse_CreateTrigStmt(objectId, parsetree);
+			break;
+
+		case T_CreatePLangStmt:
+			command = deparse_CreatePLangStmt(objectId, parsetree);
+			break;
+
+		case T_CreateDomainStmt:
+			command = deparse_CreateDomain(objectId, parsetree);
+			break;
+
+		case T_CreateConversionStmt:
+			command = deparse_CreateConversion(objectId, parsetree);
+			break;
+
+		case T_CreateCastStmt:
+			command = deparse_CreateCastStmt(objectId, parsetree);
+			break;
+
+		case T_CreateOpClassStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpFamilyStmt:
+			command = deparse_CreateOpFamily(objectId, parsetree);
+			break;
+
+		case T_AlterOpFamilyStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSDictionaryStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSConfigurationStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_RenameStmt:
+			command = deparse_RenameStmt(cmd->d.simple.address, parsetree);
+			break;
+
+		case T_AlterObjectSchemaStmt:
+			command = deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
+													parsetree,
+													cmd->d.simple.secondaryObject);
+			break;
+
+		case T_AlterOwnerStmt:
+			command = deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree);
+			break;
+
+		case T_CommentStmt:
+			command = deparse_CommentStmt(cmd->d.simple.address, parsetree);
+			break;
+
+		case T_GrantStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropOwnedStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_AlterDefaultPrivilegesStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePolicyStmt:	/* CREATE POLICY */
+			command = deparse_CreatePolicyStmt(objectId, parsetree);
+			break;
+
+		case T_AlterPolicyStmt:		/* ALTER POLICY */
+			command = deparse_AlterPolicyStmt(objectId, parsetree);
+			break;
+
+		case T_SecLabelStmt:
+			command = deparse_SecLabelStmt(cmd->d.simple.address, parsetree);
+			break;
+
+		default:
+			command = NULL;
+			elog(LOG, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return command;
+}
+
+/*
+ * Given a CollectedCommand, return a JSON representation of it.
+ *
+ * The command is expanded fully, so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+Datum
+ddl_deparse_to_json(PG_FUNCTION_ARGS)
+{
+	CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+	OverrideSearchPath *overridePath;
+	MemoryContext	oldcxt;
+	MemoryContext	tmpcxt;
+	ObjTree		   *tree;
+	char		   *command;
+	StringInfoData  str;
+
+	/*
+	 * Allocate everything done by the deparsing routines into a temp context,
+	 * to avoid having to sprinkle them with memory handling code; but allocate
+	 * the output StringInfo before switching.
+	 */
+	initStringInfo(&str);
+	tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "deparse ctx",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * in order to obtain deparsed versions of expressions.  In such results,
+	 * we want all object names to be qualified, so that results are "portable"
+	 * to environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = true;
+	PushOverrideSearchPath(overridePath);
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			tree = deparse_simple_command(cmd);
+			break;
+		case SCT_AlterTable:
+			tree = deparse_AlterTableStmt(cmd);
+			break;
+		case SCT_Grant:
+			tree = deparse_GrantStmt(cmd);
+			break;
+		case SCT_AlterOpFamily:
+			tree = deparse_AlterOpFamily(cmd);
+			break;
+		case SCT_CreateOpClass:
+			tree = deparse_CreateOpClassStmt(cmd);
+			break;
+		case SCT_AlterDefaultPrivileges:
+			tree = deparse_AlterDefaultPrivilegesStmt(cmd);
+			break;
+		case SCT_AlterTSConfig:
+			tree = deparse_AlterTSConfigurationStmt(cmd);
+			break;
+		default:
+			elog(ERROR, "unexpected deparse node type %d", cmd->type);
+	}
+
+	PopOverrideSearchPath();
+
+	if (tree)
+	{
+		Jsonb *jsonb;
+
+		jsonb = objtree_to_jsonb(tree);
+		command = JsonbToCString(&str, &jsonb->root, 128);
+	}
+	else
+		command = NULL;
+
+	/*
+	 * Clean up.  Note that since we created the StringInfo in the caller's
+	 * context, the output string is not deleted here.
+	 */
+	MemoryContextSwitchTo(oldcxt);
+	MemoryContextDelete(tmpcxt);
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(command));
+}
diff --git a/contrib/ddl_deparse/ddl_deparse.control b/contrib/ddl_deparse/ddl_deparse.control
new file mode 100644
index 0000000..bfecb93
--- /dev/null
+++ b/contrib/ddl_deparse/ddl_deparse.control
@@ -0,0 +1,5 @@
+# ddl_deparse extension
+comment = 'DDL deparse support'
+default_version = '1.0'
+module_pathname = '$libdir/ddl_deparse'
+relocatable = true
diff --git a/contrib/ddl_deparse/ddl_deparse.h b/contrib/ddl_deparse/ddl_deparse.h
new file mode 100644
index 0000000..a80c30b
--- /dev/null
+++ b/contrib/ddl_deparse/ddl_deparse.h
@@ -0,0 +1,8 @@
+#ifndef DDL_DEPARSE_H
+#define DDL_DEPARSE_H
+
+#include "tcop/deparse_utility.h"
+
+// extern char *deparse_utility_command(CollectedCommand *cmd);
+
+#endif		/* DDL_DEPARSE_H */
diff --git a/contrib/ddl_deparse/ddl_json.c b/contrib/ddl_deparse/ddl_json.c
new file mode 100644
index 0000000..b67a32a
--- /dev/null
+++ b/contrib/ddl_deparse/ddl_json.c
@@ -0,0 +1,715 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_json.c
+ *	  JSON code related to DDL command deparsing
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  contrib/ddl_deparse/ddl_json.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "lib/stringinfo.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+PG_FUNCTION_INFO_V1(ddl_deparse_expand_command);
+
+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	SpecNumber,
+	SpecStringLiteral,
+	SpecIdentifier,
+	SpecRole
+} convSpecifier;
+
+typedef enum
+{
+	tv_absent,
+	tv_true,
+	tv_false
+} trivalue;
+
+static void expand_one_jsonb_element(StringInfo out, char *param,
+						 JsonbValue *jsonval, convSpecifier specifier,
+						 const char *fmt);
+static void expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep,
+				   convSpecifier specifier, const char *fmt);
+static void fmtstr_error_callback(void *arg);
+
+static trivalue
+find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	bool		result;
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+		return tv_absent;
+	if (value->type != jbvBool)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not of type boolean",
+						keyname)));
+	result = value->val.boolean ? tv_true : tv_false;
+	pfree(value);
+
+	return result;
+}
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvString, an error is raised.  If it doesn't
+ * exist, an error is raised if missing_ok; otherwise return NULL.
+ *
+ * If it exists and is a string, a freshly palloc'ed copy is returned.
+ *
+ * If *length is not NULL, it is set to the length of the string.
+ */
+static char *
+find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname,
+							  bool missing_ok, int *length)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	char	   *str;
+
+	/* XXX verify that this is an object, not an array */
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+	{
+		if (missing_ok)
+			return NULL;
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing element \"%s\" in json object", keyname)));
+	}
+
+	str = pnstrdup(value->val.string.val, value->val.string.len);
+	if (length)
+		*length = value->val.string.len;
+	pfree(value);
+	return str;
+}
+
+#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)
+
+/*
+ * Recursive helper for pg_event_trigger_expand_command
+ *
+ * Find the "fmt" element in the given container, and expand it into the
+ * provided StringInfo.
+ */
+static void
+expand_fmt_recursive(JsonbContainer *container, StringInfo out)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	int			len;
+
+	start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len);
+	end_ptr = start_ptr + len;
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{
+		convSpecifier specifier;
+		bool		is_array;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		is_array = false;
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(out, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			for (; cp < end_ptr;)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing conversion name in conversion specifier")));
+
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'L':
+				specifier = SpecStringLiteral;
+				break;
+			case 'T':
+				specifier = SpecTypename;
+				break;
+			case 'O':
+				specifier = SpecOperatorname;
+				break;
+			case 'n':
+				specifier = SpecNumber;
+				break;
+			case 'R':
+				specifier = SpecRole;
+				break;
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid conversion specifier \"%c\"", *cp)));
+		}
+
+		/*
+		 * Obtain the element to be expanded.
+		 */
+		key.type = jbvString;
+		key.val.string.val = param;
+		key.val.string.len = strlen(param);
+
+		value = findJsonbValueFromContainer(container, JB_FOBJECT, &key);
+
+		/* Validate that we got an array if the format string specified one. */
+
+		/* And finally print out the data */
+		if (is_array)
+			expand_jsonb_array(out, param, value, arraysep, specifier, start_ptr);
+		else
+			expand_one_jsonb_element(out, param, value, specifier, start_ptr);
+	}
+}
+
+/*
+ * Expand a json value as an identifier.  The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	Assert(jsonval->type == jbvString);
+
+	str = pnstrdup(jsonval->val.string.val,
+				   jsonval->val.string.len);
+	appendStringInfoString(buf, quote_identifier(str));
+	pfree(str);
+}
+
+/*
+ * Expand a json value as a dot-separated-name.  The value must be of type
+ * object and must contain elements "schemaname" (optional), "objname"
+ * (mandatory), "attrname" (optional).  Double quotes are added to each element
+ * as necessary, and dot separators where needed.
+ *
+ * One day we might need a "catalog" element as well, but no current use case
+ * needs that.
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+		pfree(str);
+	}
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfo(buf, "%s", quote_identifier(str));
+	pfree(str);
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"attrname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, ".%s", quote_identifier(str));
+		pfree(str);
+	}
+}
+
+/*
+ * expand a json value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename;
+	char	   *typmodstr;
+	trivalue	is_array;
+	char	   *array_decor;
+
+	/*
+	 * We omit schema-qualifying the output name if the schema element is
+	 * either the empty string or NULL; the difference between those two cases
+	 * is that in the latter we quote the type name, in the former we don't.
+	 * This allows for types with special typmod needs, such as interval and
+	 * timestamp (see format_type_detailed), while at the same time allowing
+	 * for the schema name to be omitted from type names that require quotes
+	 * but are to be obtained from a user schema.
+	 */
+
+	schema = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										   "schemaname", true, NULL);
+	typename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											 "typename", false, NULL);
+	typmodstr = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+											  "typmod", true, NULL);
+	is_array = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+										   "typarray");
+	switch (is_array)
+	{
+		default:
+		case tv_absent:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("missing typarray element")));
+			break;
+		case tv_true:
+			array_decor = "[]";
+			break;
+		case tv_false:
+			array_decor = "";
+			break;
+	}
+
+	if (schema == NULL)
+		appendStringInfo(buf, "%s%s%s",
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else if (schema[0] == '\0')
+		appendStringInfo(buf, "%s%s%s",
+						 typename,
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+	else
+		appendStringInfo(buf, "%s.%s%s%s",
+						 quote_identifier(schema),
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 array_decor);
+}
+
+/*
+ * Expand a json value as an operator name
+ */
+static void
+expand_jsonval_operator(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"schemaname", true, NULL);
+	/* schema might be NULL or empty */
+	if (str != NULL && str[0] != '\0')
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+
+	str = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+										"objname", false, NULL);
+	appendStringInfoString(buf, str);
+}
+
+/*
+ * Expand a json value as a string.  The value must be of type string or of
+ * type object.  In the latter 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, JsonbValue *jsonval)
+{
+	if (jsonval->type == jbvString)
+	{
+		appendBinaryStringInfo(buf, jsonval->val.string.val,
+							   jsonval->val.string.len);
+	}
+	else if (jsonval->type == jbvBinary)
+	{
+		trivalue	present;
+
+		present = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											  "present");
+		/*
+		 * If "present" is set to false, this element expands to empty;
+		 * otherwise (either true or absent), fall through to expand "fmt".
+		 */
+		if (present == tv_false)
+			return;
+
+		expand_fmt_recursive(jsonval->val.binary.data, buf);
+	}
+}
+
+/*
+ * Expand a json value as a string literal
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval)
+{
+	char   *str;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int         dqnextchar = 0;
+
+	str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+
+	/* easy case: if there are no ' and no \, just use a single quote */
+	if (strchr(str, '\'') == NULL &&
+		strchr(str, '\\') == NULL)
+	{
+		appendStringInfo(buf, "'%s'", str);
+		pfree(str);
+		return;
+	}
+
+	/* Otherwise need to find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(str, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar %= sizeof(dqsuffixes) - 1;
+	}
+	/* add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data);
+	pfree(dqdelim.data);
+	pfree(str);
+}
+
+/*
+ * Expand a json value as an integer quantity
+ */
+static void
+expand_jsonval_number(StringInfo buf, JsonbValue *jsonval)
+{
+	char *strdatum;
+
+	strdatum = DatumGetCString(DirectFunctionCall1(numeric_out,
+												   NumericGetDatum(jsonval->val.numeric)));
+	appendStringInfoString(buf, strdatum);
+}
+
+/*
+ * Expand a json value as a role name.  If the is_public element is set to
+ * true, PUBLIC is expanded (no quotes); otherwise, expand the given role name,
+ * quoting as an identifier.
+ */
+static void
+expand_jsonval_role(StringInfo buf, JsonbValue *jsonval)
+{
+	trivalue	is_public;
+
+	is_public = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											"is_public");
+	if (is_public == tv_true)
+		appendStringInfoString(buf, "PUBLIC");
+	else
+	{
+		char *rolename;
+
+		rolename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+												 "rolename", false, NULL);
+		appendStringInfoString(buf, quote_identifier(rolename));
+	}
+}
+
+/*
+ * Expand one json element into the output StringInfo according to the
+ * conversion specifier.  The element type is validated, and an error is raised
+ * if it doesn't match what we expect for the conversion specifier.
+ */
+static void
+expand_one_jsonb_element(StringInfo out, char *param, JsonbValue *jsonval,
+						 convSpecifier specifier, const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (!jsonval)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" not found", param)));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%I element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_identifier(out, jsonval);
+			break;
+
+		case SpecDottedName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%D element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_dottedname(out, jsonval);
+			break;
+
+		case SpecString:
+			if (jsonval->type != jbvString &&
+				jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string or object for %%s element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_string(out, jsonval);
+			break;
+
+		case SpecStringLiteral:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON string for %%L element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_strlit(out, jsonval);
+			break;
+
+		case SpecTypename:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%T element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_typename(out, jsonval);
+			break;
+
+		case SpecOperatorname:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%O element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_operator(out, jsonval);
+			break;
+
+		case SpecNumber:
+			if (jsonval->type != jbvNumeric)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON numeric for %%n element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_number(out, jsonval);
+			break;
+
+		case SpecRole:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("expected JSON object for %%R element \"%s\", got %d",
+								param, jsonval->type)));
+			expand_jsonval_role(out, jsonval);
+			break;
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*
+ * Iterate on the elements of a JSON array, expanding each one into the output
+ * StringInfo per the given conversion specifier, separated by the given
+ * separator.
+ */
+static void
+expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep, convSpecifier specifier,
+				   const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+	JsonbContainer *container;
+	JsonbIterator  *it;
+	JsonbValue	v;
+	int			type;
+	bool		first = true;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (jsonarr->type != jbvBinary)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	container = jsonarr->val.binary.data;
+	if ((container->header & JB_FARRAY) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("element \"%s\" is not a JSON array", param)));
+
+	it = JsonbIteratorInit(container);
+	while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+	{
+		switch (type)
+		{
+			case WJB_ELEM:
+				if (!first)
+					appendStringInfoString(out, arraysep);
+				first = false;
+				expand_one_jsonb_element(out, param, &v, specifier, NULL);
+				break;
+		}
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ * n		expand as a simple number (no quoting)
+ * R		expand as a role name (possibly quoted name, or PUBLIC)
+ *
+ * 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.
+ *------
+ */
+Datum
+ddl_deparse_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	Datum		d;
+	Jsonb	   *jsonb;
+	StringInfoData out;
+
+	initStringInfo(&out);
+	d = DirectFunctionCall1(jsonb_in,
+							PointerGetDatum(TextDatumGetCString(json)));
+	jsonb = (Jsonb *) DatumGetPointer(d);
+
+	expand_fmt_recursive(&jsonb->root, &out);
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(out.data));
+}
+
+/*
+ * Error context callback for JSON format string expansion.
+ *
+ * Possible improvement: indicate which element we're expanding, if applicable
+ */
+static void
+fmtstr_error_callback(void *arg)
+{
+	errcontext("while expanding format string \"%s\"", (char *) arg);
+
+}
-- 
2.1.4

#44David Steele
david@pgmasters.net
In reply to: Alvaro Herrera (#43)
Re: deparsing utility commands

On 4/9/15 12:14 PM, Alvaro Herrera wrote:

Alvaro Herrera wrote:

Executive summary:

There is now a CommandDeparse_hook;
deparse_utility_command is provided as an extension, intended for 9.6;
rest of patch would be pushed to 9.5.

Actually here's another approach I like better: use a new pseudotype,
pg_ddl_command, which internally is just a pointer to a CollectedCommand
struct. We cannot give access to the pointer directly in SQL, so much
like type internal or pg_node_tree the in/out functions should just
error out. But the type can be returned as a column in
pg_event_trigger_ddl_command. An extension can create a function that
receives that type as argument, and return a different representation of
the command; the third patch creates a function ddl_deparse_to_json()
which does that.

I've tested this approach and it returns the same results as the
previous approach for all calls to pg_event_trigger_ddl_command() made
by the pg_audit regression tests. For my testing I was generally only
applying patch 0001 and 0002 (although 0001 on it's own also worked for
my case). I applied patch 0003 only for the purpose of being sure it
did not break anything, and did not specifically test the functionality
of 0002 or 0003.

However, I've tested 0001 over a very wide range of commands and have
not found any anomalous behaviors.

I've reviewed the 0001 and 0002 patches and don't have anything to add
beyond what has been mentioned in previous reviews. You've applied your
pattern consistently across a wide variety of utility commands which is
something of a feat.

Since 0003 will not be committed to core, I was more interested in the
mechanism used to connect 0003 to 0001 and 0002. I like the new
approach better than the old one in that there is no connecting hook
now. As you say, it provides more flexibility in terms of using the
command data in as many ways as you like. Even better, the user has
access to three different representations and can choose which one is
best for them. And, of course, they can write code for their own
representation using pg_ddl_command, JSON, or SQL.

I also like the way the patch has been broken up. The core
functionality will finally make event triggers truly useful in a
high-level language like pl/pgsql. I can see many uses for them now
that pg_event_trigger_ddl_command() is present.

From my review and testing, I see no barriers to committing patches 0001
and 0002. I'd love to see this functionality in 9.5.

Regards,
--
- David Steele
david@pgmasters.net

#45Amit Kapila
amit.kapila16@gmail.com
In reply to: Alvaro Herrera (#43)
Re: deparsing utility commands

On Thu, Apr 9, 2015 at 9:44 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Alvaro Herrera wrote:

Executive summary:

There is now a CommandDeparse_hook;
deparse_utility_command is provided as an extension, intended for 9.6;
rest of patch would be pushed to 9.5.

Actually here's another approach I like better: use a new pseudotype,
pg_ddl_command, which internally is just a pointer to a CollectedCommand
struct. We cannot give access to the pointer directly in SQL, so much
like type internal or pg_node_tree the in/out functions should just
error out. But the type can be returned as a column in
pg_event_trigger_ddl_command. An extension can create a function that
receives that type as argument, and return a different representation of
the command; the third patch creates a function ddl_deparse_to_json()
which does that.

You can have as many extensions as you want, and your event triggers can
use the column as many times as necessary. This removes the limitation
of the previous approach that you could only have a single extension
providing a CommandDeparse_hook function.

Few Comments/Questions regrading first 2 patches:

Regarding Patch 0001-deparse-infrastructure-needed-for-command-deparsing

1.
+ * Currently, sql_drop, table_rewrite, ddL_command_end events are the only

/ddL_command_end/ddl_command_end

'L' should be in lower case.

2.
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerAlterTableEnd(void)
{
..
}

Wouldn't the same fix be required for RollbackToSavePoint case
as well? Do you intend to fix this in separate patch?

3.
+/*-------------------------------------------------------------------------
+ *
+ * aclchk_internal.h
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group

Copyright notice years should be same.

4.
+ /*
+ * copying the node is moderately challenging ... 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);

Don't we need to copy (AclMode privileges;)?

5.
-static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid
opfamilyoid,
+static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+ List *opfamilyname, Oid amoid, Oid opfamilyoid,
  int maxOpNumber, int maxProcNumber,
  List *items);
-static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid
opfamilyoid,
+static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+  List *opfamilyname, Oid amoid, Oid opfamilyoid,
   int maxOpNumber, int maxProcNumber,
   List *items);

Now that both the above functions have parameter AlterOpFamilyStmt *stmt,
so can't we get rid of second parameter List *opfamilyname as that
is part of structure AlterOpFamilyStmt?

6.
@@ -1175,204 +1229,258 @@ ProcessUtilitySlow(Node *parsetree,
..
+ EventTriggerAlterTableStart(parsetree);
+ address =
+ DefineIndex(relid, /* OID of heap relation */
+ stmt,
+ InvalidOid, /* no predefined OID */
+ false, /* is_alter_table */
+ true, /* check_rights */
+ false, /* skip_build */
+ false); /* quiet */
+ /*
+ * Add the CREATE INDEX node itself to stash right away; if
+ * there were any commands stashed in the ALTER TABLE code,
+ * we need them to appear after this one.
+ */
+ EventTriggerCollectSimpleCommand(address, secondaryObject,
+ parsetree);
+ commandCollected = true;
+ EventTriggerAlterTableEnd();

Is there a reason why EventTriggerAlterTableRelid() is not called
after EventTriggerAlterTableStart() in this flow?

7.
+void
+EventTriggerInhibitCommandCollection(void)
+void
+EventTriggerUndoInhibitCommandCollection(void)

These function names are understandable, some alternative names could be
InhibitEventTriggerCommandCollection(),
PreventEventTriggerCommandCollection(),
or ProhibitEventTriggerCommandCollection() if you prefer anyone of these
over others.

8.
  case T_CreateOpClassStmt:
- DefineOpClass((CreateOpClassStmt *) parsetree);
+ address = DefineOpClass((CreateOpClassStmt *) parsetree);
+ /* command is stashed in DefineOpClass */
+ commandCollected = true;

Is there a need to remember address if command is already
collected?

Regarding Patch 0002-changes-to-core-to-support-the-deparse-extension:

9.
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)

Is there a reason of exposing new API instead of using existing API
format_procedure_parts()?

As far as I can see the only difference is that in new API, you have
control of whether to schema_qualify it, is that the main reason of
exposing new API?

With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

#46Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Alvaro Herrera (#43)
Re: deparsing utility commands

Hi,

As a comment to the whole email below, I think the following approach is
the best compromise we will find, allowing everybody to come up with
their own format much as in the Logical Decoding plugins world.

Of course, as in the Logical Decoding area, BDR and similar projects
will implement their own representation, in BDR IIUC the DDL will get
translated into a JSON based AST thing.

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

Actually here's another approach I like better: use a new pseudotype,
pg_ddl_command, which internally is just a pointer to a CollectedCommand
struct. We cannot give access to the pointer directly in SQL, so much
like type internal or pg_node_tree the in/out functions should just
error out. But the type can be returned as a column in
pg_event_trigger_ddl_command. An extension can create a function that
receives that type as argument, and return a different representation of
the command; the third patch creates a function ddl_deparse_to_json()
which does that.

You can have as many extensions as you want, and your event triggers can
use the column as many times as necessary. This removes the limitation
of the previous approach that you could only have a single extension
providing a CommandDeparse_hook function.

This patch applies cleanly on top of current master. You need to
install the extension with "CREATE EXTENSION ddl_deparse" after building
it in contrib.

Note: the extension is NOT to be committed to core, only the first two
patches; that will give us more room to revisit the JSON representation
more promptly. My intention is that the extension will be published
elsewhere.

+1 from me,

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

#47Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Amit Kapila (#45)
1 attachment(s)
Re: deparsing utility commands

Here's a cleaned up version of this patch; I threw together a very quick
test module, and updated a conflicting OID. As far as I can tell, I'm
only missing the documentation updates before this is push-able.

One change to note is that the AlterTable support used to ignore
commands that didn't match the OID as set by
EventTriggerAlterTableRelid(); the comment there said that the point was
to avoid collecting the same commands to child tables as recursion
occured in execution. I think that would be imposing such a decision;
perhaps some users of this infrastructure want to know about the
operations as they happen on child tables too. With this definition it
is up to the user module to ignore the duplicates.

Thanks for your review. In reply to your comments:

Amit Kapila wrote:

Few Comments/Questions regrading first 2 patches:

Regarding Patch 0001-deparse-infrastructure-needed-for-command-deparsing

1.
+ * Currently, sql_drop, table_rewrite, ddL_command_end events are the only

/ddL_command_end/ddl_command_end

'L' should be in lower case.

True. Fixed.

2.
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerAlterTableEnd(void)
{
..
}

Wouldn't the same fix be required for RollbackToSavePoint case
as well? Do you intend to fix this in separate patch?

Acrually, I figured that this is not at issue. When a subxact is rolled
back, the whole currentEventTriggerState thing is reverted to a previous
item in the stack; if an event trigger is fired by ALTER TABLE, and the
resulting function invokes ALTER TABLE again, they collect their
commands in separate state elements, so there is never a conflict.
I can't think of any situation in which one event trigger function adds
elements to the list of the calling command.

3.
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group

Copyright notice years should be same.

Yeah, fixed.

4.
+ /*
+ * copying the node is moderately challenging ... 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);

Don't we need to copy (AclMode privileges;)?

AFAICT that's copied by memcpy.

5.
-static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid
opfamilyoid,
+static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+ List *opfamilyname, Oid amoid, Oid opfamilyoid,
int maxOpNumber, int maxProcNumber,
List *items);
-static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid
opfamilyoid,
+static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+  List *opfamilyname, Oid amoid, Oid opfamilyoid,
int maxOpNumber, int maxProcNumber,
List *items);

Now that both the above functions have parameter AlterOpFamilyStmt *stmt,
so can't we get rid of second parameter List *opfamilyname as that
is part of structure AlterOpFamilyStmt?

Yeah, I considered that as I wrote the code but dropped it out of
laziness. I have done so now.

6.
@@ -1175,204 +1229,258 @@ ProcessUtilitySlow(Node *parsetree,
..
+ EventTriggerAlterTableStart(parsetree);
+ address =
+ DefineIndex(relid, /* OID of heap relation */
+ stmt,
+ InvalidOid, /* no predefined OID */
+ false, /* is_alter_table */
+ true, /* check_rights */
+ false, /* skip_build */
+ false); /* quiet */
+ /*
+ * Add the CREATE INDEX node itself to stash right away; if
+ * there were any commands stashed in the ALTER TABLE code,
+ * we need them to appear after this one.
+ */
+ EventTriggerCollectSimpleCommand(address, secondaryObject,
+ parsetree);
+ commandCollected = true;
+ EventTriggerAlterTableEnd();

Is there a reason why EventTriggerAlterTableRelid() is not called
after EventTriggerAlterTableStart() in this flow?

All paths that go through AlterTableInternal() have the Oid set by that
function.

7.
+void
+EventTriggerInhibitCommandCollection(void)
+void
+EventTriggerUndoInhibitCommandCollection(void)

These function names are understandable, some alternative names could be
InhibitEventTriggerCommandCollection(),
PreventEventTriggerCommandCollection(),
or ProhibitEventTriggerCommandCollection() if you prefer anyone of these
over others.

Hmm, most of the reason I picked these names is the EventTrigger prefix.

8.
case T_CreateOpClassStmt:
- DefineOpClass((CreateOpClassStmt *) parsetree);
+ address = DefineOpClass((CreateOpClassStmt *) parsetree);
+ /* command is stashed in DefineOpClass */
+ commandCollected = true;

Is there a need to remember address if command is already
collected?

None. Removed that.

Regarding Patch 0002-changes-to-core-to-support-the-deparse-extension:

I decided against committing 0002 patch for now, as it's mostly code
that would not have any callers in core. If needed, in BDR we can just
duplicate the ruleutils.c code ... it's not a huge problem. We can
reconsider later.

Note: for BDR, the JSON representation is pointless complication. All
it wants is the "normalized" command string unchanged, i.e. add schema
names everywhere and such. We came up with the JSON representation as a
way to appease reviewers here who wanted truly generalized access to the
parse tree. As far as BDR is concerned, we could just as well remove
all the JSON stuff and go back to some simpler representation ... The
only reason to keep it is the expectation that someday (9.6 hopefully)
we will include the whole thing in core.

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

Attachments:

ddl-deparse.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 8e75c27..943909c 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "foreign/foreign.h"
@@ -56,6 +57,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
 #include "utils/acl.h"
+#include "utils/aclchk_internal.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
@@ -65,32 +67,6 @@
 
 
 /*
- * The information about one Grant/Revoke statement, in internal format: object
- * and grantees names have been turned into Oids, the privilege list is an
- * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
- * all_privs is true, 'privileges' will be internally set to the right kind of
- * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
- * InternalGrant struct!)
- *
- * Note: 'all_privs' and 'privileges' represent object-level privileges only.
- * There might also be column-level privilege specifications, which are
- * represented in col_privs (this is a list of untransformed AccessPriv nodes).
- * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
- */
-typedef struct
-{
-	bool		is_grant;
-	GrantObjectType objtype;
-	List	   *objects;
-	bool		all_privs;
-	AclMode		privileges;
-	List	   *col_privs;
-	List	   *grantees;
-	bool		grant_option;
-	DropBehavior behavior;
-} InternalGrant;
-
-/*
  * Internal format used by ALTER DEFAULT PRIVILEGES.
  */
 typedef struct
@@ -605,6 +581,15 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	if (EventTriggerSupportsGrantObjectType(istmt->objtype))
+		EventTriggerCollectGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 0110b06..daf8ddd 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -20,21 +20,28 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_ts_config.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/trigger.h"
 #include "funcapi.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -44,6 +51,9 @@
 
 typedef struct EventTriggerQueryState
 {
+	/* memory context for this state's objects */
+	MemoryContext cxt;
+
 	/* sql_drop */
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
@@ -52,7 +62,10 @@ typedef struct EventTriggerQueryState
 	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite event */
 	int			table_rewrite_reason;	/* AT_REWRITE reason */
 
-	MemoryContext cxt;
+	/* Support for command collection */
+	bool		commandCollectionInhibited;
+	CollectedCommand *currentCommand;
+	List	   *commandList;		/* list of CollectedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -71,6 +84,7 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
 	{"AGGREGATE", true},
 	{"CAST", true},
@@ -139,6 +153,8 @@ static Oid insert_event_trigger_tuple(char *trigname, char *eventname,
 static void validate_ddl_tags(const char *filtervar, List *taglist);
 static void validate_table_rewrite_tags(const char *filtervar, List *taglist);
 static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
+static const char *stringify_grantobjtype(GrantObjectType objtype);
+static const char *stringify_adefprivs_objtype(GrantObjectType objtype);
 
 /*
  * Create an event trigger.
@@ -1206,9 +1222,9 @@ EventTriggerBeginCompleteQuery(void)
 	MemoryContext cxt;
 
 	/*
-	 * Currently, sql_drop and table_rewrite events are the only reason to
-	 * have event trigger state at all; so if there are none, don't install
-	 * one.
+	 * Currently, sql_drop, table_rewrite, ddl_command_end events are the only
+	 * reason to have event trigger state at all; so if there are none, don't
+	 * install one.
 	 */
 	if (!trackDroppedObjectsNeeded())
 		return false;
@@ -1224,6 +1240,10 @@ EventTriggerBeginCompleteQuery(void)
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
 
+	state->commandCollectionInhibited = currentEventTriggerState ?
+		currentEventTriggerState->commandCollectionInhibited : false;
+	state->currentCommand = NULL;
+	state->commandList = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
 
@@ -1262,9 +1282,13 @@ EventTriggerEndCompleteQuery(void)
 bool
 trackDroppedObjectsNeeded(void)
 {
-	/* true if any sql_drop or table_rewrite event trigger exists */
+	/*
+	 * true if any sql_drop, table_rewrite, ddl_command_end event trigger
+	 * exists
+	 */
 	return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 ||
-		list_length(EventCacheLookup(EVT_TableRewrite)) > 0;
+		list_length(EventCacheLookup(EVT_TableRewrite)) > 0 ||
+		list_length(EventCacheLookup(EVT_DDLCommandEnd)) > 0;
 }
 
 /*
@@ -1566,3 +1590,675 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
 }
+
+/*-------------------------------------------------------------------------
+ * Support for DDL command deparsing
+ *
+ * The routines below enable an event trigger function to obtain a list of
+ * DDL commands as they are executed.  There are three main pieces to this
+ * feature:
+ *
+ * 1) Within ProcessUtilitySlow, or some sub-routine thereof, each DDL command
+ * adds a struct CollectedCommand representation of itself to the command list,
+ * using the routines below.
+ *
+ * 2) Some time after that, ddl_command_end fires and the command list is made
+ * available to the event trigger function via pg_event_trigger_ddl_commands();
+ * the complete command details are exposed as a column of type pg_ddl_command.
+ *
+ * 3) An extension can install a function capable of taking a value of type
+ * pg_ddl_command and transform it into some external, user-visible and/or
+ * -modifiable representation.
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Inhibit DDL command collection.
+ */
+void
+EventTriggerInhibitCommandCollection(void)
+{
+	if (!currentEventTriggerState)
+		return;
+
+	currentEventTriggerState->commandCollectionInhibited = true;
+}
+
+/*
+ * Re-establish DDL command collection.
+ */
+void
+EventTriggerUndoInhibitCommandCollection(void)
+{
+	if (!currentEventTriggerState)
+		return;
+
+	currentEventTriggerState->commandCollectionInhibited = false;
+}
+
+/*
+ * EventTriggerCollectSimpleCommand
+ *		Save data about a simple DDL command that was just executed
+ *
+ * address identifies the object being operated on.  secondaryObject is an
+ * object address that was related in some way to the executed command; its
+ * meaning is command-specific.
+ *
+ * For instance, for an ALTER obj SET SCHEMA command, objtype is the type of
+ * object being moved, objectId is its OID, and secondaryOid is the OID of the
+ * old schema.  (The destination schema OID can be obtained by catalog lookup
+ * of the object.)
+ */
+void
+EventTriggerCollectSimpleCommand(ObjectAddress address,
+								 ObjectAddress secondaryObject,
+								 Node *parsetree)
+{
+	MemoryContext oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+
+	command->type = SCT_Simple;
+	command->in_extension = creating_extension;
+
+	command->d.simple.address = address;
+	command->d.simple.secondaryObject = secondaryObject;
+	command->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->commandList = lappend(currentEventTriggerState->commandList,
+											  command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableStart
+ *		Prepare to receive data on an ALTER TABLE command about to be executed
+ *
+ * Note we don't collect the command immediately; instead we keep it in
+ * currentCommand, and only when we're done processing the subcommands we will
+ * add it to the command list.
+ *
+ * XXX -- this API isn't considering the possibility of an ALTER TABLE command
+ * being called reentrantly by an event trigger function.  Do we need stackable
+ * commands at this level?  Perhaps at least we should detect the condition and
+ * raise an error.
+ */
+void
+EventTriggerAlterTableStart(Node *parsetree)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+
+	command->type = SCT_AlterTable;
+	command->in_extension = creating_extension;
+
+	command->d.alterTable.classId = RelationRelationId;
+	command->d.alterTable.objectId = InvalidOid;
+	command->d.alterTable.subcmds = NIL;
+	command->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->currentCommand = command;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remember the OID of the object being affected by an ALTER TABLE.
+ *
+ * This is needed because in some cases we don't know the OID until later.
+ */
+void
+EventTriggerAlterTableRelid(Oid objectId)
+{
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	currentEventTriggerState->currentCommand->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerCollectAlterTableSubcmd
+ *		Save data about a single part of an ALTER TABLE.
+ *
+ * Several different commands go through this path, but apart from ALTER TABLE
+ * itself, they are all concerned with AlterTableCmd nodes that are generated
+ * internally, so that's all that this code needs to handle at the moment.
+ */
+void
+EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
+{
+	MemoryContext	oldcxt;
+	CollectedATSubcmd *newsub;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId));
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(CollectedATSubcmd));
+	newsub->address = address;
+	newsub->parsetree = copyObject(subcmd);
+
+	currentEventTriggerState->currentCommand->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableEnd
+ *		Finish up saving an ALTER TABLE command, and add it to command list.
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerAlterTableEnd(void)
+{
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	/* If no subcommands, don't collect */
+	if (list_length(currentEventTriggerState->currentCommand->d.alterTable.subcmds) != 0)
+	{
+		currentEventTriggerState->commandList =
+			lappend(currentEventTriggerState->commandList,
+					currentEventTriggerState->currentCommand);
+	}
+	else
+		pfree(currentEventTriggerState->currentCommand);
+
+	currentEventTriggerState->currentCommand = NULL;
+}
+
+/*
+ * EventTriggerCollectGrant
+ *		Save data about a GRANT/REVOKE command being executed
+ *
+ * This function creates a copy of the InternalGrant, as the original might
+ * not have the right lifetime.
+ */
+void
+EventTriggerCollectGrant(InternalGrant *istmt)
+{
+	MemoryContext oldcxt;
+	CollectedCommand *command;
+	InternalGrant  *icopy;
+	ListCell	   *cell;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	/*
+	 * This is tedious, but necessary.
+	 */
+	icopy = palloc(sizeof(InternalGrant));
+	memcpy(icopy, istmt, sizeof(InternalGrant));
+	icopy->objects = list_copy(istmt->objects);
+	icopy->grantees = list_copy(istmt->grantees);
+	icopy->col_privs = NIL;
+	foreach(cell, istmt->col_privs)
+		icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell)));
+
+	/* Now collect it, using the copied InternalGrant */
+	command = palloc(sizeof(CollectedCommand));
+	command->type = SCT_Grant;
+	command->in_extension = creating_extension;
+	command->d.grant.istmt = icopy;
+	command->parsetree = NULL;
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterOpFam
+ *		Save data about an ALTER OPERATOR FAMILY ADD/DROP command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
+							List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+	command->type = SCT_AlterOpFamily;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.opfam.address,
+					 OperatorFamilyRelationId, opfamoid);
+	command->d.opfam.operators = operators;
+	command->d.opfam.procedures = procedures;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectCreateOpClass
+ *		Save data about a CREATE OPERATOR CLASS command being executed
+ */
+void
+EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
+							   List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_CreateOpClass;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.createopc.address,
+					 OperatorClassRelationId, opcoid);
+	command->d.createopc.operators = operators;
+	command->d.createopc.procedures = procedures;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterTSConfig
+ *		Save data about an ALTER TEXT SEARCH CONFIGURATION command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
+								 Oid *dictIds, int ndicts)
+{
+	MemoryContext   oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_AlterTSConfig;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.atscfg.address,
+					 TSConfigRelationId, cfgId);
+	command->d.atscfg.dictIds = palloc(sizeof(Oid) * ndicts);
+	memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
+	command->d.atscfg.ndicts = ndicts;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterDefPrivs
+ *		Save data about an ALTER DEFAULT PRIVILEGES command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_AlterDefaultPrivileges;
+	command->d.defprivs.objtype = stmt->action->objtype;
+	command->in_extension = creating_extension;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * In a ddl_command_end event trigger, this function reports the DDL commands
+ * being run.
+ */
+Datum
+pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_ddl_commands()")));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->commandList)
+	{
+		CollectedCommand *cmd = lfirst(lc);
+		Datum		values[9];
+		bool		nulls[9];
+		ObjectAddress addr;
+		int			i = 0;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid.  Don't return anything.
+		 *
+		 * One might think that a viable alternative would be to look up the
+		 * Oid of the existing object and run the deparse with that.  But since
+		 * the parse tree might be different from the one that created the
+		 * object in the first place, we might not end up in a consistent state
+		 * anyway.
+		 */
+		if (cmd->type == SCT_Simple &&
+			!OidIsValid(cmd->d.simple.address.objectId))
+			continue;
+
+		MemSet(nulls, 0, sizeof(nulls));
+
+		switch (cmd->type)
+		{
+			case SCT_Simple:
+			case SCT_AlterTable:
+			case SCT_AlterOpFamily:
+			case SCT_CreateOpClass:
+			case SCT_AlterTSConfig:
+				{
+					char	   *identity;
+					char	   *type;
+					char	   *schema = NULL;
+
+					if (cmd->type == SCT_Simple)
+						addr = cmd->d.simple.address;
+					else if (cmd->type == SCT_AlterTable)
+						ObjectAddressSet(addr,
+										 cmd->d.alterTable.classId,
+										 cmd->d.alterTable.objectId);
+					else if (cmd->type == SCT_AlterOpFamily)
+						addr = cmd->d.opfam.address;
+					else if (cmd->type == SCT_CreateOpClass)
+						addr = cmd->d.createopc.address;
+					else if (cmd->type == SCT_AlterTSConfig)
+						addr = cmd->d.atscfg.address;
+
+					type = getObjectTypeDescription(&addr);
+					identity = getObjectIdentity(&addr);
+
+					/*
+					 * Obtain schema name, if any ("pg_temp" if a temp object).
+					 * If the object class is not in the supported list here,
+					 * we assume it's a schema-less object type, and thus
+					 * "schema" remains set to NULL.
+					 */
+					if (is_objectclass_supported(addr.classId))
+					{
+						AttrNumber	nspAttnum;
+
+						nspAttnum = get_object_attnum_namespace(addr.classId);
+						if (nspAttnum != InvalidAttrNumber)
+						{
+							Relation	catalog;
+							HeapTuple	objtup;
+							Oid			schema_oid;
+							bool		isnull;
+
+							catalog = heap_open(addr.classId, AccessShareLock);
+							objtup = get_catalog_object_by_oid(catalog,
+															   addr.objectId);
+							if (!HeapTupleIsValid(objtup))
+								elog(ERROR, "cache lookup failed for object %u/%u",
+									 addr.classId, addr.objectId);
+							schema_oid =
+								heap_getattr(objtup, nspAttnum,
+											 RelationGetDescr(catalog), &isnull);
+							if (isnull)
+								elog(ERROR,
+									 "invalid null namespace in object %u/%u/%d",
+									 addr.classId, addr.objectId, addr.objectSubId);
+							/* XXX not quite get_namespace_name_or_temp */
+							if (isAnyTempNamespace(schema_oid))
+								schema = pstrdup("pg_temp");
+							else
+								schema = get_namespace_name(schema_oid);
+
+							heap_close(catalog, AccessShareLock);
+						}
+					}
+
+					/* classid */
+					values[i++] = ObjectIdGetDatum(addr.classId);
+					/* objid */
+					values[i++] = ObjectIdGetDatum(addr.objectId);
+					/* objsubid */
+					values[i++] = Int32GetDatum(addr.objectSubId);
+					/* command tag */
+					values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree));
+					/* object_type */
+					values[i++] = CStringGetTextDatum(type);
+					/* schema */
+					if (schema == NULL)
+						nulls[i++] = true;
+					else
+						values[i++] = CStringGetTextDatum(schema);
+					/* identity */
+					values[i++] = CStringGetTextDatum(identity);
+					/* in_extension */
+					values[i++] = BoolGetDatum(cmd->in_extension);
+					/* command */
+					values[i++] = PointerGetDatum(cmd);
+				}
+				break;
+
+			case SCT_AlterDefaultPrivileges:
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree));
+				/* object_type */
+				values[i++] = CStringGetTextDatum(stringify_adefprivs_objtype(
+																			  cmd->d.defprivs.objtype));
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = PointerGetDatum(cmd);
+				break;
+
+			case SCT_Grant:
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.istmt->is_grant ?
+												  "GRANT" : "REVOKE");
+				/* object_type */
+				values[i++] = CStringGetTextDatum(stringify_grantobjtype(
+																		 cmd->d.grant.istmt->objtype));
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = PointerGetDatum(cmd);
+				break;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * Return the GrantObjectType as a string, as it would appear in GRANT and
+ * REVOKE commands.
+ */
+static const char *
+stringify_grantobjtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+			return "COLUMN";
+		case ACL_OBJECT_RELATION:
+			return "TABLE";
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case ACL_OBJECT_DATABASE:
+			return "DATABASE";
+		case ACL_OBJECT_DOMAIN:
+			return "DOMAIN";
+		case ACL_OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case ACL_OBJECT_FOREIGN_SERVER:
+			return "FOREIGN SERVER";
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTION";
+		case ACL_OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case ACL_OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case ACL_OBJECT_NAMESPACE:
+			return "SCHEMA";
+		case ACL_OBJECT_TABLESPACE:
+			return "TABLESPACE";
+		case ACL_OBJECT_TYPE:
+			return "TYPE";
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
+
+/*
+ * Return the GrantObjectType as a string; as above, but use the spelling
+ * in ALTER DEFAULT PRIVILEGES commands instead.
+ */
+static const char *
+stringify_adefprivs_objtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_RELATION:
+			return "TABLES";
+			break;
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTIONS";
+			break;
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCES";
+			break;
+		case ACL_OBJECT_TYPE:
+			return "TYPES";
+			break;
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index c327cc0..3375f10 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -25,6 +25,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
+#include "catalog/opfam_internal.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_namespace.h"
@@ -35,6 +36,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
@@ -47,24 +49,12 @@
 #include "utils/tqual.h"
 
 
-/*
- * We use lists of this struct type to keep track of both operators and
- * procedures while building or adding to an opfamily.
- */
-typedef struct
-{
-	Oid			object;			/* operator or support proc's OID */
-	int			number;			/* strategy or support proc number */
-	Oid			lefttype;		/* lefttype */
-	Oid			righttype;		/* righttype */
-	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
-} OpFamilyMember;
-
-
-static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+				 Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items);
-static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+				  Oid amoid, Oid opfamilyoid,
 				  int maxOpNumber, int maxProcNumber,
 				  List *items);
 static void processTypesSpec(List *args, Oid *lefttype, Oid *righttype);
@@ -675,6 +665,9 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	storeProcedures(stmt->opfamilyname, amoid, opfamilyoid,
 					opclassoid, procedures, false);
 
+	/* let event triggers know what happened */
+	EventTriggerCollectCreateOpClass(stmt, opclassoid, operators, procedures);
+
 	/*
 	 * Create dependencies for the opclass proper.  Note: we do not create a
 	 * dependency link to the AM, because we don't currently support DROP
@@ -822,13 +815,11 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
 	 * ADD and DROP cases need separate code from here on down.
 	 */
 	if (stmt->isDrop)
-		AlterOpFamilyDrop(stmt->opfamilyname, amoid, opfamilyoid,
-						  maxOpNumber, maxProcNumber,
-						  stmt->items);
+		AlterOpFamilyDrop(stmt, amoid, opfamilyoid,
+						  maxOpNumber, maxProcNumber, stmt->items);
 	else
-		AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid,
-						 maxOpNumber, maxProcNumber,
-						 stmt->items);
+		AlterOpFamilyAdd(stmt, amoid, opfamilyoid,
+						 maxOpNumber, maxProcNumber, stmt->items);
 
 	return opfamilyoid;
 }
@@ -837,9 +828,8 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
  * ADD part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
-				 int maxOpNumber, int maxProcNumber,
-				 List *items)
+AlterOpFamilyAdd(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid,
+				 int maxOpNumber, int maxProcNumber, List *items)
 {
 	List	   *operators;		/* OpFamilyMember list for operators */
 	List	   *procedures;		/* OpFamilyMember list for support procs */
@@ -958,19 +948,22 @@ AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 	 * Add tuples to pg_amop and pg_amproc tying in the operators and
 	 * functions.  Dependencies on them are inserted, too.
 	 */
-	storeOperators(opfamilyname, amoid, opfamilyoid,
+	storeOperators(stmt->opfamilyname, amoid, opfamilyoid,
 				   InvalidOid, operators, true);
-	storeProcedures(opfamilyname, amoid, opfamilyoid,
+	storeProcedures(stmt->opfamilyname, amoid, opfamilyoid,
 					InvalidOid, procedures, true);
+
+	/* make information available to event triggers */
+	EventTriggerCollectAlterOpFam(stmt, opfamilyoid,
+								  operators, procedures);
 }
 
 /*
  * DROP part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
-				  int maxOpNumber, int maxProcNumber,
-				  List *items)
+AlterOpFamilyDrop(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid,
+				  int maxOpNumber, int maxProcNumber, List *items)
 {
 	List	   *operators;		/* OpFamilyMember list for operators */
 	List	   *procedures;		/* OpFamilyMember list for support procs */
@@ -1033,8 +1026,12 @@ AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 	/*
 	 * Remove tuples from pg_amop and pg_amproc.
 	 */
-	dropOperators(opfamilyname, amoid, opfamilyoid, operators);
-	dropProcedures(opfamilyname, amoid, opfamilyoid, procedures);
+	dropOperators(stmt->opfamilyname, amoid, opfamilyoid, operators);
+	dropProcedures(stmt->opfamilyname, amoid, opfamilyoid, procedures);
+
+	/* make information available to event triggers */
+	EventTriggerCollectAlterOpFam(stmt, opfamilyoid,
+								  operators, procedures);
 }
 
 
@@ -1673,7 +1670,7 @@ RemoveAmProcEntryById(Oid entryOid)
 	heap_close(rel, RowExclusiveLock);
 }
 
-static char *
+char *
 get_am_name(Oid amOid)
 {
 	HeapTuple	tup;
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index c090ed2..5a7beff 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -25,6 +25,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/schemacmds.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
@@ -52,6 +53,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	Oid			saved_uid;
 	int			save_sec_context;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
 
@@ -143,6 +145,16 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	PushOverrideSearchPath(overridePath);
 
 	/*
+	 * Report the new schema to possibly interested event triggers.  Note we
+	 * must do this here and not in ProcessUtilitySlow because otherwise the
+	 * objects created below are reported before the schema, which would be
+	 * wrong.
+	 */
+	ObjectAddressSet(address, NamespaceRelationId, namespaceId);
+	EventTriggerCollectSimpleCommand(address, InvalidObjectAddress,
+									 (Node *) stmt);
+
+	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
 	 * reorganize them into a sequentially executable order with no forward
 	 * references.  Note that the result is still a list of raw parsetrees ---
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 299d8cc..0a6b069 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2789,6 +2789,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerAlterTableRelid(relid);
+
 	ATController(NULL, rel, cmds, recurse, lockmode);
 }
 
@@ -3672,8 +3674,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 	}
 
-	/* supress compiler warning until we have some use for the address */
-	(void) address;
+	/*
+	 * Report the subcommand to interested event triggers.
+	 */
+	EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
 
 	/*
 	 * Bump the command counter to ensure the next subcommand in the sequence
@@ -9728,7 +9732,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		cmds = lappend(cmds, cmd);
 
+		EventTriggerAlterTableStart((Node *) stmt);
+		/* OID is set by AlterTableInternal */
 		AlterTableInternal(lfirst_oid(l), cmds, false);
+		EventTriggerAlterTableEnd();
 	}
 
 	return new_tablespaceoid;
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 4c404e7..ff90040 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_func.h"
@@ -1442,6 +1443,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 			}
 		}
 	}
+
+	EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict);
 }
 
 /*
@@ -1509,6 +1512,8 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
 
 		i++;
 	}
+
+	EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
 }
 
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 805045d..85ac1b4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3899,6 +3899,7 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 {
 	AlterTSConfigurationStmt *newnode = makeNode(AlterTSConfigurationStmt);
 
+	COPY_SCALAR_FIELD(kind);
 	COPY_NODE_FIELD(cfgname);
 	COPY_NODE_FIELD(tokentype);
 	COPY_NODE_FIELD(dicts);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 578ead5..de48240 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2005,6 +2005,7 @@ static bool
 _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 							   const AlterTSConfigurationStmt *b)
 {
+	COMPARE_SCALAR_FIELD(kind);
 	COMPARE_NODE_FIELD(cfgname);
 	COMPARE_NODE_FIELD(tokentype);
 	COMPARE_NODE_FIELD(dicts);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0180530..845227c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8994,6 +8994,7 @@ AlterTSConfigurationStmt:
 			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_ADD_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = $11;
@@ -9004,6 +9005,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = $11;
@@ -9014,6 +9016,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name any_with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_REPLACE_DICT;
 					n->cfgname = $5;
 					n->tokentype = NIL;
 					n->dicts = list_make2($9,$11);
@@ -9024,6 +9027,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name any_with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = list_make2($11,$13);
@@ -9034,6 +9038,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING FOR name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_DROP_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->missing_ok = false;
@@ -9042,6 +9047,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING IF_P EXISTS FOR name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_DROP_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $11;
 					n->missing_ok = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 59f09dc..78bfd34 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -912,7 +912,9 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	bool		commandCollected = false;
 	ObjectAddress address;
+	ObjectAddress secondaryObject = InvalidObjectAddress;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -931,6 +933,11 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * EventTriggerCollectSimpleCommand called by
+				 * CreateSchemaCommand
+				 */
+				commandCollected = true;
 				break;
 
 			case T_CreateStmt:
@@ -957,6 +964,9 @@ ProcessUtilitySlow(Node *parsetree,
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
 													 InvalidOid, NULL);
+							EventTriggerCollectSimpleCommand(address,
+															 secondaryObject,
+															 stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -989,10 +999,17 @@ ProcessUtilitySlow(Node *parsetree,
 													 InvalidOid, NULL);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
+							EventTriggerCollectSimpleCommand(address,
+															 secondaryObject,
+															 stmt);
 						}
 						else
 						{
-							/* Recurse for anything else */
+							/*
+							 * Recurse for anything else.  Note the recursive
+							 * call will stash the objects so created into our
+							 * event trigger context.
+							 */
 							ProcessUtility(stmt,
 										   queryString,
 										   PROCESS_UTILITY_SUBCOMMAND,
@@ -1005,6 +1022,12 @@ ProcessUtilitySlow(Node *parsetree,
 						if (lnext(l) != NULL)
 							CommandCounterIncrement();
 					}
+
+					/*
+					 * The multiple commands generated here are stashed
+					 * individually, so disable collection below.
+					 */
+					commandCollected = true;
 				}
 				break;
 
@@ -1031,6 +1054,10 @@ ProcessUtilitySlow(Node *parsetree,
 						stmts = transformAlterTableStmt(relid, atstmt,
 														queryString);
 
+						/* ... ensure we have an event trigger context ... */
+						EventTriggerAlterTableStart(parsetree);
+						EventTriggerAlterTableRelid(relid);
+
 						/* ... and do it */
 						foreach(l, stmts)
 						{
@@ -1044,25 +1071,41 @@ ProcessUtilitySlow(Node *parsetree,
 							}
 							else
 							{
-								/* Recurse for anything else */
+								/*
+								 * Recurse for anything else.  If we need to do
+								 * so, "close" the current complex-command set,
+								 * and start a new one at the bottom; this is
+								 * needed to ensure the ordering of queued
+								 * commands is consistent with the way they are
+								 * executed here.
+								 */
+								EventTriggerAlterTableEnd();
 								ProcessUtility(stmt,
 											   queryString,
 											   PROCESS_UTILITY_SUBCOMMAND,
 											   params,
 											   None_Receiver,
 											   NULL);
+								EventTriggerAlterTableStart(parsetree);
+								EventTriggerAlterTableRelid(relid);
 							}
 
 							/* Need CCI between commands */
 							if (lnext(l) != NULL)
 								CommandCounterIncrement();
 						}
+
+						/* done */
+						EventTriggerAlterTableEnd();
 					}
 					else
 						ereport(NOTICE,
 						  (errmsg("relation \"%s\" does not exist, skipping",
 								  atstmt->relation->relname)));
 				}
+
+				/* ALTER TABLE stashes commands internally */
+				commandCollected = true;
 				break;
 
 			case T_AlterDomainStmt:
@@ -1081,31 +1124,37 @@ ProcessUtilitySlow(Node *parsetree,
 							 * Recursively alter column default for table and,
 							 * if requested, for descendants
 							 */
-							AlterDomainDefault(stmt->typeName,
-											   stmt->def);
+							address =
+								AlterDomainDefault(stmt->typeName,
+												   stmt->def);
 							break;
 						case 'N':		/* ALTER DOMAIN DROP NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   false);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   false);
 							break;
 						case 'O':		/* ALTER DOMAIN SET NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   true);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   true);
 							break;
 						case 'C':		/* ADD CONSTRAINT */
-							AlterDomainAddConstraint(stmt->typeName,
-													 stmt->def,
-													 NULL);
+							address =
+								AlterDomainAddConstraint(stmt->typeName,
+														 stmt->def,
+														 &secondaryObject);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
-							AlterDomainDropConstraint(stmt->typeName,
-													  stmt->name,
-													  stmt->behavior,
-													  stmt->missing_ok);
+							address =
+								AlterDomainDropConstraint(stmt->typeName,
+														  stmt->name,
+														  stmt->behavior,
+														  stmt->missing_ok);
 							break;
 						case 'V':		/* VALIDATE CONSTRAINT */
-							AlterDomainValidateConstraint(stmt->typeName,
-														  stmt->name);
+							address =
+								AlterDomainValidateConstraint(stmt->typeName,
+															  stmt->name);
 							break;
 						default:		/* oops */
 							elog(ERROR, "unrecognized alter domain type: %d",
@@ -1125,41 +1174,46 @@ ProcessUtilitySlow(Node *parsetree,
 					switch (stmt->kind)
 					{
 						case OBJECT_AGGREGATE:
-							DefineAggregate(stmt->defnames, stmt->args,
-											stmt->oldstyle, stmt->definition,
-											queryString);
+							address =
+								DefineAggregate(stmt->defnames, stmt->args,
+												stmt->oldstyle,
+												stmt->definition, queryString);
 							break;
 						case OBJECT_OPERATOR:
 							Assert(stmt->args == NIL);
-							DefineOperator(stmt->defnames, stmt->definition);
+							address = DefineOperator(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TYPE:
 							Assert(stmt->args == NIL);
-							DefineType(stmt->defnames, stmt->definition);
+							address = DefineType(stmt->defnames,
+												  stmt->definition);
 							break;
 						case OBJECT_TSPARSER:
 							Assert(stmt->args == NIL);
-							DefineTSParser(stmt->defnames, stmt->definition);
+							address = DefineTSParser(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TSDICTIONARY:
 							Assert(stmt->args == NIL);
-							DefineTSDictionary(stmt->defnames,
-											   stmt->definition);
+							address = DefineTSDictionary(stmt->defnames,
+														  stmt->definition);
 							break;
 						case OBJECT_TSTEMPLATE:
 							Assert(stmt->args == NIL);
-							DefineTSTemplate(stmt->defnames,
-											 stmt->definition);
+							address = DefineTSTemplate(stmt->defnames,
+														stmt->definition);
 							break;
 						case OBJECT_TSCONFIGURATION:
 							Assert(stmt->args == NIL);
-							DefineTSConfiguration(stmt->defnames,
-												  stmt->definition,
-												  NULL);
+							address = DefineTSConfiguration(stmt->defnames,
+															 stmt->definition,
+															 &secondaryObject);
 							break;
 						case OBJECT_COLLATION:
 							Assert(stmt->args == NIL);
-							DefineCollation(stmt->defnames, stmt->definition);
+							address = DefineCollation(stmt->defnames,
+													   stmt->definition);
 							break;
 						default:
 							elog(ERROR, "unrecognized define stmt type: %d",
@@ -1200,143 +1254,184 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
-					DefineIndex(relid,	/* OID of heap relation */
-								stmt,
-								InvalidOid,		/* no predefined OID */
-								false,	/* is_alter_table */
-								true,	/* check_rights */
-								false,	/* skip_build */
-								false); /* quiet */
+					EventTriggerAlterTableStart(parsetree);
+					address =
+						DefineIndex(relid,	/* OID of heap relation */
+									stmt,
+									InvalidOid,		/* no predefined OID */
+									false,	/* is_alter_table */
+									true,	/* check_rights */
+									false,	/* skip_build */
+									false); /* quiet */
+					/*
+					 * Add the CREATE INDEX node itself to stash right away; if
+					 * there were any commands stashed in the ALTER TABLE code,
+					 * we need them to appear after this one.
+					 */
+					EventTriggerCollectSimpleCommand(address, secondaryObject,
+													 parsetree);
+					commandCollected = true;
+					EventTriggerAlterTableEnd();
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				address = CreateExtension((CreateExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				address = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
-											   NULL);
+				address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+														  &secondaryObject);
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				address = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
 				break;
 
 			case T_AlterFdwStmt:
-				AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				address = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				address = CreateForeignServer((CreateForeignServerStmt *) parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
-				AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				address = AlterForeignServer((AlterForeignServerStmt *) parsetree);
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				address = CreateUserMapping((CreateUserMappingStmt *) parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
-				AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				address = AlterUserMapping((AlterUserMappingStmt *) parsetree);
 				break;
 
 			case T_DropUserMappingStmt:
 				RemoveUserMapping((DropUserMappingStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_ImportForeignSchemaStmt:
 				ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+				/* commands are stashed inside ImportForeignSchema */
+				commandCollected = true;
 				break;
 
 			case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					address = DefineCompositeType(stmt->typevar,
+												  stmt->coldeflist);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				address = DefineEnum((CreateEnumStmt *) parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				address = DefineRange((CreateRangeStmt *) parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				address = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerAlterTableStart(parsetree);
+				address = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerCollectSimpleCommand(address, secondaryObject,
+												 parsetree);
+				/* stashed internally */
+				commandCollected = true;
+				EventTriggerAlterTableEnd();
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				address = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				address = AlterFunction((AlterFunctionStmt *) parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				address = DefineRule((RuleStmt *) parsetree, queryString);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				address = DefineSequence((CreateSeqStmt *) parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				address = AlterSequence((AlterSeqStmt *) parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
-								   queryString, params, completionTag);
+				/*
+				 * REFRSH CONCURRENTLY executes some DDL commands internally.
+				 * Inhibit DDL command collection here to avoid those commands
+				 * from showing up in the deparsed command queue.  The refresh
+				 * command itself is queued, which is enough.
+				 */
+				EventTriggerInhibitCommandCollection();
+				PG_TRY();
+				{
+					address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+												 queryString, params, completionTag);
+				}
+				PG_CATCH();
+				{
+					EventTriggerUndoInhibitCommandCollection();
+					PG_RE_THROW();
+				}
+				PG_END_TRY();
+				EventTriggerUndoInhibitCommandCollection();
 				break;
 
 			case T_CreateTrigStmt:
-				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
-									 InvalidOid, InvalidOid, InvalidOid,
-									 InvalidOid, false);
+				address = CreateTrigger((CreateTrigStmt *) parsetree,
+										 queryString, InvalidOid, InvalidOid,
+										 InvalidOid, InvalidOid, false);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				address = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				address = DefineDomain((CreateDomainStmt *) parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				address = CreateConversionCommand((CreateConversionStmt *) parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				address = CreateCast((CreateCastStmt *) parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
 				DefineOpClass((CreateOpClassStmt *) parsetree);
+				/* command is stashed in DefineOpClass */
+				commandCollected = true;
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				address = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
 				break;
 
 			case T_CreateTransformStmt:
@@ -1345,63 +1440,76 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_AlterOpFamilyStmt:
 				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				/* commands are stashed in AlterOpFamily */
+				commandCollected = true;
 				break;
 
 			case T_AlterTSDictionaryStmt:
-				AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				address = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
 				break;
 
 			case T_AlterTableMoveAllStmt:
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
+				/* commands are stashed in AlterTableMoveAll */
+				commandCollected = true;
 				break;
 
 			case T_DropStmt:
 				ExecDropStmt((DropStmt *) parsetree, isTopLevel);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree);
+				address = ExecRenameStmt((RenameStmt *) parsetree);
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
-										  NULL);
+				address =
+					ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
+											  &secondaryObject);
 				break;
 
 			case T_AlterOwnerStmt:
-				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
 			case T_CommentStmt:
-				CommentObject((CommentStmt *) parsetree);
+				address = CommentObject((CommentStmt *) parsetree);
 				break;
 
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
+				/* commands are stashed in ExecGrantStmt_oids */
+				commandCollected = true;
 				break;
 
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_AlterDefaultPrivilegesStmt:
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
+				EventTriggerCollectAlterDefPrivs((AlterDefaultPrivilegesStmt *) parsetree);
+				commandCollected = true;
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
-				CreatePolicy((CreatePolicyStmt *) parsetree);
+				address = CreatePolicy((CreatePolicyStmt *) parsetree);
 				break;
 
 			case T_AlterPolicyStmt:		/* ALTER POLICY */
-				AlterPolicy((AlterPolicyStmt *) parsetree);
+				address = AlterPolicy((AlterPolicyStmt *) parsetree);
 				break;
 
 			case T_SecLabelStmt:
-				ExecSecLabelStmt((SecLabelStmt *) parsetree);
+				address = ExecSecLabelStmt((SecLabelStmt *) parsetree);
 				break;
 
 			default:
@@ -1410,6 +1518,14 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 		}
 
+		/*
+		 * Remember the object so that ddl_command_end event triggers have
+		 * access to it.
+		 */
+		if (!commandCollected)
+			EventTriggerCollectSimpleCommand(address, secondaryObject,
+											 parsetree);
+
 		if (isCompleteQuery)
 		{
 			EventTriggerSQLDrop(parsetree);
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 2f0f0a1..9b674ce 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
 	return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
@@ -323,7 +326,6 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
-
 /*
  * Add typmod decoration to the basic type name
  */
@@ -338,7 +340,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,7 +352,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/include/catalog/opfam_internal.h b/src/include/catalog/opfam_internal.h
new file mode 100644
index 0000000..f01dcbe
--- /dev/null
+++ b/src/include/catalog/opfam_internal.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * opfam_internal.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/opfam_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OPFAM_INTERNAL_H
+#define OPFAM_INTERNAL_H
+
+/*
+ * We use lists of this struct type to keep track of both operators and
+ * procedures while building or adding to an opfamily.
+ */
+typedef struct
+{
+	Oid			object;			/* operator or support proc's OID */
+	int			number;			/* strategy or support proc number */
+	Oid			lefttype;		/* lefttype */
+	Oid			righttype;		/* righttype */
+	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
+} OpFamilyMember;
+
+#endif		/* OPFAM_INTERNAL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 55c246e..c50b2e1 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5128,6 +5128,8 @@ DATA(insert OID = 4566 (  pg_event_trigger_table_rewrite_oid	PGNSP PGUID 12 1 0
 DESCR("return Oid of the table getting rewritten");
 DATA(insert OID = 4567 (  pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ ));
 DESCR("return reason code for table getting rewritten");
+DATA(insert OID = 4568 (  pg_event_trigger_ddl_commands			PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,88}" "{o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, command_tag, object_type, schema, identity, in_extension, command}" _null_ _null_ pg_event_trigger_ddl_commands _null_ _null_ _null_ ));
+DESCR("list DDL actions being executed by the current command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 0a900dd..b340f06 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -364,6 +364,11 @@ DATA(insert OID = 194 ( pg_node_tree	PGNSP PGUID -1 f b S f t \054 0 0 0 pg_node
 DESCR("string representing an internal node tree");
 #define PGNODETREEOID	194
 
+/* DATA(insert OID = 88 ( pg_ddl_command PGNSP PGUID -1 f b P f \054 0 0 0 pg_ddl_command_in pg_ddl_command_out pg_ddl_command_recv pg_ddl_command_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ )); */
+DATA(insert OID = 88 ( pg_ddl_command   PGNSP PGUID SIZEOF_POINTER t b P f t \054 0 0 0 - - - - - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("internal type for passing CollectedCommand");
+#define PGDDLCOMMANDOID 88
+
 /* OIDS 200 - 299 */
 
 DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 335f09c..c3a1748 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -90,6 +90,7 @@ extern void IsThereOpClassInNamespace(const char *opcname, Oid opcmethod,
 extern void IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod,
 						   Oid opfnamespace);
 extern Oid	get_am_oid(const char *amname, bool missing_ok);
+extern char *get_am_name(Oid amOid);
 extern Oid	get_opclass_oid(Oid amID, List *opclassname, bool missing_ok);
 extern Oid	get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok);
 
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 7eb2156..579e1ef 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -17,6 +17,8 @@
 #include "catalog/objectaddress.h"
 #include "catalog/pg_event_trigger.h"
 #include "nodes/parsenodes.h"
+#include "utils/aclchk_internal.h"
+#include "tcop/deparse_utility.h"
 
 typedef struct EventTriggerData
 {
@@ -60,4 +62,28 @@ extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 							 bool original, bool normal);
 
+extern void EventTriggerInhibitCommandCollection(void);
+extern void EventTriggerUndoInhibitCommandCollection(void);
+
+extern void EventTriggerCollectSimpleCommand(ObjectAddress address,
+								 ObjectAddress secondaryObject,
+								 Node *parsetree);
+
+extern void EventTriggerAlterTableStart(Node *parsetree);
+extern void EventTriggerAlterTableRelid(Oid objectId);
+extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd,
+								  ObjectAddress address);
+extern void EventTriggerAlterTableEnd(void);
+
+extern void EventTriggerCollectGrant(InternalGrant *istmt);
+extern void EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt,
+							  Oid opfamoid, List *operators,
+							  List *procedures);
+extern void EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt,
+								 Oid opcoid, List *operators,
+								 List *procedures);
+extern void EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt,
+								 Oid cfgId, Oid *dictIds, int ndicts);
+extern void EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 40ecea2..0423350 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -24,7 +24,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 852eb4f..9819703 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2874,9 +2874,19 @@ typedef struct AlterTSDictionaryStmt
 /*
  * TS Configuration stmts: DefineStmt, RenameStmt and DropStmt are default
  */
+typedef enum AlterTSConfigType
+{
+	ALTER_TSCONFIG_ADD_MAPPING,
+	ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN,
+	ALTER_TSCONFIG_REPLACE_DICT,
+	ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN,
+	ALTER_TSCONFIG_DROP_MAPPING
+} AlterTSConfigType;
+
 typedef struct AlterTSConfigurationStmt
 {
 	NodeTag		type;
+	AlterTSConfigType	kind;	/* ALTER_TSCONFIG_ADD_MAPPING, etc */
 	List	   *cfgname;		/* qualified name (list of Value strings) */
 
 	/*
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..b6bcbeb
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,105 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+#include "access/attnum.h"
+#include "catalog/objectaddress.h"
+#include "nodes/nodes.h"
+#include "utils/aclchk_internal.h"
+
+
+/*
+ * Support for keeping track of collected commands.
+ */
+typedef enum CollectedCommandType
+{
+	SCT_Simple,
+	SCT_AlterTable,
+	SCT_Grant,
+	SCT_AlterOpFamily,
+	SCT_AlterDefaultPrivileges,
+	SCT_CreateOpClass,
+	SCT_AlterTSConfig
+} CollectedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct CollectedATSubcmd
+{
+	ObjectAddress	address; /* affected column, constraint, index, ... */
+	Node		   *parsetree;
+} CollectedATSubcmd;
+
+typedef struct CollectedCommand
+{
+	CollectedCommandType type;
+	bool		in_extension;
+	Node	   *parsetree;
+
+	union
+	{
+		/* most commands */
+		struct
+		{
+			ObjectAddress address;
+			ObjectAddress secondaryObject;
+		} simple;
+
+		/* ALTER TABLE, and internal uses thereof */
+		struct
+		{
+			Oid		objectId;
+			Oid		classId;
+			List   *subcmds;
+		} alterTable;
+
+		/* GRANT / REVOKE */
+		struct
+		{
+			InternalGrant *istmt;
+		} grant;
+
+		/* ALTER OPERATOR FAMILY */
+		struct
+		{
+			ObjectAddress address;
+			List   *operators;
+			List   *procedures;
+		} opfam;
+
+		/* CREATE OPERATOR CLASS */
+		struct
+		{
+			ObjectAddress address;
+			List   *operators;
+			List   *procedures;
+		} createopc;
+
+		/* ALTER TEXT SEARCH CONFIGURATION ADD/ALTER/DROP MAPPING */
+		struct
+		{
+			ObjectAddress address;
+			Oid	   *dictIds;
+			int		ndicts;
+		} atscfg;
+
+		/* ALTER DEFAULT PRIVILEGES */
+		struct
+		{
+			GrantObjectType objtype;
+		} defprivs;
+	} d;
+} CollectedCommand;
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/aclchk_internal.h b/src/include/utils/aclchk_internal.h
new file mode 100644
index 0000000..0855bf1
--- /dev/null
+++ b/src/include/utils/aclchk_internal.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * aclchk_internal.h
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/aclchk_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLCHK_INTERNAL_H
+#define ACLCHK_INTERNAL_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+/*
+ * The information about one Grant/Revoke statement, in internal format: object
+ * and grantees names have been turned into Oids, the privilege list is an
+ * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
+ * all_privs is true, 'privileges' will be internally set to the right kind of
+ * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
+ * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
+ */
+typedef struct
+{
+	bool		is_grant;
+	GrantObjectType objtype;
+	List	   *objects;
+	bool		all_privs;
+	AclMode		privileges;
+	List	   *col_privs;
+	List	   *grantees;
+	bool		grant_option;
+	DropBehavior behavior;
+} InternalGrant;
+
+
+#endif	/* ACLCHK_INTERNAL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 33a453f..dc884ad 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1218,6 +1218,7 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 730fa75..8213e23 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -6,11 +6,12 @@ include $(top_builddir)/src/Makefile.global
 
 SUBDIRS = \
 		  commit_ts \
-		  worker_spi \
 		  dummy_seclabel \
+		  test_ddl_deparse \
+		  test_parser \
 		  test_rls_hooks \
 		  test_shm_mq \
-		  test_parser
+		  worker_spi
 
 all: submake-errcodes
 
diff --git a/src/test/modules/test_ddl_deparse/Makefile b/src/test/modules/test_ddl_deparse/Makefile
new file mode 100644
index 0000000..246677d
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/Makefile
@@ -0,0 +1,18 @@
+MODULES = test_ddl_deparse
+PGFILEDESC = "test_ddl_deparse - regression testing for DDL deparsing"
+
+EXTENSION = test_ddl_deparse
+DATA = test_ddl_deparse--1.0.sql
+
+REGRESS = test_ddl_deparse
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_ddl_deparse
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out
new file mode 100644
index 0000000..1f869e9
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out
@@ -0,0 +1,48 @@
+CREATE EXTENSION test_ddl_deparse;
+CREATE OR REPLACE FUNCTION test_ddl_deparse()
+  RETURNS event_trigger LANGUAGE plpgsql AS
+$$
+DECLARE
+	r record;
+	r2 record;
+	cmdtype text;
+	tag text;
+BEGIN
+	FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+	LOOP
+		-- verify that tags match
+		tag = get_command_tag(r.command);
+		IF tag <> r.command_tag THEN
+			RAISE WARNING 'tag % doesn''t match %', tag, r.command_tag;
+		END IF;
+
+		-- log the operation
+		cmdtype = get_command_type(r.command);
+		RAISE NOTICE 'type %, tag %', cmdtype, tag, ;
+
+		-- if alter table, log more
+		IF cmdtype = 'alter table' THEN
+			FOR r2 IN SELECT *
+						FROM unnest(get_altertable_subcmdtypes(r.command))
+			LOOP
+				RAISE NOTICE '  subcommand: %', r2.unnest;
+			END LOOP;
+		END IF;
+	END LOOP;
+END;
+$$;
+ERROR:  missing expression at or near ";"
+LINE 20:   RAISE NOTICE 'type %, tag %', cmdtype, tag, ;
+                                                       ^
+CREATE EVENT TRIGGER test_ddl_deparse
+ON ddl_command_end EXECUTE PROCEDURE test_ddl_deparse();
+ERROR:  function test_ddl_deparse() does not exist
+CREATE TABLE ddl_test_p1();
+CREATE TABLE ddl_test_p2();
+CREATE TABLE ddl_test(a serial primary key) INHERITS (ddl_test_p1);
+ALTER TABLE ddl_test
+   DROP CONSTRAINT ddl_test_pkey,
+   ADD COLUMN b serial,
+   INHERIT ddl_test_p2,
+   NO INHERIT ddl_test_p1
+;
diff --git a/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql
new file mode 100644
index 0000000..9b7edee
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql
@@ -0,0 +1,49 @@
+CREATE EXTENSION test_ddl_deparse;
+
+CREATE OR REPLACE FUNCTION test_ddl_deparse()
+  RETURNS event_trigger LANGUAGE plpgsql AS
+$$
+DECLARE
+	r record;
+	r2 record;
+	cmdtype text;
+	tag text;
+BEGIN
+	FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+	LOOP
+		-- verify that tags match
+		tag = get_command_tag(r.command);
+		IF tag <> r.command_tag THEN
+			RAISE WARNING 'tag % doesn''t match %', tag, r.command_tag;
+		END IF;
+
+		-- log the operation
+		cmdtype = get_command_type(r.command);
+		RAISE NOTICE 'type %, tag %', cmdtype, tag, ;
+
+		-- if alter table, log more
+		IF cmdtype = 'alter table' THEN
+			FOR r2 IN SELECT *
+						FROM unnest(get_altertable_subcmdtypes(r.command))
+			LOOP
+				RAISE NOTICE '  subcommand: %', r2.unnest;
+			END LOOP;
+		END IF;
+	END LOOP;
+END;
+$$;
+
+CREATE EVENT TRIGGER test_ddl_deparse
+ON ddl_command_end EXECUTE PROCEDURE test_ddl_deparse();
+
+CREATE TABLE ddl_test_p1();
+CREATE TABLE ddl_test_p2();
+
+CREATE TABLE ddl_test(a serial primary key) INHERITS (ddl_test_p1);
+
+ALTER TABLE ddl_test
+   DROP CONSTRAINT ddl_test_pkey,
+   ADD COLUMN b serial,
+   INHERIT ddl_test_p2,
+   NO INHERIT ddl_test_p1
+;
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql b/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql
new file mode 100644
index 0000000..093005a
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql
@@ -0,0 +1,16 @@
+/* src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ddl_deparse" to load this file. \quit
+
+CREATE FUNCTION get_command_type(pg_ddl_command)
+  RETURNS text IMMUTABLE STRICT
+  AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_command_tag(pg_ddl_command)
+  RETURNS text IMMUTABLE STRICT
+  AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_altertable_subcmdtypes(pg_ddl_command)
+  RETURNS text[] IMMUTABLE STRICT
+  AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
new file mode 100644
index 0000000..97ecbfd
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -0,0 +1,264 @@
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(get_command_type);
+PG_FUNCTION_INFO_V1(get_command_tag);
+PG_FUNCTION_INFO_V1(get_altertable_subcmdtypes);
+
+Datum
+get_command_type(PG_FUNCTION_ARGS)
+{
+	CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+	const char *type;
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			type = "simple";
+			break;
+		case SCT_AlterTable:
+			type = "alter table";
+			break;
+		case SCT_Grant:
+			type = "grant";
+			break;
+		case SCT_AlterOpFamily:
+			type = "alter operator family";
+			break;
+		case SCT_AlterDefaultPrivileges:
+			type = "alter default privileges";
+			break;
+		case SCT_CreateOpClass:
+			type = "create operator class";
+			break;
+		case SCT_AlterTSConfig:
+			type = "alter text search configuration";
+			break;
+		default:
+			type = "unknown command type";
+			break;
+	}
+
+	PG_RETURN_TEXT_P(cstring_to_text(type));
+}
+
+Datum
+get_command_tag(PG_FUNCTION_ARGS)
+{
+	CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+
+	PG_RETURN_TEXT_P(cstring_to_text(CreateCommandTag(cmd->parsetree)));
+}
+
+Datum
+get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
+{
+	CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+	ArrayBuildState	*astate = NULL;
+	ListCell *cell;
+
+	if (cmd->type != SCT_AlterTable)
+		elog(ERROR, "command is not ALTER TABLE");
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		CollectedATSubcmd *sub = lfirst(cell);
+		AlterTableCmd  *subcmd = (AlterTableCmd *) sub->parsetree;
+		const char     *strtype;
+
+		Assert(IsA(subcmd, AlterTableCmd));
+
+		switch (subcmd->subtype)
+		{
+			case AT_AddColumn:
+				strtype = "ADD COLUMN";
+				break;
+			case AT_AddColumnRecurse:
+				strtype = "ADD COLUMN (and recurse)";
+				break;
+			case AT_AddColumnToView:
+				strtype = "ADD COLUMN TO VIEW";
+				break;
+			case AT_ColumnDefault:
+				strtype = "ALTER COLUMN SET DEFAULT";
+				break;
+			case AT_DropNotNull:
+				strtype = "DROP NOT NULL";
+				break;
+			case AT_SetNotNull:
+				strtype = "SET NOT NULL";
+				break;
+			case AT_SetStatistics:
+				strtype = "SET STATS";
+				break;
+			case AT_SetOptions:
+				strtype = "SET OPTIONS";
+				break;
+			case AT_ResetOptions:
+				strtype = "RESET OPTIONS";
+				break;
+			case AT_SetStorage:
+				strtype = "SET STORAGE";
+				break;
+			case AT_DropColumn:
+				strtype = "DROP COLUMN";
+				break;
+			case AT_DropColumnRecurse:
+				strtype = "DROP COLUMN (and recurse)";
+				break;
+			case AT_AddIndex:
+				strtype = "ADD INDEX";
+				break;
+			case AT_ReAddIndex:
+				strtype = "(re) ADD INDEX";
+				break;
+			case AT_AddConstraint:
+				strtype = "ADD CONSTRAINT";
+				break;
+			case AT_AddConstraintRecurse:
+				strtype = "ADD CONSTRAINT (and recurse)";
+				break;
+			case AT_ReAddConstraint:
+				strtype = "(re) ADD CONSTRAINT";
+				break;
+			case AT_AlterConstraint:
+				strtype = "ALTER CONSTRAINT";
+				break;
+			case AT_ValidateConstraint:
+				strtype = "VALIDATE CONSTRAINT";
+				break;
+			case AT_ValidateConstraintRecurse:
+				strtype = "VALIDATE CONSTRAINT (and recurse)";
+				break;
+			case AT_ProcessedConstraint:
+				strtype = "ADD (processed) CONSTRAINT";
+				break;
+			case AT_AddIndexConstraint:
+				strtype = "ADD CONSTRAINT (using index)";
+				break;
+			case AT_DropConstraint:
+				strtype = "DROP CONSTRAINT";
+				break;
+			case AT_DropConstraintRecurse:
+				strtype = "DROP CONSTRAINT (and recurse)";
+				break;
+			case AT_AlterColumnType:
+				strtype = "ALTER COLUMN SET TYPE";
+				break;
+			case AT_AlterColumnGenericOptions:
+				strtype = "ALTER COLUMN SET OPTIONS";
+				break;
+			case AT_ChangeOwner:
+				strtype = "CHANGE OWNER";
+				break;
+			case AT_ClusterOn:
+				strtype = "CLUSTER";
+				break;
+			case AT_DropCluster:
+				strtype = "DROP CLUSTER";
+				break;
+			case AT_SetLogged:
+				strtype = "SET LOGGED";
+				break;
+			case AT_SetUnLogged:
+				strtype = "SET UNLOGGED";
+				break;
+			case AT_AddOids:
+				strtype = "ADD OIDS";
+				break;
+			case AT_AddOidsRecurse:
+				strtype = "ADD OIDS (and recurse)";
+				break;
+			case AT_DropOids:
+				strtype = "DROP OIDS";
+				break;
+			case AT_SetTableSpace:
+				strtype = "SET TABLESPACE";
+				break;
+			case AT_SetRelOptions:
+				strtype = "SET RELOPTIONS";
+				break;
+			case AT_ResetRelOptions:
+				strtype = "RESET RELOPTIONS";
+				break;
+			case AT_ReplaceRelOptions:
+				strtype = "REPLACE RELOPTIONS";
+				break;
+			case AT_EnableTrig:
+				strtype = "ENABLE TRIGGER";
+				break;
+			case AT_EnableAlwaysTrig:
+				strtype = "ENABLE TRIGGER (always)";
+				break;
+			case AT_EnableReplicaTrig:
+				strtype = "ENABLE TRIGGER (replica)";
+				break;
+			case AT_DisableTrig:
+				strtype = "DISABLE TRIGGER";
+				break;
+			case AT_EnableTrigAll:
+				strtype = "ENABLE TRIGGER (all)";
+				break;
+			case AT_DisableTrigAll:
+				strtype = "DISABLE TRIGGER (all)";
+				break;
+			case AT_EnableTrigUser:
+				strtype = "ENABLE TRIGGER (user)";
+				break;
+			case AT_DisableTrigUser:
+				strtype = "DISABLE TRIGGER (user)";
+				break;
+			case AT_EnableRule:
+				strtype = "ENABLE RULE";
+				break;
+			case AT_EnableAlwaysRule:
+				strtype = "ENABLE RULE (always)";
+				break;
+			case AT_EnableReplicaRule:
+				strtype = "ENABLE RULE (replica)";
+				break;
+			case AT_DisableRule:
+				strtype = "DISABLE RULE";
+				break;
+			case AT_AddInherit:
+				strtype = "ADD INHERIT";
+				break;
+			case AT_DropInherit:
+				strtype = "DROP INHERIT";
+				break;
+			case AT_AddOf:
+				strtype = "OF";
+				break;
+			case AT_DropOf:
+				strtype = "NOT OF";
+				break;
+			case AT_ReplicaIdentity:
+				strtype = "REPLICA IDENTITY";
+				break;
+			case AT_EnableRowSecurity:
+				strtype = "ENABLE ROW SECURITY";
+				break;
+			case AT_DisableRowSecurity:
+				strtype = "DISABLE ROW SECURITY";
+				break;
+			case AT_GenericOptions:
+				strtype = "SET OPTIONS";
+				break;
+		}
+
+		astate =
+			accumArrayResult(astate, CStringGetTextDatum(strtype),
+							 false, TEXTOID, CurrentMemoryContext);
+	}
+
+	if (astate == NULL)
+		elog(ERROR, "empty alter table subcommand list");
+
+	PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
+}
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.control b/src/test/modules/test_ddl_deparse/test_ddl_deparse.control
new file mode 100644
index 0000000..09112ee
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.control
@@ -0,0 +1,4 @@
+comment = 'Test code for DDL deparse feature'
+default_version = '1.0'
+module_pathname = '$libdir/test_ddl_deparse'
+relocatable = true
#48Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#47)
Re: deparsing utility commands

Alvaro Herrera wrote:

Here's a cleaned up version of this patch; I threw together a very quick
test module, and updated a conflicting OID. As far as I can tell, I'm
only missing the documentation updates before this is push-able.

Here is a complete version. Barring serious problems, I intend to
commit this on Monday.

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

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

#49Fabrízio de Royes Mello
fabriziomello@gmail.com
In reply to: Alvaro Herrera (#48)
Re: deparsing utility commands

On Fri, May 8, 2015 at 3:14 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Alvaro Herrera wrote:

Here's a cleaned up version of this patch; I threw together a very quick
test module, and updated a conflicting OID. As far as I can tell, I'm
only missing the documentation updates before this is push-able.

Here is a complete version. Barring serious problems, I intend to
commit this on Monday.

You forgot to attach the patches!

Regards,

--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL

Show quoted text

Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello

#50Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#48)
1 attachment(s)
Re: deparsing utility commands

Alvaro Herrera wrote:

Alvaro Herrera wrote:

Here's a cleaned up version of this patch; I threw together a very quick
test module, and updated a conflicting OID. As far as I can tell, I'm
only missing the documentation updates before this is push-able.

Here is a complete version. Barring serious problems, I intend to
commit this on Monday.

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

Attachments:

ddl-deparse-3.patchtext/x-diff; charset=us-asciiDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fb39731..04762e8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18270,6 +18270,97 @@ CREATE EVENT TRIGGER test_table_rewrite_oid
 </programlisting>
     </para>
   </sect2>
+
+  <sect2 id="pg-event-trigger-ddl-command-end-functions">
+   <title>Capturing Changes at Command End</title>
+
+   <indexterm>
+    <primary>pg_event_trigger_ddl_commands</primary>
+   </indexterm>
+
+   <para>
+    <function>pg_event_trigger_ddl_commands</> returns a list of
+    <acronym>DDL</acronym> (data definition language) commands executed by
+    each user action, when invoked in a function attached to a
+    <literal>ddl_command_end</> event trigger.  If called in any other
+    context, an error is raised.
+    <function>pg_event_trigger_ddl_commands</> returns one row for each
+    base command executed; some commands that are a single SQL sentence
+    may return more than one row.  This function returns the following
+    columns:
+
+    <informaltable>
+     <tgroup cols="3">
+      <thead>
+       <row>
+        <entry>Name</entry>
+        <entry>Type</entry>
+        <entry>Description</entry>
+       </row>
+      </thead>
+
+      <tbody>
+       <row>
+        <entry><literal>classid</literal></entry>
+        <entry><type>Oid</type></entry>
+        <entry>OID of catalog the object belongs in</entry>
+       </row>
+       <row>
+        <entry><literal>objid</literal></entry>
+        <entry><type>Oid</type></entry>
+        <entry>OID of the object in the catalog</entry>
+       </row>
+       <row>
+        <entry><literal>objsubid</literal></entry>
+        <entry><type>integer</type></entry>
+        <entry>Object sub-id (e.g. attribute number for columns)</entry>
+       </row>
+       <row>
+        <entry><literal>command_tag</literal></entry>
+        <entry><type>text</type></entry>
+        <entry>command tag</entry>
+       </row>
+       <row>
+        <entry><literal>object_type</literal></entry>
+        <entry><type>text</type></entry>
+        <entry>Type of the object</entry>
+       </row>
+       <row>
+        <entry><literal>schema_name</literal></entry>
+        <entry><type>text</type></entry>
+        <entry>
+         Name of the schema the object belongs in, if any; otherwise <literal>NULL</>.
+         No quoting is applied.
+        </entry>
+       </row>
+       <row>
+        <entry><literal>object_identity</literal></entry>
+        <entry><type>text</type></entry>
+        <entry>
+         Text rendering of the object identity, schema-qualified. Each and every
+         identifier present in the identity is quoted if necessary.
+        </entry>
+       </row>
+       <row>
+        <entry><literal>in_extension</literal></entry>
+        <entry><type>bool</type></entry>
+        <entry>whether the command is part of an extension script</entry>
+       </row>
+       <row>
+        <entry><literal>command</literal></entry>
+        <entry><type>pg_ddl_command</type></entry>
+        <entry>
+         A complete representation of the command, in internal format.
+         This cannot be output directly, but it can be passed to other
+         functions to obtain different pieces of information about the
+         command.
+        </entry>
+       </row>
+      </tbody>
+     </tgroup>
+    </informaltable>
+   </para>
+  </sect2>
   </sect1>
 
 </chapter>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 8e75c27..943909c 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "foreign/foreign.h"
@@ -56,6 +57,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
 #include "utils/acl.h"
+#include "utils/aclchk_internal.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
@@ -65,32 +67,6 @@
 
 
 /*
- * The information about one Grant/Revoke statement, in internal format: object
- * and grantees names have been turned into Oids, the privilege list is an
- * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
- * all_privs is true, 'privileges' will be internally set to the right kind of
- * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
- * InternalGrant struct!)
- *
- * Note: 'all_privs' and 'privileges' represent object-level privileges only.
- * There might also be column-level privilege specifications, which are
- * represented in col_privs (this is a list of untransformed AccessPriv nodes).
- * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
- */
-typedef struct
-{
-	bool		is_grant;
-	GrantObjectType objtype;
-	List	   *objects;
-	bool		all_privs;
-	AclMode		privileges;
-	List	   *col_privs;
-	List	   *grantees;
-	bool		grant_option;
-	DropBehavior behavior;
-} InternalGrant;
-
-/*
  * Internal format used by ALTER DEFAULT PRIVILEGES.
  */
 typedef struct
@@ -605,6 +581,15 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	if (EventTriggerSupportsGrantObjectType(istmt->objtype))
+		EventTriggerCollectGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 0110b06..daf8ddd 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -20,21 +20,28 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_ts_config.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/trigger.h"
 #include "funcapi.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -44,6 +51,9 @@
 
 typedef struct EventTriggerQueryState
 {
+	/* memory context for this state's objects */
+	MemoryContext cxt;
+
 	/* sql_drop */
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
@@ -52,7 +62,10 @@ typedef struct EventTriggerQueryState
 	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite event */
 	int			table_rewrite_reason;	/* AT_REWRITE reason */
 
-	MemoryContext cxt;
+	/* Support for command collection */
+	bool		commandCollectionInhibited;
+	CollectedCommand *currentCommand;
+	List	   *commandList;		/* list of CollectedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -71,6 +84,7 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
 	{"AGGREGATE", true},
 	{"CAST", true},
@@ -139,6 +153,8 @@ static Oid insert_event_trigger_tuple(char *trigname, char *eventname,
 static void validate_ddl_tags(const char *filtervar, List *taglist);
 static void validate_table_rewrite_tags(const char *filtervar, List *taglist);
 static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
+static const char *stringify_grantobjtype(GrantObjectType objtype);
+static const char *stringify_adefprivs_objtype(GrantObjectType objtype);
 
 /*
  * Create an event trigger.
@@ -1206,9 +1222,9 @@ EventTriggerBeginCompleteQuery(void)
 	MemoryContext cxt;
 
 	/*
-	 * Currently, sql_drop and table_rewrite events are the only reason to
-	 * have event trigger state at all; so if there are none, don't install
-	 * one.
+	 * Currently, sql_drop, table_rewrite, ddl_command_end events are the only
+	 * reason to have event trigger state at all; so if there are none, don't
+	 * install one.
 	 */
 	if (!trackDroppedObjectsNeeded())
 		return false;
@@ -1224,6 +1240,10 @@ EventTriggerBeginCompleteQuery(void)
 	state->in_sql_drop = false;
 	state->table_rewrite_oid = InvalidOid;
 
+	state->commandCollectionInhibited = currentEventTriggerState ?
+		currentEventTriggerState->commandCollectionInhibited : false;
+	state->currentCommand = NULL;
+	state->commandList = NIL;
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
 
@@ -1262,9 +1282,13 @@ EventTriggerEndCompleteQuery(void)
 bool
 trackDroppedObjectsNeeded(void)
 {
-	/* true if any sql_drop or table_rewrite event trigger exists */
+	/*
+	 * true if any sql_drop, table_rewrite, ddl_command_end event trigger
+	 * exists
+	 */
 	return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 ||
-		list_length(EventCacheLookup(EVT_TableRewrite)) > 0;
+		list_length(EventCacheLookup(EVT_TableRewrite)) > 0 ||
+		list_length(EventCacheLookup(EVT_DDLCommandEnd)) > 0;
 }
 
 /*
@@ -1566,3 +1590,675 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
 }
+
+/*-------------------------------------------------------------------------
+ * Support for DDL command deparsing
+ *
+ * The routines below enable an event trigger function to obtain a list of
+ * DDL commands as they are executed.  There are three main pieces to this
+ * feature:
+ *
+ * 1) Within ProcessUtilitySlow, or some sub-routine thereof, each DDL command
+ * adds a struct CollectedCommand representation of itself to the command list,
+ * using the routines below.
+ *
+ * 2) Some time after that, ddl_command_end fires and the command list is made
+ * available to the event trigger function via pg_event_trigger_ddl_commands();
+ * the complete command details are exposed as a column of type pg_ddl_command.
+ *
+ * 3) An extension can install a function capable of taking a value of type
+ * pg_ddl_command and transform it into some external, user-visible and/or
+ * -modifiable representation.
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Inhibit DDL command collection.
+ */
+void
+EventTriggerInhibitCommandCollection(void)
+{
+	if (!currentEventTriggerState)
+		return;
+
+	currentEventTriggerState->commandCollectionInhibited = true;
+}
+
+/*
+ * Re-establish DDL command collection.
+ */
+void
+EventTriggerUndoInhibitCommandCollection(void)
+{
+	if (!currentEventTriggerState)
+		return;
+
+	currentEventTriggerState->commandCollectionInhibited = false;
+}
+
+/*
+ * EventTriggerCollectSimpleCommand
+ *		Save data about a simple DDL command that was just executed
+ *
+ * address identifies the object being operated on.  secondaryObject is an
+ * object address that was related in some way to the executed command; its
+ * meaning is command-specific.
+ *
+ * For instance, for an ALTER obj SET SCHEMA command, objtype is the type of
+ * object being moved, objectId is its OID, and secondaryOid is the OID of the
+ * old schema.  (The destination schema OID can be obtained by catalog lookup
+ * of the object.)
+ */
+void
+EventTriggerCollectSimpleCommand(ObjectAddress address,
+								 ObjectAddress secondaryObject,
+								 Node *parsetree)
+{
+	MemoryContext oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+
+	command->type = SCT_Simple;
+	command->in_extension = creating_extension;
+
+	command->d.simple.address = address;
+	command->d.simple.secondaryObject = secondaryObject;
+	command->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->commandList = lappend(currentEventTriggerState->commandList,
+											  command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableStart
+ *		Prepare to receive data on an ALTER TABLE command about to be executed
+ *
+ * Note we don't collect the command immediately; instead we keep it in
+ * currentCommand, and only when we're done processing the subcommands we will
+ * add it to the command list.
+ *
+ * XXX -- this API isn't considering the possibility of an ALTER TABLE command
+ * being called reentrantly by an event trigger function.  Do we need stackable
+ * commands at this level?  Perhaps at least we should detect the condition and
+ * raise an error.
+ */
+void
+EventTriggerAlterTableStart(Node *parsetree)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+
+	command->type = SCT_AlterTable;
+	command->in_extension = creating_extension;
+
+	command->d.alterTable.classId = RelationRelationId;
+	command->d.alterTable.objectId = InvalidOid;
+	command->d.alterTable.subcmds = NIL;
+	command->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->currentCommand = command;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remember the OID of the object being affected by an ALTER TABLE.
+ *
+ * This is needed because in some cases we don't know the OID until later.
+ */
+void
+EventTriggerAlterTableRelid(Oid objectId)
+{
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	currentEventTriggerState->currentCommand->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerCollectAlterTableSubcmd
+ *		Save data about a single part of an ALTER TABLE.
+ *
+ * Several different commands go through this path, but apart from ALTER TABLE
+ * itself, they are all concerned with AlterTableCmd nodes that are generated
+ * internally, so that's all that this code needs to handle at the moment.
+ */
+void
+EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
+{
+	MemoryContext	oldcxt;
+	CollectedATSubcmd *newsub;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId));
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(CollectedATSubcmd));
+	newsub->address = address;
+	newsub->parsetree = copyObject(subcmd);
+
+	currentEventTriggerState->currentCommand->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableEnd
+ *		Finish up saving an ALTER TABLE command, and add it to command list.
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerAlterTableEnd(void)
+{
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	/* If no subcommands, don't collect */
+	if (list_length(currentEventTriggerState->currentCommand->d.alterTable.subcmds) != 0)
+	{
+		currentEventTriggerState->commandList =
+			lappend(currentEventTriggerState->commandList,
+					currentEventTriggerState->currentCommand);
+	}
+	else
+		pfree(currentEventTriggerState->currentCommand);
+
+	currentEventTriggerState->currentCommand = NULL;
+}
+
+/*
+ * EventTriggerCollectGrant
+ *		Save data about a GRANT/REVOKE command being executed
+ *
+ * This function creates a copy of the InternalGrant, as the original might
+ * not have the right lifetime.
+ */
+void
+EventTriggerCollectGrant(InternalGrant *istmt)
+{
+	MemoryContext oldcxt;
+	CollectedCommand *command;
+	InternalGrant  *icopy;
+	ListCell	   *cell;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	/*
+	 * This is tedious, but necessary.
+	 */
+	icopy = palloc(sizeof(InternalGrant));
+	memcpy(icopy, istmt, sizeof(InternalGrant));
+	icopy->objects = list_copy(istmt->objects);
+	icopy->grantees = list_copy(istmt->grantees);
+	icopy->col_privs = NIL;
+	foreach(cell, istmt->col_privs)
+		icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell)));
+
+	/* Now collect it, using the copied InternalGrant */
+	command = palloc(sizeof(CollectedCommand));
+	command->type = SCT_Grant;
+	command->in_extension = creating_extension;
+	command->d.grant.istmt = icopy;
+	command->parsetree = NULL;
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterOpFam
+ *		Save data about an ALTER OPERATOR FAMILY ADD/DROP command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
+							List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+	command->type = SCT_AlterOpFamily;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.opfam.address,
+					 OperatorFamilyRelationId, opfamoid);
+	command->d.opfam.operators = operators;
+	command->d.opfam.procedures = procedures;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectCreateOpClass
+ *		Save data about a CREATE OPERATOR CLASS command being executed
+ */
+void
+EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
+							   List *operators, List *procedures)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_CreateOpClass;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.createopc.address,
+					 OperatorClassRelationId, opcoid);
+	command->d.createopc.operators = operators;
+	command->d.createopc.procedures = procedures;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterTSConfig
+ *		Save data about an ALTER TEXT SEARCH CONFIGURATION command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
+								 Oid *dictIds, int ndicts)
+{
+	MemoryContext   oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_AlterTSConfig;
+	command->in_extension = creating_extension;
+	ObjectAddressSet(command->d.atscfg.address,
+					 TSConfigRelationId, cfgId);
+	command->d.atscfg.dictIds = palloc(sizeof(Oid) * ndicts);
+	memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
+	command->d.atscfg.ndicts = ndicts;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterDefPrivs
+ *		Save data about an ALTER DEFAULT PRIVILEGES command being
+ *		executed
+ */
+void
+EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt)
+{
+	MemoryContext	oldcxt;
+	CollectedCommand *command;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc0(sizeof(CollectedCommand));
+	command->type = SCT_AlterDefaultPrivileges;
+	command->d.defprivs.objtype = stmt->action->objtype;
+	command->in_extension = creating_extension;
+	command->parsetree = copyObject(stmt);
+
+	currentEventTriggerState->commandList =
+		lappend(currentEventTriggerState->commandList, command);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * In a ddl_command_end event trigger, this function reports the DDL commands
+ * being run.
+ */
+Datum
+pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_ddl_commands()")));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->commandList)
+	{
+		CollectedCommand *cmd = lfirst(lc);
+		Datum		values[9];
+		bool		nulls[9];
+		ObjectAddress addr;
+		int			i = 0;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid.  Don't return anything.
+		 *
+		 * One might think that a viable alternative would be to look up the
+		 * Oid of the existing object and run the deparse with that.  But since
+		 * the parse tree might be different from the one that created the
+		 * object in the first place, we might not end up in a consistent state
+		 * anyway.
+		 */
+		if (cmd->type == SCT_Simple &&
+			!OidIsValid(cmd->d.simple.address.objectId))
+			continue;
+
+		MemSet(nulls, 0, sizeof(nulls));
+
+		switch (cmd->type)
+		{
+			case SCT_Simple:
+			case SCT_AlterTable:
+			case SCT_AlterOpFamily:
+			case SCT_CreateOpClass:
+			case SCT_AlterTSConfig:
+				{
+					char	   *identity;
+					char	   *type;
+					char	   *schema = NULL;
+
+					if (cmd->type == SCT_Simple)
+						addr = cmd->d.simple.address;
+					else if (cmd->type == SCT_AlterTable)
+						ObjectAddressSet(addr,
+										 cmd->d.alterTable.classId,
+										 cmd->d.alterTable.objectId);
+					else if (cmd->type == SCT_AlterOpFamily)
+						addr = cmd->d.opfam.address;
+					else if (cmd->type == SCT_CreateOpClass)
+						addr = cmd->d.createopc.address;
+					else if (cmd->type == SCT_AlterTSConfig)
+						addr = cmd->d.atscfg.address;
+
+					type = getObjectTypeDescription(&addr);
+					identity = getObjectIdentity(&addr);
+
+					/*
+					 * Obtain schema name, if any ("pg_temp" if a temp object).
+					 * If the object class is not in the supported list here,
+					 * we assume it's a schema-less object type, and thus
+					 * "schema" remains set to NULL.
+					 */
+					if (is_objectclass_supported(addr.classId))
+					{
+						AttrNumber	nspAttnum;
+
+						nspAttnum = get_object_attnum_namespace(addr.classId);
+						if (nspAttnum != InvalidAttrNumber)
+						{
+							Relation	catalog;
+							HeapTuple	objtup;
+							Oid			schema_oid;
+							bool		isnull;
+
+							catalog = heap_open(addr.classId, AccessShareLock);
+							objtup = get_catalog_object_by_oid(catalog,
+															   addr.objectId);
+							if (!HeapTupleIsValid(objtup))
+								elog(ERROR, "cache lookup failed for object %u/%u",
+									 addr.classId, addr.objectId);
+							schema_oid =
+								heap_getattr(objtup, nspAttnum,
+											 RelationGetDescr(catalog), &isnull);
+							if (isnull)
+								elog(ERROR,
+									 "invalid null namespace in object %u/%u/%d",
+									 addr.classId, addr.objectId, addr.objectSubId);
+							/* XXX not quite get_namespace_name_or_temp */
+							if (isAnyTempNamespace(schema_oid))
+								schema = pstrdup("pg_temp");
+							else
+								schema = get_namespace_name(schema_oid);
+
+							heap_close(catalog, AccessShareLock);
+						}
+					}
+
+					/* classid */
+					values[i++] = ObjectIdGetDatum(addr.classId);
+					/* objid */
+					values[i++] = ObjectIdGetDatum(addr.objectId);
+					/* objsubid */
+					values[i++] = Int32GetDatum(addr.objectSubId);
+					/* command tag */
+					values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree));
+					/* object_type */
+					values[i++] = CStringGetTextDatum(type);
+					/* schema */
+					if (schema == NULL)
+						nulls[i++] = true;
+					else
+						values[i++] = CStringGetTextDatum(schema);
+					/* identity */
+					values[i++] = CStringGetTextDatum(identity);
+					/* in_extension */
+					values[i++] = BoolGetDatum(cmd->in_extension);
+					/* command */
+					values[i++] = PointerGetDatum(cmd);
+				}
+				break;
+
+			case SCT_AlterDefaultPrivileges:
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree));
+				/* object_type */
+				values[i++] = CStringGetTextDatum(stringify_adefprivs_objtype(
+																			  cmd->d.defprivs.objtype));
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = PointerGetDatum(cmd);
+				break;
+
+			case SCT_Grant:
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.istmt->is_grant ?
+												  "GRANT" : "REVOKE");
+				/* object_type */
+				values[i++] = CStringGetTextDatum(stringify_grantobjtype(
+																		 cmd->d.grant.istmt->objtype));
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = PointerGetDatum(cmd);
+				break;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * Return the GrantObjectType as a string, as it would appear in GRANT and
+ * REVOKE commands.
+ */
+static const char *
+stringify_grantobjtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+			return "COLUMN";
+		case ACL_OBJECT_RELATION:
+			return "TABLE";
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case ACL_OBJECT_DATABASE:
+			return "DATABASE";
+		case ACL_OBJECT_DOMAIN:
+			return "DOMAIN";
+		case ACL_OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case ACL_OBJECT_FOREIGN_SERVER:
+			return "FOREIGN SERVER";
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTION";
+		case ACL_OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case ACL_OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case ACL_OBJECT_NAMESPACE:
+			return "SCHEMA";
+		case ACL_OBJECT_TABLESPACE:
+			return "TABLESPACE";
+		case ACL_OBJECT_TYPE:
+			return "TYPE";
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
+
+/*
+ * Return the GrantObjectType as a string; as above, but use the spelling
+ * in ALTER DEFAULT PRIVILEGES commands instead.
+ */
+static const char *
+stringify_adefprivs_objtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_RELATION:
+			return "TABLES";
+			break;
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTIONS";
+			break;
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCES";
+			break;
+		case ACL_OBJECT_TYPE:
+			return "TYPES";
+			break;
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index c327cc0..3375f10 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -25,6 +25,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
+#include "catalog/opfam_internal.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_namespace.h"
@@ -35,6 +36,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
@@ -47,24 +49,12 @@
 #include "utils/tqual.h"
 
 
-/*
- * We use lists of this struct type to keep track of both operators and
- * procedures while building or adding to an opfamily.
- */
-typedef struct
-{
-	Oid			object;			/* operator or support proc's OID */
-	int			number;			/* strategy or support proc number */
-	Oid			lefttype;		/* lefttype */
-	Oid			righttype;		/* righttype */
-	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
-} OpFamilyMember;
-
-
-static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+				 Oid amoid, Oid opfamilyoid,
 				 int maxOpNumber, int maxProcNumber,
 				 List *items);
-static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+				  Oid amoid, Oid opfamilyoid,
 				  int maxOpNumber, int maxProcNumber,
 				  List *items);
 static void processTypesSpec(List *args, Oid *lefttype, Oid *righttype);
@@ -675,6 +665,9 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	storeProcedures(stmt->opfamilyname, amoid, opfamilyoid,
 					opclassoid, procedures, false);
 
+	/* let event triggers know what happened */
+	EventTriggerCollectCreateOpClass(stmt, opclassoid, operators, procedures);
+
 	/*
 	 * Create dependencies for the opclass proper.  Note: we do not create a
 	 * dependency link to the AM, because we don't currently support DROP
@@ -822,13 +815,11 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
 	 * ADD and DROP cases need separate code from here on down.
 	 */
 	if (stmt->isDrop)
-		AlterOpFamilyDrop(stmt->opfamilyname, amoid, opfamilyoid,
-						  maxOpNumber, maxProcNumber,
-						  stmt->items);
+		AlterOpFamilyDrop(stmt, amoid, opfamilyoid,
+						  maxOpNumber, maxProcNumber, stmt->items);
 	else
-		AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid,
-						 maxOpNumber, maxProcNumber,
-						 stmt->items);
+		AlterOpFamilyAdd(stmt, amoid, opfamilyoid,
+						 maxOpNumber, maxProcNumber, stmt->items);
 
 	return opfamilyoid;
 }
@@ -837,9 +828,8 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
  * ADD part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
-				 int maxOpNumber, int maxProcNumber,
-				 List *items)
+AlterOpFamilyAdd(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid,
+				 int maxOpNumber, int maxProcNumber, List *items)
 {
 	List	   *operators;		/* OpFamilyMember list for operators */
 	List	   *procedures;		/* OpFamilyMember list for support procs */
@@ -958,19 +948,22 @@ AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 	 * Add tuples to pg_amop and pg_amproc tying in the operators and
 	 * functions.  Dependencies on them are inserted, too.
 	 */
-	storeOperators(opfamilyname, amoid, opfamilyoid,
+	storeOperators(stmt->opfamilyname, amoid, opfamilyoid,
 				   InvalidOid, operators, true);
-	storeProcedures(opfamilyname, amoid, opfamilyoid,
+	storeProcedures(stmt->opfamilyname, amoid, opfamilyoid,
 					InvalidOid, procedures, true);
+
+	/* make information available to event triggers */
+	EventTriggerCollectAlterOpFam(stmt, opfamilyoid,
+								  operators, procedures);
 }
 
 /*
  * DROP part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
-				  int maxOpNumber, int maxProcNumber,
-				  List *items)
+AlterOpFamilyDrop(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid,
+				  int maxOpNumber, int maxProcNumber, List *items)
 {
 	List	   *operators;		/* OpFamilyMember list for operators */
 	List	   *procedures;		/* OpFamilyMember list for support procs */
@@ -1033,8 +1026,12 @@ AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 	/*
 	 * Remove tuples from pg_amop and pg_amproc.
 	 */
-	dropOperators(opfamilyname, amoid, opfamilyoid, operators);
-	dropProcedures(opfamilyname, amoid, opfamilyoid, procedures);
+	dropOperators(stmt->opfamilyname, amoid, opfamilyoid, operators);
+	dropProcedures(stmt->opfamilyname, amoid, opfamilyoid, procedures);
+
+	/* make information available to event triggers */
+	EventTriggerCollectAlterOpFam(stmt, opfamilyoid,
+								  operators, procedures);
 }
 
 
@@ -1673,7 +1670,7 @@ RemoveAmProcEntryById(Oid entryOid)
 	heap_close(rel, RowExclusiveLock);
 }
 
-static char *
+char *
 get_am_name(Oid amOid)
 {
 	HeapTuple	tup;
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index c090ed2..5a7beff 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -25,6 +25,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/schemacmds.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
@@ -52,6 +53,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	Oid			saved_uid;
 	int			save_sec_context;
 	AclResult	aclresult;
+	ObjectAddress address;
 
 	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
 
@@ -143,6 +145,16 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	PushOverrideSearchPath(overridePath);
 
 	/*
+	 * Report the new schema to possibly interested event triggers.  Note we
+	 * must do this here and not in ProcessUtilitySlow because otherwise the
+	 * objects created below are reported before the schema, which would be
+	 * wrong.
+	 */
+	ObjectAddressSet(address, NamespaceRelationId, namespaceId);
+	EventTriggerCollectSimpleCommand(address, InvalidObjectAddress,
+									 (Node *) stmt);
+
+	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
 	 * reorganize them into a sequentially executable order with no forward
 	 * references.  Note that the result is still a list of raw parsetrees ---
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 299d8cc..0a6b069 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2789,6 +2789,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerAlterTableRelid(relid);
+
 	ATController(NULL, rel, cmds, recurse, lockmode);
 }
 
@@ -3672,8 +3674,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 	}
 
-	/* supress compiler warning until we have some use for the address */
-	(void) address;
+	/*
+	 * Report the subcommand to interested event triggers.
+	 */
+	EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
 
 	/*
 	 * Bump the command counter to ensure the next subcommand in the sequence
@@ -9728,7 +9732,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		cmds = lappend(cmds, cmd);
 
+		EventTriggerAlterTableStart((Node *) stmt);
+		/* OID is set by AlterTableInternal */
 		AlterTableInternal(lfirst_oid(l), cmds, false);
+		EventTriggerAlterTableEnd();
 	}
 
 	return new_tablespaceoid;
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 4c404e7..ff90040 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_func.h"
@@ -1442,6 +1443,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 			}
 		}
 	}
+
+	EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict);
 }
 
 /*
@@ -1509,6 +1512,8 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
 
 		i++;
 	}
+
+	EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
 }
 
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a3139d3..937a8c5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3972,6 +3972,7 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 {
 	AlterTSConfigurationStmt *newnode = makeNode(AlterTSConfigurationStmt);
 
+	COPY_SCALAR_FIELD(kind);
 	COPY_NODE_FIELD(cfgname);
 	COPY_NODE_FIELD(tokentype);
 	COPY_NODE_FIELD(dicts);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7c86e91..e032142 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2032,6 +2032,7 @@ static bool
 _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 							   const AlterTSConfigurationStmt *b)
 {
+	COMPARE_SCALAR_FIELD(kind);
 	COMPARE_NODE_FIELD(cfgname);
 	COMPARE_NODE_FIELD(tokentype);
 	COMPARE_NODE_FIELD(dicts);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7a4c073..e71d926 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8998,6 +8998,7 @@ AlterTSConfigurationStmt:
 			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_ADD_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = $11;
@@ -9008,6 +9009,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = $11;
@@ -9018,6 +9020,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name any_with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_REPLACE_DICT;
 					n->cfgname = $5;
 					n->tokentype = NIL;
 					n->dicts = list_make2($9,$11);
@@ -9028,6 +9031,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name any_with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->dicts = list_make2($11,$13);
@@ -9038,6 +9042,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING FOR name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_DROP_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $9;
 					n->missing_ok = false;
@@ -9046,6 +9051,7 @@ AlterTSConfigurationStmt:
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING IF_P EXISTS FOR name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+					n->kind = ALTER_TSCONFIG_DROP_MAPPING;
 					n->cfgname = $5;
 					n->tokentype = $11;
 					n->missing_ok = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 59f09dc..78bfd34 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -912,7 +912,9 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	bool		commandCollected = false;
 	ObjectAddress address;
+	ObjectAddress secondaryObject = InvalidObjectAddress;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -931,6 +933,11 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * EventTriggerCollectSimpleCommand called by
+				 * CreateSchemaCommand
+				 */
+				commandCollected = true;
 				break;
 
 			case T_CreateStmt:
@@ -957,6 +964,9 @@ ProcessUtilitySlow(Node *parsetree,
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
 													 InvalidOid, NULL);
+							EventTriggerCollectSimpleCommand(address,
+															 secondaryObject,
+															 stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -989,10 +999,17 @@ ProcessUtilitySlow(Node *parsetree,
 													 InvalidOid, NULL);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
+							EventTriggerCollectSimpleCommand(address,
+															 secondaryObject,
+															 stmt);
 						}
 						else
 						{
-							/* Recurse for anything else */
+							/*
+							 * Recurse for anything else.  Note the recursive
+							 * call will stash the objects so created into our
+							 * event trigger context.
+							 */
 							ProcessUtility(stmt,
 										   queryString,
 										   PROCESS_UTILITY_SUBCOMMAND,
@@ -1005,6 +1022,12 @@ ProcessUtilitySlow(Node *parsetree,
 						if (lnext(l) != NULL)
 							CommandCounterIncrement();
 					}
+
+					/*
+					 * The multiple commands generated here are stashed
+					 * individually, so disable collection below.
+					 */
+					commandCollected = true;
 				}
 				break;
 
@@ -1031,6 +1054,10 @@ ProcessUtilitySlow(Node *parsetree,
 						stmts = transformAlterTableStmt(relid, atstmt,
 														queryString);
 
+						/* ... ensure we have an event trigger context ... */
+						EventTriggerAlterTableStart(parsetree);
+						EventTriggerAlterTableRelid(relid);
+
 						/* ... and do it */
 						foreach(l, stmts)
 						{
@@ -1044,25 +1071,41 @@ ProcessUtilitySlow(Node *parsetree,
 							}
 							else
 							{
-								/* Recurse for anything else */
+								/*
+								 * Recurse for anything else.  If we need to do
+								 * so, "close" the current complex-command set,
+								 * and start a new one at the bottom; this is
+								 * needed to ensure the ordering of queued
+								 * commands is consistent with the way they are
+								 * executed here.
+								 */
+								EventTriggerAlterTableEnd();
 								ProcessUtility(stmt,
 											   queryString,
 											   PROCESS_UTILITY_SUBCOMMAND,
 											   params,
 											   None_Receiver,
 											   NULL);
+								EventTriggerAlterTableStart(parsetree);
+								EventTriggerAlterTableRelid(relid);
 							}
 
 							/* Need CCI between commands */
 							if (lnext(l) != NULL)
 								CommandCounterIncrement();
 						}
+
+						/* done */
+						EventTriggerAlterTableEnd();
 					}
 					else
 						ereport(NOTICE,
 						  (errmsg("relation \"%s\" does not exist, skipping",
 								  atstmt->relation->relname)));
 				}
+
+				/* ALTER TABLE stashes commands internally */
+				commandCollected = true;
 				break;
 
 			case T_AlterDomainStmt:
@@ -1081,31 +1124,37 @@ ProcessUtilitySlow(Node *parsetree,
 							 * Recursively alter column default for table and,
 							 * if requested, for descendants
 							 */
-							AlterDomainDefault(stmt->typeName,
-											   stmt->def);
+							address =
+								AlterDomainDefault(stmt->typeName,
+												   stmt->def);
 							break;
 						case 'N':		/* ALTER DOMAIN DROP NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   false);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   false);
 							break;
 						case 'O':		/* ALTER DOMAIN SET NOT NULL */
-							AlterDomainNotNull(stmt->typeName,
-											   true);
+							address =
+								AlterDomainNotNull(stmt->typeName,
+												   true);
 							break;
 						case 'C':		/* ADD CONSTRAINT */
-							AlterDomainAddConstraint(stmt->typeName,
-													 stmt->def,
-													 NULL);
+							address =
+								AlterDomainAddConstraint(stmt->typeName,
+														 stmt->def,
+														 &secondaryObject);
 							break;
 						case 'X':		/* DROP CONSTRAINT */
-							AlterDomainDropConstraint(stmt->typeName,
-													  stmt->name,
-													  stmt->behavior,
-													  stmt->missing_ok);
+							address =
+								AlterDomainDropConstraint(stmt->typeName,
+														  stmt->name,
+														  stmt->behavior,
+														  stmt->missing_ok);
 							break;
 						case 'V':		/* VALIDATE CONSTRAINT */
-							AlterDomainValidateConstraint(stmt->typeName,
-														  stmt->name);
+							address =
+								AlterDomainValidateConstraint(stmt->typeName,
+															  stmt->name);
 							break;
 						default:		/* oops */
 							elog(ERROR, "unrecognized alter domain type: %d",
@@ -1125,41 +1174,46 @@ ProcessUtilitySlow(Node *parsetree,
 					switch (stmt->kind)
 					{
 						case OBJECT_AGGREGATE:
-							DefineAggregate(stmt->defnames, stmt->args,
-											stmt->oldstyle, stmt->definition,
-											queryString);
+							address =
+								DefineAggregate(stmt->defnames, stmt->args,
+												stmt->oldstyle,
+												stmt->definition, queryString);
 							break;
 						case OBJECT_OPERATOR:
 							Assert(stmt->args == NIL);
-							DefineOperator(stmt->defnames, stmt->definition);
+							address = DefineOperator(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TYPE:
 							Assert(stmt->args == NIL);
-							DefineType(stmt->defnames, stmt->definition);
+							address = DefineType(stmt->defnames,
+												  stmt->definition);
 							break;
 						case OBJECT_TSPARSER:
 							Assert(stmt->args == NIL);
-							DefineTSParser(stmt->defnames, stmt->definition);
+							address = DefineTSParser(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TSDICTIONARY:
 							Assert(stmt->args == NIL);
-							DefineTSDictionary(stmt->defnames,
-											   stmt->definition);
+							address = DefineTSDictionary(stmt->defnames,
+														  stmt->definition);
 							break;
 						case OBJECT_TSTEMPLATE:
 							Assert(stmt->args == NIL);
-							DefineTSTemplate(stmt->defnames,
-											 stmt->definition);
+							address = DefineTSTemplate(stmt->defnames,
+														stmt->definition);
 							break;
 						case OBJECT_TSCONFIGURATION:
 							Assert(stmt->args == NIL);
-							DefineTSConfiguration(stmt->defnames,
-												  stmt->definition,
-												  NULL);
+							address = DefineTSConfiguration(stmt->defnames,
+															 stmt->definition,
+															 &secondaryObject);
 							break;
 						case OBJECT_COLLATION:
 							Assert(stmt->args == NIL);
-							DefineCollation(stmt->defnames, stmt->definition);
+							address = DefineCollation(stmt->defnames,
+													   stmt->definition);
 							break;
 						default:
 							elog(ERROR, "unrecognized define stmt type: %d",
@@ -1200,143 +1254,184 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
-					DefineIndex(relid,	/* OID of heap relation */
-								stmt,
-								InvalidOid,		/* no predefined OID */
-								false,	/* is_alter_table */
-								true,	/* check_rights */
-								false,	/* skip_build */
-								false); /* quiet */
+					EventTriggerAlterTableStart(parsetree);
+					address =
+						DefineIndex(relid,	/* OID of heap relation */
+									stmt,
+									InvalidOid,		/* no predefined OID */
+									false,	/* is_alter_table */
+									true,	/* check_rights */
+									false,	/* skip_build */
+									false); /* quiet */
+					/*
+					 * Add the CREATE INDEX node itself to stash right away; if
+					 * there were any commands stashed in the ALTER TABLE code,
+					 * we need them to appear after this one.
+					 */
+					EventTriggerCollectSimpleCommand(address, secondaryObject,
+													 parsetree);
+					commandCollected = true;
+					EventTriggerAlterTableEnd();
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				address = CreateExtension((CreateExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				address = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
-											   NULL);
+				address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+														  &secondaryObject);
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				address = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
 				break;
 
 			case T_AlterFdwStmt:
-				AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				address = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				address = CreateForeignServer((CreateForeignServerStmt *) parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
-				AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				address = AlterForeignServer((AlterForeignServerStmt *) parsetree);
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				address = CreateUserMapping((CreateUserMappingStmt *) parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
-				AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				address = AlterUserMapping((AlterUserMappingStmt *) parsetree);
 				break;
 
 			case T_DropUserMappingStmt:
 				RemoveUserMapping((DropUserMappingStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_ImportForeignSchemaStmt:
 				ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+				/* commands are stashed inside ImportForeignSchema */
+				commandCollected = true;
 				break;
 
 			case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					address = DefineCompositeType(stmt->typevar,
+												  stmt->coldeflist);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				address = DefineEnum((CreateEnumStmt *) parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				address = DefineRange((CreateRangeStmt *) parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				address = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerAlterTableStart(parsetree);
+				address = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerCollectSimpleCommand(address, secondaryObject,
+												 parsetree);
+				/* stashed internally */
+				commandCollected = true;
+				EventTriggerAlterTableEnd();
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				address = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				address = AlterFunction((AlterFunctionStmt *) parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				address = DefineRule((RuleStmt *) parsetree, queryString);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				address = DefineSequence((CreateSeqStmt *) parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				address = AlterSequence((AlterSeqStmt *) parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
-								   queryString, params, completionTag);
+				/*
+				 * REFRSH CONCURRENTLY executes some DDL commands internally.
+				 * Inhibit DDL command collection here to avoid those commands
+				 * from showing up in the deparsed command queue.  The refresh
+				 * command itself is queued, which is enough.
+				 */
+				EventTriggerInhibitCommandCollection();
+				PG_TRY();
+				{
+					address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+												 queryString, params, completionTag);
+				}
+				PG_CATCH();
+				{
+					EventTriggerUndoInhibitCommandCollection();
+					PG_RE_THROW();
+				}
+				PG_END_TRY();
+				EventTriggerUndoInhibitCommandCollection();
 				break;
 
 			case T_CreateTrigStmt:
-				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
-									 InvalidOid, InvalidOid, InvalidOid,
-									 InvalidOid, false);
+				address = CreateTrigger((CreateTrigStmt *) parsetree,
+										 queryString, InvalidOid, InvalidOid,
+										 InvalidOid, InvalidOid, false);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				address = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				address = DefineDomain((CreateDomainStmt *) parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				address = CreateConversionCommand((CreateConversionStmt *) parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				address = CreateCast((CreateCastStmt *) parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
 				DefineOpClass((CreateOpClassStmt *) parsetree);
+				/* command is stashed in DefineOpClass */
+				commandCollected = true;
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				address = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
 				break;
 
 			case T_CreateTransformStmt:
@@ -1345,63 +1440,76 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_AlterOpFamilyStmt:
 				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				/* commands are stashed in AlterOpFamily */
+				commandCollected = true;
 				break;
 
 			case T_AlterTSDictionaryStmt:
-				AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				address = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
 				break;
 
 			case T_AlterTableMoveAllStmt:
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
+				/* commands are stashed in AlterTableMoveAll */
+				commandCollected = true;
 				break;
 
 			case T_DropStmt:
 				ExecDropStmt((DropStmt *) parsetree, isTopLevel);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree);
+				address = ExecRenameStmt((RenameStmt *) parsetree);
 				break;
 
 			case T_AlterObjectSchemaStmt:
-				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
-										  NULL);
+				address =
+					ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
+											  &secondaryObject);
 				break;
 
 			case T_AlterOwnerStmt:
-				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
 			case T_CommentStmt:
-				CommentObject((CommentStmt *) parsetree);
+				address = CommentObject((CommentStmt *) parsetree);
 				break;
 
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
+				/* commands are stashed in ExecGrantStmt_oids */
+				commandCollected = true;
 				break;
 
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
+				/* no commands stashed for DROP */
+				commandCollected = true;
 				break;
 
 			case T_AlterDefaultPrivilegesStmt:
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
+				EventTriggerCollectAlterDefPrivs((AlterDefaultPrivilegesStmt *) parsetree);
+				commandCollected = true;
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
-				CreatePolicy((CreatePolicyStmt *) parsetree);
+				address = CreatePolicy((CreatePolicyStmt *) parsetree);
 				break;
 
 			case T_AlterPolicyStmt:		/* ALTER POLICY */
-				AlterPolicy((AlterPolicyStmt *) parsetree);
+				address = AlterPolicy((AlterPolicyStmt *) parsetree);
 				break;
 
 			case T_SecLabelStmt:
-				ExecSecLabelStmt((SecLabelStmt *) parsetree);
+				address = ExecSecLabelStmt((SecLabelStmt *) parsetree);
 				break;
 
 			default:
@@ -1410,6 +1518,14 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 		}
 
+		/*
+		 * Remember the object so that ddl_command_end event triggers have
+		 * access to it.
+		 */
+		if (!commandCollected)
+			EventTriggerCollectSimpleCommand(address, secondaryObject,
+											 parsetree);
+
 		if (isCompleteQuery)
 		{
 			EventTriggerSQLDrop(parsetree);
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 2f0f0a1..9b674ce 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
 	return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
@@ -323,7 +326,6 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
-
 /*
  * Add typmod decoration to the basic type name
  */
@@ -338,7 +340,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,7 +352,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/include/catalog/opfam_internal.h b/src/include/catalog/opfam_internal.h
new file mode 100644
index 0000000..f01dcbe
--- /dev/null
+++ b/src/include/catalog/opfam_internal.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * opfam_internal.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/opfam_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OPFAM_INTERNAL_H
+#define OPFAM_INTERNAL_H
+
+/*
+ * We use lists of this struct type to keep track of both operators and
+ * procedures while building or adding to an opfamily.
+ */
+typedef struct
+{
+	Oid			object;			/* operator or support proc's OID */
+	int			number;			/* strategy or support proc number */
+	Oid			lefttype;		/* lefttype */
+	Oid			righttype;		/* righttype */
+	Oid			sortfamily;		/* ordering operator's sort opfamily, or 0 */
+} OpFamilyMember;
+
+#endif		/* OPFAM_INTERNAL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index bd67d72..9cb6b1e 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5138,6 +5138,8 @@ DATA(insert OID = 4566 (  pg_event_trigger_table_rewrite_oid	PGNSP PGUID 12 1 0
 DESCR("return Oid of the table getting rewritten");
 DATA(insert OID = 4567 (  pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ ));
 DESCR("return reason code for table getting rewritten");
+DATA(insert OID = 4568 (  pg_event_trigger_ddl_commands			PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,88}" "{o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension, command}" _null_ _null_ pg_event_trigger_ddl_commands _null_ _null_ _null_ ));
+DESCR("list DDL actions being executed by the current command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 0a900dd..b340f06 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -364,6 +364,11 @@ DATA(insert OID = 194 ( pg_node_tree	PGNSP PGUID -1 f b S f t \054 0 0 0 pg_node
 DESCR("string representing an internal node tree");
 #define PGNODETREEOID	194
 
+/* DATA(insert OID = 88 ( pg_ddl_command PGNSP PGUID -1 f b P f \054 0 0 0 pg_ddl_command_in pg_ddl_command_out pg_ddl_command_recv pg_ddl_command_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ )); */
+DATA(insert OID = 88 ( pg_ddl_command   PGNSP PGUID SIZEOF_POINTER t b P f t \054 0 0 0 - - - - - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("internal type for passing CollectedCommand");
+#define PGDDLCOMMANDOID 88
+
 /* OIDS 200 - 299 */
 
 DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 335f09c..c3a1748 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -90,6 +90,7 @@ extern void IsThereOpClassInNamespace(const char *opcname, Oid opcmethod,
 extern void IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod,
 						   Oid opfnamespace);
 extern Oid	get_am_oid(const char *amname, bool missing_ok);
+extern char *get_am_name(Oid amOid);
 extern Oid	get_opclass_oid(Oid amID, List *opclassname, bool missing_ok);
 extern Oid	get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok);
 
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 7eb2156..579e1ef 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -17,6 +17,8 @@
 #include "catalog/objectaddress.h"
 #include "catalog/pg_event_trigger.h"
 #include "nodes/parsenodes.h"
+#include "utils/aclchk_internal.h"
+#include "tcop/deparse_utility.h"
 
 typedef struct EventTriggerData
 {
@@ -60,4 +62,28 @@ extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
 							 bool original, bool normal);
 
+extern void EventTriggerInhibitCommandCollection(void);
+extern void EventTriggerUndoInhibitCommandCollection(void);
+
+extern void EventTriggerCollectSimpleCommand(ObjectAddress address,
+								 ObjectAddress secondaryObject,
+								 Node *parsetree);
+
+extern void EventTriggerAlterTableStart(Node *parsetree);
+extern void EventTriggerAlterTableRelid(Oid objectId);
+extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd,
+								  ObjectAddress address);
+extern void EventTriggerAlterTableEnd(void);
+
+extern void EventTriggerCollectGrant(InternalGrant *istmt);
+extern void EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt,
+							  Oid opfamoid, List *operators,
+							  List *procedures);
+extern void EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt,
+								 Oid opcoid, List *operators,
+								 List *procedures);
+extern void EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt,
+								 Oid cfgId, Oid *dictIds, int ndicts);
+extern void EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 40ecea2..0423350 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -24,7 +24,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 91ca9c6..556c1c5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2911,9 +2911,19 @@ typedef struct AlterTSDictionaryStmt
 /*
  * TS Configuration stmts: DefineStmt, RenameStmt and DropStmt are default
  */
+typedef enum AlterTSConfigType
+{
+	ALTER_TSCONFIG_ADD_MAPPING,
+	ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN,
+	ALTER_TSCONFIG_REPLACE_DICT,
+	ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN,
+	ALTER_TSCONFIG_DROP_MAPPING
+} AlterTSConfigType;
+
 typedef struct AlterTSConfigurationStmt
 {
 	NodeTag		type;
+	AlterTSConfigType	kind;	/* ALTER_TSCONFIG_ADD_MAPPING, etc */
 	List	   *cfgname;		/* qualified name (list of Value strings) */
 
 	/*
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..b6bcbeb
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,105 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+#include "access/attnum.h"
+#include "catalog/objectaddress.h"
+#include "nodes/nodes.h"
+#include "utils/aclchk_internal.h"
+
+
+/*
+ * Support for keeping track of collected commands.
+ */
+typedef enum CollectedCommandType
+{
+	SCT_Simple,
+	SCT_AlterTable,
+	SCT_Grant,
+	SCT_AlterOpFamily,
+	SCT_AlterDefaultPrivileges,
+	SCT_CreateOpClass,
+	SCT_AlterTSConfig
+} CollectedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct CollectedATSubcmd
+{
+	ObjectAddress	address; /* affected column, constraint, index, ... */
+	Node		   *parsetree;
+} CollectedATSubcmd;
+
+typedef struct CollectedCommand
+{
+	CollectedCommandType type;
+	bool		in_extension;
+	Node	   *parsetree;
+
+	union
+	{
+		/* most commands */
+		struct
+		{
+			ObjectAddress address;
+			ObjectAddress secondaryObject;
+		} simple;
+
+		/* ALTER TABLE, and internal uses thereof */
+		struct
+		{
+			Oid		objectId;
+			Oid		classId;
+			List   *subcmds;
+		} alterTable;
+
+		/* GRANT / REVOKE */
+		struct
+		{
+			InternalGrant *istmt;
+		} grant;
+
+		/* ALTER OPERATOR FAMILY */
+		struct
+		{
+			ObjectAddress address;
+			List   *operators;
+			List   *procedures;
+		} opfam;
+
+		/* CREATE OPERATOR CLASS */
+		struct
+		{
+			ObjectAddress address;
+			List   *operators;
+			List   *procedures;
+		} createopc;
+
+		/* ALTER TEXT SEARCH CONFIGURATION ADD/ALTER/DROP MAPPING */
+		struct
+		{
+			ObjectAddress address;
+			Oid	   *dictIds;
+			int		ndicts;
+		} atscfg;
+
+		/* ALTER DEFAULT PRIVILEGES */
+		struct
+		{
+			GrantObjectType objtype;
+		} defprivs;
+	} d;
+} CollectedCommand;
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/aclchk_internal.h b/src/include/utils/aclchk_internal.h
new file mode 100644
index 0000000..0855bf1
--- /dev/null
+++ b/src/include/utils/aclchk_internal.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * aclchk_internal.h
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/aclchk_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLCHK_INTERNAL_H
+#define ACLCHK_INTERNAL_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+/*
+ * The information about one Grant/Revoke statement, in internal format: object
+ * and grantees names have been turned into Oids, the privilege list is an
+ * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
+ * all_privs is true, 'privileges' will be internally set to the right kind of
+ * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
+ * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
+ */
+typedef struct
+{
+	bool		is_grant;
+	GrantObjectType objtype;
+	List	   *objects;
+	bool		all_privs;
+	AclMode		privileges;
+	List	   *col_privs;
+	List	   *grantees;
+	bool		grant_option;
+	DropBehavior behavior;
+} InternalGrant;
+
+
+#endif	/* ACLCHK_INTERNAL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index e8104f0..254f66c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1220,6 +1220,7 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 730fa75..8213e23 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -6,11 +6,12 @@ include $(top_builddir)/src/Makefile.global
 
 SUBDIRS = \
 		  commit_ts \
-		  worker_spi \
 		  dummy_seclabel \
+		  test_ddl_deparse \
+		  test_parser \
 		  test_rls_hooks \
 		  test_shm_mq \
-		  test_parser
+		  worker_spi
 
 all: submake-errcodes
 
diff --git a/src/test/modules/test_ddl_deparse/Makefile b/src/test/modules/test_ddl_deparse/Makefile
new file mode 100644
index 0000000..f11153c
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/Makefile
@@ -0,0 +1,18 @@
+MODULES = test_ddl_deparse
+PGFILEDESC = "test_ddl_deparse - regression testing for DDL deparsing"
+
+EXTENSION = test_ddl_deparse
+DATA = test_ddl_deparse--1.0.sql
+
+REGRESS = --schedule=$(srcdir)/regress_schedule
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_ddl_deparse
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_extension.out b/src/test/modules/test_ddl_deparse/expected/alter_extension.out
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_function.out b/src/test/modules/test_ddl_deparse/expected/alter_function.out
new file mode 100644
index 0000000..3694f96
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_function.out
@@ -0,0 +1,15 @@
+--
+-- ALTER_FUNCTION
+--
+ALTER FUNCTION plpgsql_function_trigger_1 ()
+   SET SCHEMA foo;
+NOTICE:  DDL test: type simple, tag ALTER FUNCTION
+ALTER FUNCTION foo.plpgsql_function_trigger_1()
+  COST 10;
+NOTICE:  DDL test: type simple, tag ALTER FUNCTION
+CREATE ROLE tmprole;
+ALTER FUNCTION plpgsql_function_trigger_2()
+  OWNER TO tmprole;
+ERROR:  function plpgsql_function_trigger_2() does not exist
+DROP OWNED BY tmprole;
+DROP ROLE tmprole;
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_sequence.out b/src/test/modules/test_ddl_deparse/expected/alter_sequence.out
new file mode 100644
index 0000000..319f36f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_sequence.out
@@ -0,0 +1,15 @@
+--
+-- ALTER_SEQUENCE
+--
+ALTER SEQUENCE fkey_table_seq
+  MINVALUE 10
+  START 20
+  CACHE 1
+  NO CYCLE;
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+ALTER SEQUENCE fkey_table_seq
+  RENAME TO fkey_table_seq_renamed;
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+ALTER SEQUENCE fkey_table_seq_renamed
+  SET SCHEMA foo;
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out b/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out
new file mode 100644
index 0000000..74107c2
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out
@@ -0,0 +1,7 @@
+---
+--- ALTER_TYPE_ENUM
+---
+ALTER TYPE enum_test ADD VALUE 'zzz' AFTER 'baz';
+NOTICE:  DDL test: type simple, tag ALTER TYPE
+ALTER TYPE enum_test ADD VALUE 'aaa' BEFORE 'foo';
+NOTICE:  DDL test: type simple, tag ALTER TYPE
diff --git a/src/test/modules/test_ddl_deparse/expected/comment_on.out b/src/test/modules/test_ddl_deparse/expected/comment_on.out
new file mode 100644
index 0000000..4ebc89d
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/comment_on.out
@@ -0,0 +1,25 @@
+--
+-- COMMENT_ON
+--
+COMMENT ON SCHEMA foo IS 'This is schema foo';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON TYPE enum_test IS 'ENUM test';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON TYPE int2range  IS 'RANGE test';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON DOMAIN japanese_postal_code IS 'DOMAIN test';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON SEQUENCE fkey_table_seq IS 'SEQUENCE test';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON TABLE datatype_table IS 'This table should contain all native datatypes';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON VIEW datatype_view IS 'This is a view';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON FUNCTION c_function_test() IS 'FUNCTION test';
+ERROR:  function c_function_test() does not exist
+COMMENT ON TRIGGER trigger_1 ON datatype_table IS 'TRIGGER test';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON RULE rule_1 IS 'RULE test';
+NOTICE:  DDL test: type simple, tag COMMENT
+-- should not fire
+COMMENT ON DATABASE contrib_regression IS 'contrib regression';
diff --git a/src/test/modules/test_ddl_deparse/expected/create_conversion.out b/src/test/modules/test_ddl_deparse/expected/create_conversion.out
new file mode 100644
index 0000000..e8697cf
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_conversion.out
@@ -0,0 +1,6 @@
+---
+--- CREATE_CONVERSION
+---
+-- Simple test should suffice for this
+CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
+NOTICE:  DDL test: type simple, tag CREATE CONVERSION
diff --git a/src/test/modules/test_ddl_deparse/expected/create_domain.out b/src/test/modules/test_ddl_deparse/expected/create_domain.out
new file mode 100644
index 0000000..2e7f585
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_domain.out
@@ -0,0 +1,11 @@
+---
+--- CREATE_DOMAIN
+---
+CREATE DOMAIN domainvarchar VARCHAR(5);
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+CREATE DOMAIN japanese_postal_code AS TEXT
+CHECK(
+   VALUE ~ '^\d{3}$'
+OR VALUE ~ '^\d{3}-\d{4}$'
+);
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
diff --git a/src/test/modules/test_ddl_deparse/expected/create_extension.out b/src/test/modules/test_ddl_deparse/expected/create_extension.out
new file mode 100644
index 0000000..4042e02
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_extension.out
@@ -0,0 +1,5 @@
+---
+--- CREATE_EXTENSION
+---
+CREATE EXTENSION pg_stat_statements;
+NOTICE:  DDL test: type simple, tag CREATE EXTENSION
diff --git a/src/test/modules/test_ddl_deparse/expected/create_function.out b/src/test/modules/test_ddl_deparse/expected/create_function.out
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/modules/test_ddl_deparse/expected/create_operator.out b/src/test/modules/test_ddl_deparse/expected/create_operator.out
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/modules/test_ddl_deparse/expected/create_rule.out b/src/test/modules/test_ddl_deparse/expected/create_rule.out
new file mode 100644
index 0000000..fe3d047
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_rule.out
@@ -0,0 +1,30 @@
+---
+--- CREATE_RULE
+---
+CREATE RULE rule_1 AS
+  ON INSERT
+  TO datatype_table
+  DO NOTHING;
+NOTICE:  DDL test: type simple, tag CREATE RULE
+CREATE RULE rule_2 AS
+  ON UPDATE
+  TO datatype_table
+  DO INSERT INTO unlogged_table (id) VALUES(NEW.id);
+NOTICE:  DDL test: type simple, tag CREATE RULE
+CREATE RULE rule_3 AS
+  ON DELETE
+  TO datatype_table
+  DO ALSO NOTHING;
+NOTICE:  DDL test: type simple, tag CREATE RULE
+CREATE RULE "_RETURN" AS
+  ON SELECT
+  TO like_datatype_table
+  DO INSTEAD
+    SELECT * FROM datatype_view;
+NOTICE:  DDL test: type simple, tag CREATE RULE
+CREATE RULE rule_3 AS
+  ON DELETE
+  TO like_datatype_table
+  WHERE id < 100
+  DO ALSO NOTHING;
+NOTICE:  DDL test: type simple, tag CREATE RULE
diff --git a/src/test/modules/test_ddl_deparse/expected/create_schema.out b/src/test/modules/test_ddl_deparse/expected/create_schema.out
new file mode 100644
index 0000000..8ab4eb0
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -0,0 +1,19 @@
+--
+-- CREATE_SCHEMA
+--
+CREATE SCHEMA foo;
+NOTICE:  DDL test: type simple, tag CREATE SCHEMA
+CREATE SCHEMA IF NOT EXISTS bar;
+NOTICE:  DDL test: type simple, tag CREATE SCHEMA
+CREATE SCHEMA baz;
+NOTICE:  DDL test: type simple, tag CREATE SCHEMA
+-- Will not be created, and will not be handled by the
+-- event trigger
+CREATE SCHEMA IF NOT EXISTS baz;
+NOTICE:  schema "baz" already exists, skipping
+CREATE SCHEMA element_test
+  CREATE TABLE foo (id int)
+  CREATE VIEW bar AS SELECT * FROM foo;
+NOTICE:  DDL test: type simple, tag CREATE SCHEMA
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE VIEW
diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
new file mode 100644
index 0000000..5837ea4
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
@@ -0,0 +1,11 @@
+--
+-- CREATE_SEQUENCE
+--
+CREATE SEQUENCE fkey_table_seq
+  INCREMENT BY 1
+  MINVALUE 0
+  MAXVALUE 1000000
+  START 10
+  CACHE 10
+  CYCLE;
+NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
diff --git a/src/test/modules/test_ddl_deparse/expected/create_table.out b/src/test/modules/test_ddl_deparse/expected/create_table.out
new file mode 100644
index 0000000..d27a775
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_table.out
@@ -0,0 +1,160 @@
+--
+-- CREATE_TABLE
+--
+-- Datatypes
+CREATE TABLE datatype_table (
+    id             SERIAL,
+    id_big         BIGSERIAL,
+    is_small       SMALLSERIAL,
+    v_bytea        BYTEA,
+    v_smallint     SMALLINT,
+    v_int          INT,
+    v_bigint       BIGINT,
+    v_char         CHAR(1),
+    v_varchar      VARCHAR(10),
+    v_text         TEXT,
+    v_bool         BOOLEAN,
+    v_inet         INET,
+    v_cidr         CIDR,
+    v_macaddr      MACADDR,
+    v_numeric      NUMERIC(1,0),
+    v_real         REAL,
+    v_float        FLOAT(1),
+    v_float8       FLOAT8,
+    v_money        MONEY,
+    v_tsquery      TSQUERY,
+    v_tsvector     TSVECTOR,
+    v_date         DATE,
+    v_time         TIME,
+    v_time_tz      TIME WITH TIME ZONE,
+    v_timestamp    TIMESTAMP,
+    v_timestamp_tz TIMESTAMP WITH TIME ZONE,
+    v_interval     INTERVAL,
+    v_bit          BIT,
+    v_bit4         BIT(4),
+    v_varbit       VARBIT,
+    v_varbit4      VARBIT(4),
+    v_box          BOX,
+    v_circle       CIRCLE,
+    v_lseg         LSEG,
+    v_path         PATH,
+    v_point        POINT,
+    v_polygon      POLYGON,
+    v_json         JSON,
+    v_xml          XML,
+    v_uuid         UUID,
+    v_txid_snapshot txid_snapshot,
+    v_enum         ENUM_TEST,
+    v_postal_code  japanese_postal_code,
+    v_int2range    int2range,
+    PRIMARY KEY (id),
+    UNIQUE (id_big)
+);
+NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+-- Constraint definitions
+CREATE TABLE IF NOT EXISTS fkey_table (
+    id           INT NOT NULL DEFAULT nextval('fkey_table_seq'::REGCLASS),
+    datatype_id  INT NOT NULL REFERENCES datatype_table(id),
+    big_id       BIGINT NOT NULL,
+    sometext     TEXT COLLATE "POSIX",
+    check_col_1  INT NOT NULL CHECK(check_col_1 < 10),
+    check_col_2  INT NOT NULL,
+    PRIMARY KEY  (id),
+    CONSTRAINT fkey_big_id
+      FOREIGN KEY (big_id)
+      REFERENCES datatype_table(id_big),
+    EXCLUDE USING btree (check_col_2 WITH =)
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type alter table, tag ALTER TABLE
+NOTICE:    subcommand: ADD CONSTRAINT (and recurse)
+NOTICE:    subcommand: ADD CONSTRAINT (and recurse)
+-- Typed table
+CREATE TABLE employees OF employee_type (
+    PRIMARY KEY (name),
+    salary WITH OPTIONS DEFAULT 1000
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+-- Inheritance
+CREATE TABLE person (
+    id          INT NOT NULL PRIMARY KEY,
+	name 		text,
+	age			int4,
+	location 	point
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+CREATE TABLE emp (
+	salary 		int4,
+	manager 	name
+) INHERITS (person) WITH OIDS;
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+CREATE TABLE student (
+	gpa 		float8
+) INHERITS (person);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+CREATE TABLE stud_emp (
+	percent 	int4
+) INHERITS (emp, student);
+NOTICE:  merging multiple inherited definitions of column "id"
+NOTICE:  merging multiple inherited definitions of column "name"
+NOTICE:  merging multiple inherited definitions of column "age"
+NOTICE:  merging multiple inherited definitions of column "location"
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+-- Storage parameters
+CREATE TABLE storage (
+    id INT
+) WITH (
+    fillfactor = 10,
+    autovacuum_enabled = FALSE
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+-- LIKE
+CREATE TABLE like_datatype_table (
+  LIKE datatype_table
+  EXCLUDING ALL
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+CREATE TABLE like_fkey_table (
+  LIKE fkey_table
+  INCLUDING DEFAULTS
+  INCLUDING INDEXES
+  INCLUDING STORAGE
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+-- Volatile table types
+CREATE UNLOGGED TABLE unlogged_table (
+    id INT PRIMARY KEY
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+CREATE TEMP TABLE temp_table (
+    id INT PRIMARY KEY
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+CREATE TEMP TABLE temp_table_commit_delete (
+    id INT PRIMARY KEY
+)
+ON COMMIT DELETE ROWS;
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+CREATE TEMP TABLE temp_table_commit_drop (
+    id INT PRIMARY KEY
+)
+ON COMMIT DROP;
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
diff --git a/src/test/modules/test_ddl_deparse/expected/create_trigger.out b/src/test/modules/test_ddl_deparse/expected/create_trigger.out
new file mode 100644
index 0000000..c89c847
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_trigger.out
@@ -0,0 +1,18 @@
+---
+--- CREATE_TRIGGER
+---
+CREATE FUNCTION plpgsql_function_trigger_1()
+  RETURNS TRIGGER
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+  RETURN NEW;
+END;
+$$;
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+CREATE TRIGGER trigger_1
+  BEFORE INSERT OR UPDATE
+  ON datatype_table
+  FOR EACH ROW
+  EXECUTE PROCEDURE plpgsql_function_trigger_1();
+NOTICE:  DDL test: type simple, tag CREATE TRIGGER
diff --git a/src/test/modules/test_ddl_deparse/expected/create_type.out b/src/test/modules/test_ddl_deparse/expected/create_type.out
new file mode 100644
index 0000000..dadbc8f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_type.out
@@ -0,0 +1,24 @@
+---
+--- CREATE_TYPE
+---
+CREATE FUNCTION text_w_default_in(cstring)
+   RETURNS text_w_default
+   AS 'textin'
+   LANGUAGE internal STABLE STRICT;
+NOTICE:  type "text_w_default" is not yet defined
+DETAIL:  Creating a shell type definition.
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+CREATE FUNCTION text_w_default_out(text_w_default)
+   RETURNS cstring
+   AS 'textout'
+   LANGUAGE internal STABLE STRICT ;
+NOTICE:  argument type text_w_default is only a shell
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+CREATE TYPE employee_type AS (name TEXT, salary NUMERIC);
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+CREATE TYPE enum_test AS ENUM ('foo', 'bar', 'baz');
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+CREATE TYPE int2range AS RANGE (
+  SUBTYPE = int2
+);
+NOTICE:  DDL test: type simple, tag CREATE TYPE
diff --git a/src/test/modules/test_ddl_deparse/expected/create_view.out b/src/test/modules/test_ddl_deparse/expected/create_view.out
new file mode 100644
index 0000000..2ae4e2d
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_view.out
@@ -0,0 +1,19 @@
+--
+-- CREATE_VIEW
+--
+CREATE VIEW static_view AS
+  SELECT 'foo'::TEXT AS col;
+NOTICE:  DDL test: type simple, tag CREATE VIEW
+CREATE OR REPLACE VIEW static_view AS
+  SELECT 'bar'::TEXT AS col;
+NOTICE:  DDL test: type simple, tag CREATE VIEW
+NOTICE:  DDL test: type alter table, tag CREATE VIEW
+NOTICE:    subcommand: REPLACE RELOPTIONS
+CREATE VIEW datatype_view AS
+  SELECT * FROM datatype_table;
+NOTICE:  DDL test: type simple, tag CREATE VIEW
+CREATE RECURSIVE VIEW nums_1_100 (n) AS
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM nums_1_100 WHERE n < 100;
+NOTICE:  DDL test: type simple, tag CREATE VIEW
diff --git a/src/test/modules/test_ddl_deparse/expected/defprivs.out b/src/test/modules/test_ddl_deparse/expected/defprivs.out
new file mode 100644
index 0000000..66b2680
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/defprivs.out
@@ -0,0 +1,6 @@
+--
+-- ALTER DEFAULT PRIVILEGES
+--
+ALTER DEFAULT PRIVILEGES IN SCHEMA public
+  REVOKE ALL PRIVILEGES ON TABLES FROM public;
+NOTICE:  DDL test: type alter default privileges, tag ALTER DEFAULT PRIVILEGES
diff --git a/src/test/modules/test_ddl_deparse/expected/matviews.out b/src/test/modules/test_ddl_deparse/expected/matviews.out
new file mode 100644
index 0000000..b946ff0
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/matviews.out
@@ -0,0 +1,8 @@
+--
+-- Materialized views
+--
+CREATE MATERIALIZED VIEW pg_class_mv AS
+  SELECT * FROM pg_class LIMIT 1 WITH NO DATA;
+NOTICE:  DDL test: type simple, tag CREATE MATERIALIZED VIEW
+REFRESH MATERIALIZED VIEW pg_class_mv;
+NOTICE:  DDL test: type simple, tag REFRESH MATERIALIZED VIEW
diff --git a/src/test/modules/test_ddl_deparse/expected/opfamily.out b/src/test/modules/test_ddl_deparse/expected/opfamily.out
new file mode 100644
index 0000000..14bd603
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/opfamily.out
@@ -0,0 +1,67 @@
+-- copied from equivclass.sql
+create type int8alias1;
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+create function int8alias1in(cstring) returns int8alias1
+  strict immutable language internal as 'int8in';
+NOTICE:  return type int8alias1 is only a shell
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create function int8alias1out(int8alias1) returns cstring
+  strict immutable language internal as 'int8out';
+NOTICE:  argument type int8alias1 is only a shell
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create type int8alias1 (
+    input = int8alias1in,
+    output = int8alias1out,
+    like = int8
+);
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+create type int8alias2;
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+create function int8alias2in(cstring) returns int8alias2
+  strict immutable language internal as 'int8in';
+NOTICE:  return type int8alias2 is only a shell
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create function int8alias2out(int8alias2) returns cstring
+  strict immutable language internal as 'int8out';
+NOTICE:  argument type int8alias2 is only a shell
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create type int8alias2 (
+    input = int8alias2in,
+    output = int8alias2out,
+    like = int8
+);
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+create cast (int8 as int8alias1) without function;
+NOTICE:  DDL test: type simple, tag CREATE CAST
+create cast (int8 as int8alias2) without function;
+NOTICE:  DDL test: type simple, tag CREATE CAST
+create cast (int8alias1 as int8) without function;
+NOTICE:  DDL test: type simple, tag CREATE CAST
+create cast (int8alias2 as int8) without function;
+NOTICE:  DDL test: type simple, tag CREATE CAST
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+  strict immutable language internal as 'int8eq';
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create operator = (
+    procedure = int8alias1eq,
+    leftarg = int8alias1, rightarg = int8alias1,
+    commutator = =,
+    restrict = eqsel, join = eqjoinsel,
+    merges
+);
+NOTICE:  DDL test: type simple, tag CREATE OPERATOR
+alter operator family integer_ops using btree add
+  operator 3 = (int8alias1, int8alias1);
+NOTICE:  DDL test: type alter operator family, tag ALTER OPERATOR FAMILY
+-- copied from alter_table.sql
+create type ctype as (f1 int, f2 text);
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+create function same(ctype, ctype) returns boolean language sql
+as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2';
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create operator =(procedure = same, leftarg  = ctype, rightarg = ctype);
+NOTICE:  DDL test: type simple, tag CREATE OPERATOR
+create operator class ctype_hash_ops
+  default for type ctype using hash as
+  operator 1 =(ctype, ctype);
+NOTICE:  DDL test: type create operator class, tag CREATE OPERATOR CLASS
diff --git a/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out
new file mode 100644
index 0000000..e2e49f9
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out
@@ -0,0 +1,40 @@
+CREATE EXTENSION test_ddl_deparse;
+CREATE OR REPLACE FUNCTION test_ddl_deparse()
+  RETURNS event_trigger LANGUAGE plpgsql AS
+$$
+DECLARE
+	r record;
+	r2 record;
+	cmdtype text;
+	objtype text;
+	tag text;
+BEGIN
+	FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+	LOOP
+		-- verify that tags match
+		tag = get_command_tag(r.command);
+		IF tag <> r.command_tag THEN
+			RAISE NOTICE 'tag % doesn''t match %', tag, r.command_tag;
+		END IF;
+
+		-- log the operation
+		cmdtype = get_command_type(r.command);
+		IF cmdtype <> 'grant' THEN
+			RAISE NOTICE 'DDL test: type %, tag %', cmdtype, tag;
+		ELSE
+			RAISE NOTICE 'DDL test: type %, object type %', cmdtype, r.object_type;
+		END IF;
+
+		-- if alter table, log more
+		IF cmdtype = 'alter table' THEN
+			FOR r2 IN SELECT *
+						FROM unnest(get_altertable_subcmdtypes(r.command))
+			LOOP
+				RAISE NOTICE '  subcommand: %', r2.unnest;
+			END LOOP;
+		END IF;
+	END LOOP;
+END;
+$$;
+CREATE EVENT TRIGGER test_ddl_deparse
+ON ddl_command_end EXECUTE PROCEDURE test_ddl_deparse();
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_function.sql b/src/test/modules/test_ddl_deparse/sql/alter_function.sql
new file mode 100644
index 0000000..8f13950
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_function.sql
@@ -0,0 +1,17 @@
+--
+-- ALTER_FUNCTION
+--
+
+ALTER FUNCTION plpgsql_function_trigger_1 ()
+   SET SCHEMA foo;
+
+ALTER FUNCTION foo.plpgsql_function_trigger_1()
+  COST 10;
+
+CREATE ROLE tmprole;
+
+ALTER FUNCTION plpgsql_function_trigger_2()
+  OWNER TO tmprole;
+
+DROP OWNED BY tmprole;
+DROP ROLE tmprole;
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql b/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql
new file mode 100644
index 0000000..3f9fe36
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql
@@ -0,0 +1,17 @@
+--
+-- ALTER_SEQUENCE
+--
+
+ALTER SEQUENCE fkey_table_seq
+  MINVALUE 10
+  START 20
+  CACHE 1
+  NO CYCLE;
+
+ALTER SEQUENCE fkey_table_seq
+  RENAME TO fkey_table_seq_renamed;
+
+ALTER SEQUENCE fkey_table_seq_renamed
+  SET SCHEMA foo;
+
+
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql b/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql
new file mode 100644
index 0000000..8999b38
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql
@@ -0,0 +1,6 @@
+---
+--- ALTER_TYPE_ENUM
+---
+
+ALTER TYPE enum_test ADD VALUE 'zzz' AFTER 'baz';
+ALTER TYPE enum_test ADD VALUE 'aaa' BEFORE 'foo';
diff --git a/src/test/modules/test_ddl_deparse/sql/comment_on.sql b/src/test/modules/test_ddl_deparse/sql/comment_on.sql
new file mode 100644
index 0000000..69114c3
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/comment_on.sql
@@ -0,0 +1,17 @@
+--
+-- COMMENT_ON
+--
+
+COMMENT ON SCHEMA foo IS 'This is schema foo';
+COMMENT ON TYPE enum_test IS 'ENUM test';
+COMMENT ON TYPE int2range  IS 'RANGE test';
+COMMENT ON DOMAIN japanese_postal_code IS 'DOMAIN test';
+COMMENT ON SEQUENCE fkey_table_seq IS 'SEQUENCE test';
+COMMENT ON TABLE datatype_table IS 'This table should contain all native datatypes';
+COMMENT ON VIEW datatype_view IS 'This is a view';
+COMMENT ON FUNCTION c_function_test() IS 'FUNCTION test';
+COMMENT ON TRIGGER trigger_1 ON datatype_table IS 'TRIGGER test';
+COMMENT ON RULE rule_1 IS 'RULE test';
+
+-- should not fire
+COMMENT ON DATABASE contrib_regression IS 'contrib regression';
diff --git a/src/test/modules/test_ddl_deparse/sql/create_conversion.sql b/src/test/modules/test_ddl_deparse/sql/create_conversion.sql
new file mode 100644
index 0000000..813c66d
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_conversion.sql
@@ -0,0 +1,6 @@
+---
+--- CREATE_CONVERSION
+---
+
+-- Simple test should suffice for this
+CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_domain.sql b/src/test/modules/test_ddl_deparse/sql/create_domain.sql
new file mode 100644
index 0000000..6ab5525
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_domain.sql
@@ -0,0 +1,10 @@
+---
+--- CREATE_DOMAIN
+---
+CREATE DOMAIN domainvarchar VARCHAR(5);
+
+CREATE DOMAIN japanese_postal_code AS TEXT
+CHECK(
+   VALUE ~ '^\d{3}$'
+OR VALUE ~ '^\d{3}-\d{4}$'
+);
diff --git a/src/test/modules/test_ddl_deparse/sql/create_extension.sql b/src/test/modules/test_ddl_deparse/sql/create_extension.sql
new file mode 100644
index 0000000..52437de
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_extension.sql
@@ -0,0 +1,6 @@
+---
+--- CREATE_EXTENSION
+---
+
+CREATE EXTENSION pg_stat_statements;
+
diff --git a/src/test/modules/test_ddl_deparse/sql/create_rule.sql b/src/test/modules/test_ddl_deparse/sql/create_rule.sql
new file mode 100644
index 0000000..60ac151
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_rule.sql
@@ -0,0 +1,31 @@
+---
+--- CREATE_RULE
+---
+
+
+CREATE RULE rule_1 AS
+  ON INSERT
+  TO datatype_table
+  DO NOTHING;
+
+CREATE RULE rule_2 AS
+  ON UPDATE
+  TO datatype_table
+  DO INSERT INTO unlogged_table (id) VALUES(NEW.id);
+
+CREATE RULE rule_3 AS
+  ON DELETE
+  TO datatype_table
+  DO ALSO NOTHING;
+
+CREATE RULE "_RETURN" AS
+  ON SELECT
+  TO like_datatype_table
+  DO INSTEAD
+    SELECT * FROM datatype_view;
+
+CREATE RULE rule_3 AS
+  ON DELETE
+  TO like_datatype_table
+  WHERE id < 100
+  DO ALSO NOTHING;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_schema.sql b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
new file mode 100644
index 0000000..f314dc2
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -0,0 +1,17 @@
+--
+-- CREATE_SCHEMA
+--
+
+CREATE SCHEMA foo;
+
+CREATE SCHEMA IF NOT EXISTS bar;
+
+CREATE SCHEMA baz;
+
+-- Will not be created, and will not be handled by the
+-- event trigger
+CREATE SCHEMA IF NOT EXISTS baz;
+
+CREATE SCHEMA element_test
+  CREATE TABLE foo (id int)
+  CREATE VIEW bar AS SELECT * FROM foo;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql b/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql
new file mode 100644
index 0000000..d289940
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql
@@ -0,0 +1,12 @@
+--
+-- CREATE_SEQUENCE
+--
+
+CREATE SEQUENCE fkey_table_seq
+  INCREMENT BY 1
+  MINVALUE 0
+  MAXVALUE 1000000
+  START 10
+  CACHE 10
+  CYCLE;
+
diff --git a/src/test/modules/test_ddl_deparse/sql/create_table.sql b/src/test/modules/test_ddl_deparse/sql/create_table.sql
new file mode 100644
index 0000000..5e78452
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_table.sql
@@ -0,0 +1,142 @@
+--
+-- CREATE_TABLE
+--
+
+-- Datatypes
+CREATE TABLE datatype_table (
+    id             SERIAL,
+    id_big         BIGSERIAL,
+    is_small       SMALLSERIAL,
+    v_bytea        BYTEA,
+    v_smallint     SMALLINT,
+    v_int          INT,
+    v_bigint       BIGINT,
+    v_char         CHAR(1),
+    v_varchar      VARCHAR(10),
+    v_text         TEXT,
+    v_bool         BOOLEAN,
+    v_inet         INET,
+    v_cidr         CIDR,
+    v_macaddr      MACADDR,
+    v_numeric      NUMERIC(1,0),
+    v_real         REAL,
+    v_float        FLOAT(1),
+    v_float8       FLOAT8,
+    v_money        MONEY,
+    v_tsquery      TSQUERY,
+    v_tsvector     TSVECTOR,
+    v_date         DATE,
+    v_time         TIME,
+    v_time_tz      TIME WITH TIME ZONE,
+    v_timestamp    TIMESTAMP,
+    v_timestamp_tz TIMESTAMP WITH TIME ZONE,
+    v_interval     INTERVAL,
+    v_bit          BIT,
+    v_bit4         BIT(4),
+    v_varbit       VARBIT,
+    v_varbit4      VARBIT(4),
+    v_box          BOX,
+    v_circle       CIRCLE,
+    v_lseg         LSEG,
+    v_path         PATH,
+    v_point        POINT,
+    v_polygon      POLYGON,
+    v_json         JSON,
+    v_xml          XML,
+    v_uuid         UUID,
+    v_txid_snapshot txid_snapshot,
+    v_enum         ENUM_TEST,
+    v_postal_code  japanese_postal_code,
+    v_int2range    int2range,
+    PRIMARY KEY (id),
+    UNIQUE (id_big)
+);
+
+-- Constraint definitions
+
+CREATE TABLE IF NOT EXISTS fkey_table (
+    id           INT NOT NULL DEFAULT nextval('fkey_table_seq'::REGCLASS),
+    datatype_id  INT NOT NULL REFERENCES datatype_table(id),
+    big_id       BIGINT NOT NULL,
+    sometext     TEXT COLLATE "POSIX",
+    check_col_1  INT NOT NULL CHECK(check_col_1 < 10),
+    check_col_2  INT NOT NULL,
+    PRIMARY KEY  (id),
+    CONSTRAINT fkey_big_id
+      FOREIGN KEY (big_id)
+      REFERENCES datatype_table(id_big),
+    EXCLUDE USING btree (check_col_2 WITH =)
+);
+
+-- Typed table
+
+CREATE TABLE employees OF employee_type (
+    PRIMARY KEY (name),
+    salary WITH OPTIONS DEFAULT 1000
+);
+
+-- Inheritance
+CREATE TABLE person (
+    id          INT NOT NULL PRIMARY KEY,
+	name 		text,
+	age			int4,
+	location 	point
+);
+
+CREATE TABLE emp (
+	salary 		int4,
+	manager 	name
+) INHERITS (person) WITH OIDS;
+
+
+CREATE TABLE student (
+	gpa 		float8
+) INHERITS (person);
+
+CREATE TABLE stud_emp (
+	percent 	int4
+) INHERITS (emp, student);
+
+
+-- Storage parameters
+
+CREATE TABLE storage (
+    id INT
+) WITH (
+    fillfactor = 10,
+    autovacuum_enabled = FALSE
+);
+
+-- LIKE
+
+CREATE TABLE like_datatype_table (
+  LIKE datatype_table
+  EXCLUDING ALL
+);
+
+CREATE TABLE like_fkey_table (
+  LIKE fkey_table
+  INCLUDING DEFAULTS
+  INCLUDING INDEXES
+  INCLUDING STORAGE
+);
+
+
+-- Volatile table types
+CREATE UNLOGGED TABLE unlogged_table (
+    id INT PRIMARY KEY
+);
+
+CREATE TEMP TABLE temp_table (
+    id INT PRIMARY KEY
+);
+
+CREATE TEMP TABLE temp_table_commit_delete (
+    id INT PRIMARY KEY
+)
+ON COMMIT DELETE ROWS;
+
+CREATE TEMP TABLE temp_table_commit_drop (
+    id INT PRIMARY KEY
+)
+ON COMMIT DROP;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_trigger.sql b/src/test/modules/test_ddl_deparse/sql/create_trigger.sql
new file mode 100644
index 0000000..fc0aef7
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_trigger.sql
@@ -0,0 +1,18 @@
+---
+--- CREATE_TRIGGER
+---
+
+CREATE FUNCTION plpgsql_function_trigger_1()
+  RETURNS TRIGGER
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+  RETURN NEW;
+END;
+$$;
+
+CREATE TRIGGER trigger_1
+  BEFORE INSERT OR UPDATE
+  ON datatype_table
+  FOR EACH ROW
+  EXECUTE PROCEDURE plpgsql_function_trigger_1();
diff --git a/src/test/modules/test_ddl_deparse/sql/create_type.sql b/src/test/modules/test_ddl_deparse/sql/create_type.sql
new file mode 100644
index 0000000..a387cfd
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_type.sql
@@ -0,0 +1,21 @@
+---
+--- CREATE_TYPE
+---
+
+CREATE FUNCTION text_w_default_in(cstring)
+   RETURNS text_w_default
+   AS 'textin'
+   LANGUAGE internal STABLE STRICT;
+
+CREATE FUNCTION text_w_default_out(text_w_default)
+   RETURNS cstring
+   AS 'textout'
+   LANGUAGE internal STABLE STRICT ;
+
+CREATE TYPE employee_type AS (name TEXT, salary NUMERIC);
+
+CREATE TYPE enum_test AS ENUM ('foo', 'bar', 'baz');
+
+CREATE TYPE int2range AS RANGE (
+  SUBTYPE = int2
+);
diff --git a/src/test/modules/test_ddl_deparse/sql/create_view.sql b/src/test/modules/test_ddl_deparse/sql/create_view.sql
new file mode 100644
index 0000000..030b76f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_view.sql
@@ -0,0 +1,17 @@
+--
+-- CREATE_VIEW
+--
+
+CREATE VIEW static_view AS
+  SELECT 'foo'::TEXT AS col;
+
+CREATE OR REPLACE VIEW static_view AS
+  SELECT 'bar'::TEXT AS col;
+
+CREATE VIEW datatype_view AS
+  SELECT * FROM datatype_table;
+
+CREATE RECURSIVE VIEW nums_1_100 (n) AS
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM nums_1_100 WHERE n < 100;
diff --git a/src/test/modules/test_ddl_deparse/sql/defprivs.sql b/src/test/modules/test_ddl_deparse/sql/defprivs.sql
new file mode 100644
index 0000000..a0fb4c2
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/defprivs.sql
@@ -0,0 +1,6 @@
+--
+-- ALTER DEFAULT PRIVILEGES
+--
+
+ALTER DEFAULT PRIVILEGES IN SCHEMA public
+  REVOKE ALL PRIVILEGES ON TABLES FROM public;
diff --git a/src/test/modules/test_ddl_deparse/sql/opfamily.sql b/src/test/modules/test_ddl_deparse/sql/opfamily.sql
new file mode 100644
index 0000000..988936b
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/opfamily.sql
@@ -0,0 +1,53 @@
+-- copied from equivclass.sql
+create type int8alias1;
+create function int8alias1in(cstring) returns int8alias1
+  strict immutable language internal as 'int8in';
+create function int8alias1out(int8alias1) returns cstring
+  strict immutable language internal as 'int8out';
+create type int8alias1 (
+    input = int8alias1in,
+    output = int8alias1out,
+    like = int8
+);
+
+create type int8alias2;
+create function int8alias2in(cstring) returns int8alias2
+  strict immutable language internal as 'int8in';
+create function int8alias2out(int8alias2) returns cstring
+  strict immutable language internal as 'int8out';
+create type int8alias2 (
+    input = int8alias2in,
+    output = int8alias2out,
+    like = int8
+);
+
+create cast (int8 as int8alias1) without function;
+create cast (int8 as int8alias2) without function;
+create cast (int8alias1 as int8) without function;
+create cast (int8alias2 as int8) without function;
+
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+  strict immutable language internal as 'int8eq';
+create operator = (
+    procedure = int8alias1eq,
+    leftarg = int8alias1, rightarg = int8alias1,
+    commutator = =,
+    restrict = eqsel, join = eqjoinsel,
+    merges
+);
+alter operator family integer_ops using btree add
+  operator 3 = (int8alias1, int8alias1);
+
+
+-- copied from alter_table.sql
+create type ctype as (f1 int, f2 text);
+
+create function same(ctype, ctype) returns boolean language sql
+as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2';
+
+create operator =(procedure = same, leftarg  = ctype, rightarg = ctype);
+
+create operator class ctype_hash_ops
+  default for type ctype using hash as
+  operator 1 =(ctype, ctype);
+
diff --git a/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql
new file mode 100644
index 0000000..4d08aaa
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql
@@ -0,0 +1,42 @@
+CREATE EXTENSION test_ddl_deparse;
+
+CREATE OR REPLACE FUNCTION test_ddl_deparse()
+  RETURNS event_trigger LANGUAGE plpgsql AS
+$$
+DECLARE
+	r record;
+	r2 record;
+	cmdtype text;
+	objtype text;
+	tag text;
+BEGIN
+	FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+	LOOP
+		-- verify that tags match
+		tag = get_command_tag(r.command);
+		IF tag <> r.command_tag THEN
+			RAISE NOTICE 'tag % doesn''t match %', tag, r.command_tag;
+		END IF;
+
+		-- log the operation
+		cmdtype = get_command_type(r.command);
+		IF cmdtype <> 'grant' THEN
+			RAISE NOTICE 'DDL test: type %, tag %', cmdtype, tag;
+		ELSE
+			RAISE NOTICE 'DDL test: type %, object type %', cmdtype, r.object_type;
+		END IF;
+
+		-- if alter table, log more
+		IF cmdtype = 'alter table' THEN
+			FOR r2 IN SELECT *
+						FROM unnest(get_altertable_subcmdtypes(r.command))
+			LOOP
+				RAISE NOTICE '  subcommand: %', r2.unnest;
+			END LOOP;
+		END IF;
+	END LOOP;
+END;
+$$;
+
+CREATE EVENT TRIGGER test_ddl_deparse
+ON ddl_command_end EXECUTE PROCEDURE test_ddl_deparse();
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql b/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql
new file mode 100644
index 0000000..093005a
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql
@@ -0,0 +1,16 @@
+/* src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ddl_deparse" to load this file. \quit
+
+CREATE FUNCTION get_command_type(pg_ddl_command)
+  RETURNS text IMMUTABLE STRICT
+  AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_command_tag(pg_ddl_command)
+  RETURNS text IMMUTABLE STRICT
+  AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_altertable_subcmdtypes(pg_ddl_command)
+  RETURNS text[] IMMUTABLE STRICT
+  AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
new file mode 100644
index 0000000..f9ba413
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -0,0 +1,267 @@
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(get_command_type);
+PG_FUNCTION_INFO_V1(get_command_tag);
+PG_FUNCTION_INFO_V1(get_altertable_subcmdtypes);
+
+Datum
+get_command_type(PG_FUNCTION_ARGS)
+{
+	CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+	const char *type;
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			type = "simple";
+			break;
+		case SCT_AlterTable:
+			type = "alter table";
+			break;
+		case SCT_Grant:
+			type = "grant";
+			break;
+		case SCT_AlterOpFamily:
+			type = "alter operator family";
+			break;
+		case SCT_AlterDefaultPrivileges:
+			type = "alter default privileges";
+			break;
+		case SCT_CreateOpClass:
+			type = "create operator class";
+			break;
+		case SCT_AlterTSConfig:
+			type = "alter text search configuration";
+			break;
+		default:
+			type = "unknown command type";
+			break;
+	}
+
+	PG_RETURN_TEXT_P(cstring_to_text(type));
+}
+
+Datum
+get_command_tag(PG_FUNCTION_ARGS)
+{
+	CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+
+	if (!cmd->parsetree)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text(CreateCommandTag(cmd->parsetree)));
+}
+
+Datum
+get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
+{
+	CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+	ArrayBuildState	*astate = NULL;
+	ListCell *cell;
+
+	if (cmd->type != SCT_AlterTable)
+		elog(ERROR, "command is not ALTER TABLE");
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		CollectedATSubcmd *sub = lfirst(cell);
+		AlterTableCmd  *subcmd = (AlterTableCmd *) sub->parsetree;
+		const char     *strtype;
+
+		Assert(IsA(subcmd, AlterTableCmd));
+
+		switch (subcmd->subtype)
+		{
+			case AT_AddColumn:
+				strtype = "ADD COLUMN";
+				break;
+			case AT_AddColumnRecurse:
+				strtype = "ADD COLUMN (and recurse)";
+				break;
+			case AT_AddColumnToView:
+				strtype = "ADD COLUMN TO VIEW";
+				break;
+			case AT_ColumnDefault:
+				strtype = "ALTER COLUMN SET DEFAULT";
+				break;
+			case AT_DropNotNull:
+				strtype = "DROP NOT NULL";
+				break;
+			case AT_SetNotNull:
+				strtype = "SET NOT NULL";
+				break;
+			case AT_SetStatistics:
+				strtype = "SET STATS";
+				break;
+			case AT_SetOptions:
+				strtype = "SET OPTIONS";
+				break;
+			case AT_ResetOptions:
+				strtype = "RESET OPTIONS";
+				break;
+			case AT_SetStorage:
+				strtype = "SET STORAGE";
+				break;
+			case AT_DropColumn:
+				strtype = "DROP COLUMN";
+				break;
+			case AT_DropColumnRecurse:
+				strtype = "DROP COLUMN (and recurse)";
+				break;
+			case AT_AddIndex:
+				strtype = "ADD INDEX";
+				break;
+			case AT_ReAddIndex:
+				strtype = "(re) ADD INDEX";
+				break;
+			case AT_AddConstraint:
+				strtype = "ADD CONSTRAINT";
+				break;
+			case AT_AddConstraintRecurse:
+				strtype = "ADD CONSTRAINT (and recurse)";
+				break;
+			case AT_ReAddConstraint:
+				strtype = "(re) ADD CONSTRAINT";
+				break;
+			case AT_AlterConstraint:
+				strtype = "ALTER CONSTRAINT";
+				break;
+			case AT_ValidateConstraint:
+				strtype = "VALIDATE CONSTRAINT";
+				break;
+			case AT_ValidateConstraintRecurse:
+				strtype = "VALIDATE CONSTRAINT (and recurse)";
+				break;
+			case AT_ProcessedConstraint:
+				strtype = "ADD (processed) CONSTRAINT";
+				break;
+			case AT_AddIndexConstraint:
+				strtype = "ADD CONSTRAINT (using index)";
+				break;
+			case AT_DropConstraint:
+				strtype = "DROP CONSTRAINT";
+				break;
+			case AT_DropConstraintRecurse:
+				strtype = "DROP CONSTRAINT (and recurse)";
+				break;
+			case AT_AlterColumnType:
+				strtype = "ALTER COLUMN SET TYPE";
+				break;
+			case AT_AlterColumnGenericOptions:
+				strtype = "ALTER COLUMN SET OPTIONS";
+				break;
+			case AT_ChangeOwner:
+				strtype = "CHANGE OWNER";
+				break;
+			case AT_ClusterOn:
+				strtype = "CLUSTER";
+				break;
+			case AT_DropCluster:
+				strtype = "DROP CLUSTER";
+				break;
+			case AT_SetLogged:
+				strtype = "SET LOGGED";
+				break;
+			case AT_SetUnLogged:
+				strtype = "SET UNLOGGED";
+				break;
+			case AT_AddOids:
+				strtype = "ADD OIDS";
+				break;
+			case AT_AddOidsRecurse:
+				strtype = "ADD OIDS (and recurse)";
+				break;
+			case AT_DropOids:
+				strtype = "DROP OIDS";
+				break;
+			case AT_SetTableSpace:
+				strtype = "SET TABLESPACE";
+				break;
+			case AT_SetRelOptions:
+				strtype = "SET RELOPTIONS";
+				break;
+			case AT_ResetRelOptions:
+				strtype = "RESET RELOPTIONS";
+				break;
+			case AT_ReplaceRelOptions:
+				strtype = "REPLACE RELOPTIONS";
+				break;
+			case AT_EnableTrig:
+				strtype = "ENABLE TRIGGER";
+				break;
+			case AT_EnableAlwaysTrig:
+				strtype = "ENABLE TRIGGER (always)";
+				break;
+			case AT_EnableReplicaTrig:
+				strtype = "ENABLE TRIGGER (replica)";
+				break;
+			case AT_DisableTrig:
+				strtype = "DISABLE TRIGGER";
+				break;
+			case AT_EnableTrigAll:
+				strtype = "ENABLE TRIGGER (all)";
+				break;
+			case AT_DisableTrigAll:
+				strtype = "DISABLE TRIGGER (all)";
+				break;
+			case AT_EnableTrigUser:
+				strtype = "ENABLE TRIGGER (user)";
+				break;
+			case AT_DisableTrigUser:
+				strtype = "DISABLE TRIGGER (user)";
+				break;
+			case AT_EnableRule:
+				strtype = "ENABLE RULE";
+				break;
+			case AT_EnableAlwaysRule:
+				strtype = "ENABLE RULE (always)";
+				break;
+			case AT_EnableReplicaRule:
+				strtype = "ENABLE RULE (replica)";
+				break;
+			case AT_DisableRule:
+				strtype = "DISABLE RULE";
+				break;
+			case AT_AddInherit:
+				strtype = "ADD INHERIT";
+				break;
+			case AT_DropInherit:
+				strtype = "DROP INHERIT";
+				break;
+			case AT_AddOf:
+				strtype = "OF";
+				break;
+			case AT_DropOf:
+				strtype = "NOT OF";
+				break;
+			case AT_ReplicaIdentity:
+				strtype = "REPLICA IDENTITY";
+				break;
+			case AT_EnableRowSecurity:
+				strtype = "ENABLE ROW SECURITY";
+				break;
+			case AT_DisableRowSecurity:
+				strtype = "DISABLE ROW SECURITY";
+				break;
+			case AT_GenericOptions:
+				strtype = "SET OPTIONS";
+				break;
+		}
+
+		astate =
+			accumArrayResult(astate, CStringGetTextDatum(strtype),
+							 false, TEXTOID, CurrentMemoryContext);
+	}
+
+	if (astate == NULL)
+		elog(ERROR, "empty alter table subcommand list");
+
+	PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
+}
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.control b/src/test/modules/test_ddl_deparse/test_ddl_deparse.control
new file mode 100644
index 0000000..09112ee
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.control
@@ -0,0 +1,4 @@
+comment = 'Test code for DDL deparse feature'
+default_version = '1.0'
+module_pathname = '$libdir/test_ddl_deparse'
+relocatable = true
#51David Steele
david@pgmasters.net
In reply to: Alvaro Herrera (#50)
Re: deparsing utility commands

On 5/8/15 3:29 PM, Alvaro Herrera wrote:

Here is a complete version. Barring serious problems, I intend to
commit this on Monday.

I have reviewed and tested this patch and everything looks good to me.
It also looks like you added better coverage for schema DDL, which is a
welcome addition.

--
- David Steele
david@pgmasters.net

#52Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: David Steele (#51)
Re: deparsing utility commands

David Steele wrote:

I have reviewed and tested this patch and everything looks good to me.
It also looks like you added better coverage for schema DDL, which is a
welcome addition.

Thanks -- I have pushed this now.

My hope is to get this test module extended quite a bit, not only to
cover existing commands, but also so that it causes future changes to
cause failure unless command collection is considered. (In a previous
version of this patch, there was a test mode that ran everything in the
serial_schedule of regular regression tests. That was IMV a good way to
ensure that new commands were also tested to run under command
collection. That hasn't been enabled in the new test module, and I
think it's necessary.)

If anyone wants to contribute to the test module so that more is
covered, that would be much appreciated.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#53Shulgin, Oleksandr
oleksandr.shulgin@zalando.de
In reply to: Alvaro Herrera (#52)
1 attachment(s)
Re: deparsing utility commands

On Tue, May 12, 2015 at 12:25 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

David Steele wrote:

I have reviewed and tested this patch and everything looks good to me.
It also looks like you added better coverage for schema DDL, which is a
welcome addition.

Thanks -- I have pushed this now.

Hi,

I've tried compiling the 0003-ddl_deparse-extension part from
/messages/by-id/20150409161419.GC4369@alvh.no-ip.org
on current master and that has failed because the 0002 part hasn't been
actually pushed (I've asked Alvaro off the list about this, that's how I
know the reason ;-).

I was able to update the 0002 part so it applies cleanly (updated version
attached), and then the contrib module compiles after one minor change and
seems to work.

I've started to look into what it would take to move 0002's code to the
extension itself, and I've got a question about use of printTypmod() in
format_type_detailed():

if (typemod >= 0)
*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
else
*typemodstr = pstrdup("");

Given that printTypmod() does psprintf("%s%s") one way or the other,
shouldn't we pass an empty string here instead of NULL as typname argument?

My hope is to get this test module extended quite a bit, not only to

cover existing commands, but also so that it causes future changes to
cause failure unless command collection is considered. (In a previous
version of this patch, there was a test mode that ran everything in the
serial_schedule of regular regression tests. That was IMV a good way to
ensure that new commands were also tested to run under command
collection. That hasn't been enabled in the new test module, and I
think it's necessary.)

If anyone wants to contribute to the test module so that more is
covered, that would be much appreciated.

I'm planning to have a look at this part also.

--
Alex

Attachments:

ddl_deparse-core-support.patchtext/x-patch; charset=US-ASCII; name=ddl_deparse-core-support.patchDownload
From 5381f5efafd1c2fd29b7c842e5ef1edd552d545e Mon Sep 17 00:00:00 2001
From: Oleksandr Shulgin <oleksandr.shulgin@zalando.de>
Date: Wed, 29 Jul 2015 11:09:56 +0200
Subject: [PATCH] ddl_deparse core support

---
 src/backend/commands/seclabel.c     |   5 +
 src/backend/commands/sequence.c     |  34 +++
 src/backend/commands/tablecmds.c    |   3 +-
 src/backend/utils/adt/format_type.c | 127 +++++++++
 src/backend/utils/adt/regproc.c     | 101 +++++--
 src/backend/utils/adt/ruleutils.c   | 516 ++++++++++++++++++++++++++++++++----
 src/include/commands/sequence.h     |   1 +
 src/include/utils/builtins.h        |   4 +
 src/include/utils/ruleutils.h       |  21 +-
 9 files changed, 730 insertions(+), 82 deletions(-)

diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 1ef98ce..aa4de97 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -63,6 +63,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("must specify provider when multiple security label providers have been loaded")));
 		provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
 	}
 	else
 	{
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 9c1037f..b0f8003 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1517,6 +1517,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/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d394713..5058f9f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8169,7 +8169,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/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index a851983..a8aacfb 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -328,6 +328,133 @@ format_type_internal(Oid type_oid, int32 typemod,
 
 
 /*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name in this case.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *typarray)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID ||
+		type_oid == TIMEOID ||
+		type_oid == TIMETZOID)
+	{
+		*typarray = false;
+
+peculiar_typmod:
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPTZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmod, so fall through */
+			case TIMESTAMPOID:
+				*typname = pstrdup("TIMESTAMP");
+				break;
+			case TIMETZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIME WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmode, so fall through */
+			case TIMEOID:
+				*typname = pstrdup("TIME");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		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;
+		*typarray = true;
+
+		/*
+		 * If it's an array of one of the types with special typmod rules,
+		 * have the element type be processed as above, but now with typarray
+		 * set to true.
+		 */
+		if (type_oid == INTERVALOID ||
+			type_oid == TIMESTAMPTZOID ||
+			type_oid == TIMESTAMPOID ||
+			type_oid == TIMETZOID ||
+			type_oid == TIMEOID)
+			goto peculiar_typmod;
+	}
+	else
+		*typarray = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
+
+/*
  * Add typmod decoration to the basic type name
  */
 static char *
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 0bfeb5e..5efa5ea 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -43,7 +43,11 @@
 #include "utils/acl.h"
 
 static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
+static char *format_procedure_internal(Oid procedure_oid, bool force_qualify,
+						  bool args_only);
+static void format_procedure_args_internal(Form_pg_proc procform,
+							   StringInfo buf, bool force_qualify);
+
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -364,13 +368,36 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_internal(procedure_oid, false, false);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_internal(procedure_oid, true, false);
+}
+
+/*
+ * format_procedure_args	- converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+	StringInfoData buf;
+	HeapTuple	proctup;
+	Form_pg_proc procform;
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+	initStringInfo(&buf);
+	format_procedure_args_internal(procform, &buf, force_qualify);
+
+	ReleaseSysCache(proctup);
+
+	return buf.data;
 }
 
 /*
@@ -381,7 +408,7 @@ format_procedure_qualified(Oid procedure_oid)
  * qualified if the function is not in path.
  */
 static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+format_procedure_internal(Oid procedure_oid, bool force_qualify, bool args_only)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -392,8 +419,6 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 	{
 		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
 		char	   *proname = NameStr(procform->proname);
-		int			nargs = procform->pronargs;
-		int			i;
 		char	   *nspname;
 		StringInfoData buf;
 
@@ -401,29 +426,24 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		initStringInfo(&buf);
 
-		/*
-		 * Would this proc be found (given the right args) by regprocedurein?
-		 * If not, or if caller requests it, we need to qualify it.
-		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
-			nspname = NULL;
-		else
-			nspname = get_namespace_name(procform->pronamespace);
-
-		appendStringInfo(&buf, "%s(",
-						 quote_qualified_identifier(nspname, proname));
-		for (i = 0; i < nargs; i++)
+		if (!args_only)
 		{
-			Oid			thisargtype = procform->proargtypes.values[i];
-
-			if (i > 0)
-				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf,
-								   force_qualify ?
-								   format_type_be_qualified(thisargtype) :
-								   format_type_be(thisargtype));
+			/*
+			 * Would this proc be found (given the right args) by
+			 * regprocedurein?  If not, or if caller requests it, we need to
+			 * qualify it.
+			 */
+			if (!force_qualify && FunctionIsVisible(procedure_oid))
+				nspname = NULL;
+			else
+				nspname = get_namespace_name(procform->pronamespace);
+
+			appendStringInfo(&buf, "%s",
+							 quote_qualified_identifier(nspname, proname));
 		}
-		appendStringInfoChar(&buf, ')');
+
+		/* add the attributes */
+		format_procedure_args_internal(procform, &buf, force_qualify);
 
 		result = buf.data;
 
@@ -440,6 +460,33 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 }
 
 /*
+ * Append the parenthised arguments of the given pg_proc row into the output
+ * buffer.  force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+							   bool force_qualify)
+{
+	int			i;
+	int			nargs = procform->pronargs;
+
+	appendStringInfoChar(buf, '(');
+	for (i = 0; i < nargs; i++)
+	{
+		Oid			thisargtype = procform->proargtypes.values[i];
+
+		if (i > 0)
+			appendStringInfoChar(buf, ',');
+		appendStringInfoString(buf,
+							   force_qualify ?
+							   format_type_be_qualified(thisargtype) :
+							   format_type_be(thisargtype));
+	}
+	appendStringInfoChar(buf, ')');
+}
+
+/*
  * Output an objname/objargs representation for the procedure with the
  * given OID.  If it doesn't exist, an error is thrown.
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5112cac..5cfaf9b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -462,6 +462,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)
@@ -612,6 +683,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
  */
@@ -686,6 +764,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def	- Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * get_triggerdef			- Get the definition of a trigger
  * ----------
@@ -836,60 +929,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char	   *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.ctes = NIL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-		context.special_exprkind = EXPR_KIND_NONE;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
@@ -930,6 +975,64 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char		relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+	initStringInfo(&buf);
+
+	relkind = get_rel_relkind(trigrec->tgrelid);
+
+	/* Build minimal OLD and NEW RTEs for the rel */
+	oldrte = makeNode(RangeTblEntry);
+	oldrte->rtekind = RTE_RELATION;
+	oldrte->relid = trigrec->tgrelid;
+	oldrte->relkind = relkind;
+	oldrte->alias = makeAlias("old", NIL);
+	oldrte->eref = oldrte->alias;
+	oldrte->lateral = false;
+	oldrte->inh = false;
+	oldrte->inFromCl = true;
+
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_RELATION;
+	newrte->relid = trigrec->tgrelid;
+	newrte->relkind = relkind;
+	newrte->alias = makeAlias("new", NIL);
+	newrte->eref = newrte->alias;
+	newrte->lateral = false;
+	newrte->inh = false;
+	newrte->inFromCl = true;
+
+	/* Build two-element rtable */
+	memset(&dpns, 0, sizeof(dpns));
+	dpns.rtable = list_make2(oldrte, newrte);
+	dpns.ctes = NIL;
+	set_rtable_names(&dpns, NIL, NULL);
+	set_simple_column_names(&dpns);
+
+	/* Set up context with one-deep namespace stack */
+	context.buf = &buf;
+	context.namespaces = list_make1(&dpns);
+	context.windowClause = NIL;
+	context.windowTList = NIL;
+	context.varprefix = true;
+	context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+	context.wrapColumn = WRAP_COLUMN_DEFAULT;
+	context.indentLevel = PRETTYINDENT_STD;
+	context.special_exprkind = EXPR_KIND_NONE;
+
+	get_rule_expr(whenClause, &context, false);
+
+	return buf.data;
+}
+
 /* ----------
  * get_indexdef			- Get the definition of an index
  *
@@ -993,6 +1096,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,
@@ -1272,6 +1377,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
@@ -1306,9 +1650,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);
 }
 
 /*
@@ -9753,3 +10097,69 @@ 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;
+}
+
+/*
+ * Return the default value of a domain.
+ */
+char *
+DomainGetDefault(HeapTuple domTup)
+{
+	Datum	def;
+	Node   *defval;
+	char   *defstr;
+	bool	isnull;
+
+	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+							 &isnull);
+	if (isnull)
+		elog(ERROR, "domain \"%s\" does not have a default value",
+			 NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+	defval = stringToNode(TextDatumGetCString(def));
+	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
+									   false, false, 0, 0);
+
+	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/commands/sequence.h b/src/include/commands/sequence.h
index 44862bb..8d8e556 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -72,6 +72,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 ObjectAddress DefineSequence(CreateSeqStmt *stmt);
 extern ObjectAddress AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fcb0bf0..642e03d 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -660,6 +660,7 @@ extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
 					   List **objargs);
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
@@ -1105,6 +1106,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);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 3494b13..8ff6a0c 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,6 +13,7 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
@@ -20,8 +21,20 @@
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+						  Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+					   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
 
-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);
@@ -31,5 +44,11 @@ extern List *set_deparse_context_planstate(List *dpcontext,
 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);
+
+extern char *DomainGetDefault(HeapTuple domTup);
 
 #endif   /* RULEUTILS_H */
-- 
2.1.4

#54Shulgin, Oleksandr
oleksandr.shulgin@zalando.de
In reply to: Shulgin, Oleksandr (#53)
Re: deparsing utility commands

On Wed, Jul 29, 2015 at 12:44 PM, Shulgin, Oleksandr <
oleksandr.shulgin@zalando.de> wrote:

On Tue, May 12, 2015 at 12:25 AM, Alvaro Herrera <alvherre@2ndquadrant.com

wrote:

David Steele wrote:

I have reviewed and tested this patch and everything looks good to me.
It also looks like you added better coverage for schema DDL, which is a
welcome addition.

Thanks -- I have pushed this now.

Hi,

I've tried compiling the 0003-ddl_deparse-extension part from
/messages/by-id/20150409161419.GC4369@alvh.no-ip.org
on current master and that has failed because the 0002 part hasn't been
actually pushed (I've asked Alvaro off the list about this, that's how I
know the reason ;-).

I was able to update the 0002 part so it applies cleanly (updated version
attached), and then the contrib module compiles after one minor change and
seems to work.

I've started to look into what it would take to move 0002's code to the
extension itself, and I've got a question about use of printTypmod() in
format_type_detailed():

if (typemod >= 0)
*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
else
*typemodstr = pstrdup("");

Given that printTypmod() does psprintf("%s%s") one way or the other,
shouldn't we pass an empty string here instead of NULL as typname argument?

Testing shows this is a bug indeed, easily triggered by deparsing any type
with typmod.

My hope is to get this test module extended quite a bit, not only to

cover existing commands, but also so that it causes future changes to
cause failure unless command collection is considered. (In a previous
version of this patch, there was a test mode that ran everything in the
serial_schedule of regular regression tests. That was IMV a good way to
ensure that new commands were also tested to run under command
collection. That hasn't been enabled in the new test module, and I
think it's necessary.)

If anyone wants to contribute to the test module so that more is
covered, that would be much appreciated.

I'm planning to have a look at this part also.

While running deparsecheck suite I'm getting a number of oddly looking
errors:

WARNING: state: 42883 errm: operator does not exist: pg_catalog.oid =
pg_catalog.oid

This is caused by deparsing create view, e.g.:

STATEMENT: create view v1 as select * from t1 ;
ERROR: operator does not exist: pg_catalog.oid = pg_catalog.oid at
character 52
HINT: No operator matches the given name and argument type(s). You might
need to add explicit type casts.
QUERY: SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND
rulename = $2
CONTEXT: PL/pgSQL function test_ddl_deparse() line 1 at FOR over SELECT
rows

The pg_rewrite query comes from ruleutils.c, while ddl_deparse.c calls it
through pg_get_viewdef_internal() but don't understand how is it different
from e.g., select pg_get_viewdef(...), and that last one is not affected.

Thoughts?

--
Alex

#55Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Shulgin, Oleksandr (#54)
Re: deparsing utility commands

On 7/31/15 8:45 AM, Shulgin, Oleksandr wrote:

While running deparsecheck suite I'm getting a number of oddly looking
errors:

WARNING: state: 42883 errm: operator does not exist: pg_catalog.oid =
pg_catalog.oid

This is caused by deparsing create view, e.g.:

STATEMENT: create view v1 as select * from t1 ;
ERROR: operator does not exist: pg_catalog.oid = pg_catalog.oid at
character 52
HINT: No operator matches the given name and argument type(s). You
might need to add explicit type casts.
QUERY: SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND
rulename = $2
CONTEXT: PL/pgSQL function test_ddl_deparse() line 1 at FOR over SELECT
rows

The pg_rewrite query comes from ruleutils.c, while ddl_deparse.c calls
it through pg_get_viewdef_internal() but don't understand how is it
different from e.g., select pg_get_viewdef(...), and that last one is
not affected.

I'm not sure what test_ddl_deparse is doing, is that where the oid = oid
is coming from?

It might be enlightening to replace = with OPERATOR(pg_catalog.=) and
see if that works.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com

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

#56Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Jim Nasby (#55)
Re: deparsing utility commands

Jim Nasby wrote:

On 7/31/15 8:45 AM, Shulgin, Oleksandr wrote:

STATEMENT: create view v1 as select * from t1 ;
ERROR: operator does not exist: pg_catalog.oid = pg_catalog.oid at
character 52
HINT: No operator matches the given name and argument type(s). You
might need to add explicit type casts.
QUERY: SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND
rulename = $2
CONTEXT: PL/pgSQL function test_ddl_deparse() line 1 at FOR over SELECT
rows

I'm not sure what test_ddl_deparse is doing, is that where the oid = oid is
coming from?

It might be enlightening to replace = with OPERATOR(pg_catalog.=) and see if
that works.

That worked, thanks!

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#57Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Alvaro Herrera (#56)
Re: deparsing utility commands

On 8/5/15 9:55 PM, Alvaro Herrera wrote:

Jim Nasby wrote:

On 7/31/15 8:45 AM, Shulgin, Oleksandr wrote:

STATEMENT: create view v1 as select * from t1 ;
ERROR: operator does not exist: pg_catalog.oid = pg_catalog.oid at
character 52
HINT: No operator matches the given name and argument type(s). You
might need to add explicit type casts.
QUERY: SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND
rulename = $2
CONTEXT: PL/pgSQL function test_ddl_deparse() line 1 at FOR over SELECT
rows

I'm not sure what test_ddl_deparse is doing, is that where the oid = oid is
coming from?

It might be enlightening to replace = with OPERATOR(pg_catalog.=) and see if
that works.

That worked, thanks!

Good, but why weren't operator resolution rules working correctly here
to begin with?

FWIW, my interest in this is largely due to the problems I've had with
this in the variant type. In particular, using the same resolution rules
for functions and operators. So I'm wondering if there's a bigger issue
here.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com

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

#58Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Jim Nasby (#57)
Re: deparsing utility commands

Jim Nasby wrote:

FWIW, my interest in this is largely due to the problems I've had with this
in the variant type. In particular, using the same resolution rules for
functions and operators. So I'm wondering if there's a bigger issue here.

I'd be glad to review your variant stuff, but I doubt it's the same
thing at play. Please don't hijack this thread. Deparse is doing some
pretty nasty games with the backend innards to make the deparsed
representation usable generally. We were just missing a simple trick.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#59Shulgin, Oleksandr
oleksandr.shulgin@zalando.de
In reply to: Shulgin, Oleksandr (#54)
2 attachment(s)
Re: deparsing utility commands

My hope is to get this test module extended quite a bit, not only to

cover existing commands, but also so that it causes future changes to
cause failure unless command collection is considered. (In a previous
version of this patch, there was a test mode that ran everything in the
serial_schedule of regular regression tests. That was IMV a good way to
ensure that new commands were also tested to run under command
collection. That hasn't been enabled in the new test module, and I
think it's necessary.)

If anyone wants to contribute to the test module so that more is
covered, that would be much appreciated.

I'm planning to have a look at this part also.

I've made some progress with this patch recently. Now I was able to get
rid of most errors in the deparsecheck run and it's time to have a look on
deparse_dump.errors. For now it can be summarized as follows:

src/test/regress$ wc -l results/deparse_dump.errors
147 results/deparse_dump.errors

src/test/regress$ < results/deparse_dump.errors sed
's/"[^"]*"\|[0-9]\+/???/g' | sort | uniq -c | sort -nr
98 ERROR: relation ??? does not exist
11 ERROR: column ??? of relation ??? does not exist
10 ERROR: role ??? does not exist
9 ERROR: large object ??? does not exist
6 ERROR: cannot alter type of column ??? twice
4 ERROR: column ??? of relation ??? already exists
2 ERROR: constraint ??? of relation ??? does not exist
1 ERROR: type public.rw_view??? does not exist
1 ERROR: schema ??? does not exist
1 ERROR: invalid value for parameter ???: ???
1 ERROR: invalid reference to FROM-clause entry for table ???
1 ERROR: index ??? does not exist
1 ERROR: column data type integer can only have storage PLAIN
1 ERROR: cannot alter type ??? because it is the type of a typed
table

A particularly nasty one is:

ERROR: index "cwi_replaced_pkey" does not exist

The test statement that's causing it is this one:

ALTER TABLE cwi_test DROP CONSTRAINT cwi_uniq_idx,
ADD CONSTRAINT cwi_replaced_pkey PRIMARY KEY
USING INDEX cwi_uniq2_idx;

Which gets deparsed as:

ALTER TABLE cwi_test DROP CONSTRAINT cwi_uniq_idx,
ADD CONSTRAINT cwi_replaced_pkey PRIMARY KEY
USING INDEX cwi_replaced_pkey;

The problem is that during constraint transformation, the index is being
renamed to match the constraint name and at the deparse stage the original
index name appears to be lost completely... I haven't figure out if
there's a way around unless we introduce a new field in IndexStmt
(originalName?) to cover exactly this corner case.

The updated versions of the core-support patch and the contrib modules are
attached.

--
Alex

Attachments:

ddl_deparse-core-support-v2.patchtext/x-patch; charset=US-ASCII; name=ddl_deparse-core-support-v2.patchDownload
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 90b1cd8..08a3ea6 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
 #include "catalog/pg_transform.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"
@@ -149,6 +150,7 @@ static const Oid object_classes[] = {
 	TSDictionaryRelationId,		/* OCLASS_TSDICT */
 	TSTemplateRelationId,		/* OCLASS_TSTEMPLATE */
 	TSConfigRelationId,			/* OCLASS_TSCONFIG */
+	TSConfigMapRelationId,		/* OCLASS_TSCONFIG_MAP */
 	AuthIdRelationId,			/* OCLASS_ROLE */
 	DatabaseRelationId,			/* OCLASS_DATABASE */
 	TableSpaceRelationId,		/* OCLASS_TBLSPACE */
@@ -2382,6 +2384,9 @@ getObjectClass(const ObjectAddress *object)
 		case TSConfigRelationId:
 			return OCLASS_TSCONFIG;
 
+		case TSConfigMapRelationId:
+			return OCLASS_TSCONFIG_MAP;
+
 		case AuthIdRelationId:
 			return OCLASS_ROLE;
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 052aab1..e01caa1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_transform.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"
@@ -390,6 +391,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		true
 	},
 	{
+		TSConfigMapRelationId,
+		TSConfigMapIndexId,
+		TSCONFIGMAP,
+		TSCONFIGNAMENSP,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		ACL_KIND_TSCONFIGURATION,
+		true
+	},
+	{
 		TSDictionaryRelationId,
 		TSDictionaryOidIndexId,
 		TSDICTOID,
@@ -595,6 +608,10 @@ static const struct object_type_map
 	{
 		"text search configuration", OBJECT_TSCONFIGURATION
 	},
+	/* OCLASS_TSCONFIG_MAP */
+	{
+		"text search configuration mapping", OBJECT_TSCONFIG_MAPPING
+	},
 	/* OCLASS_ROLE */
 	{
 		"role", OBJECT_ROLE
@@ -903,6 +920,11 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 				address.objectId = get_ts_config_oid(objname, missing_ok);
 				address.objectSubId = 0;
 				break;
+			case OBJECT_TSCONFIG_MAPPING:
+				address.classId = TSConfigMapRelationId;
+				address.objectId = get_ts_config_oid(objname, missing_ok);
+				address.objectSubId = 0;
+				break;
 			case OBJECT_USER_MAPPING:
 				address = get_object_address_usermapping(objname, objargs,
 														 missing_ok);
@@ -2150,6 +2172,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 							   NameListToString(objname));
 			break;
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_TSCONFIG_MAPPING:
 			if (!pg_ts_config_ownercheck(address.objectId, roleid))
 				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
 							   NameListToString(objname));
@@ -2237,7 +2260,7 @@ get_object_namespace(const ObjectAddress *address)
 int
 read_objtype_from_string(const char *objtype)
 {
-	ObjectType	type;
+	ObjectType	type = -1;
 	int			i;
 
 	for (i = 0; i < lengthof(ObjectTypeMap); i++)
@@ -2905,6 +2928,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_TSCONFIG:
+		case OCLASS_TSCONFIG_MAP:
 			{
 				HeapTuple	tup;
 
@@ -3564,6 +3588,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "text search configuration");
 			break;
 
+		case OCLASS_TSCONFIG_MAP:
+			appendStringInfoString(&buffer, "text search configuration mapping");
+			break;
+
 		case OCLASS_ROLE:
 			appendStringInfoString(&buffer, "role");
 			break;
@@ -4296,6 +4324,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_TSCONFIG:
+		case OCLASS_TSCONFIG_MAP:
 			{
 				HeapTuple	tup;
 				Form_pg_ts_config formCfg;
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 1ef98ce..aa4de97 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -63,6 +63,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("must specify provider when multiple security label providers have been loaded")));
 		provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
 	}
 	else
 	{
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 9c1037f..b0f8003 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1517,6 +1517,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/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 126b119..54a5ebf 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3517,8 +3517,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = CommentObject((CommentStmt *) cmd->def);
 			break;
 		case AT_AddIndexConstraint:		/* ADD CONSTRAINT USING INDEX */
+			EventTriggerInhibitCommandCollection();
 			address = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
 											   lockmode);
+			EventTriggerUndoInhibitCommandCollection();
 			break;
 		case AT_AlterConstraint:		/* ALTER CONSTRAINT */
 			address = ATExecAlterConstraint(rel, cmd, false, false, lockmode);
@@ -8192,7 +8194,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/utility.c b/src/backend/tcop/utility.c
index e81bbc6..b848ec6 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1454,6 +1454,8 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_AlterTSConfigurationStmt:
 				address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				/* commands are stashed in Make/DropConfigurationMapping */
+				commandCollected = true;
 				break;
 
 			case T_AlterTableMoveAllStmt:
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 0bfeb5e..5efa5ea 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -43,7 +43,11 @@
 #include "utils/acl.h"
 
 static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
+static char *format_procedure_internal(Oid procedure_oid, bool force_qualify,
+						  bool args_only);
+static void format_procedure_args_internal(Form_pg_proc procform,
+							   StringInfo buf, bool force_qualify);
+
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -364,13 +368,36 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_internal(procedure_oid, false, false);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_internal(procedure_oid, true, false);
+}
+
+/*
+ * format_procedure_args	- converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+	StringInfoData buf;
+	HeapTuple	proctup;
+	Form_pg_proc procform;
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+	initStringInfo(&buf);
+	format_procedure_args_internal(procform, &buf, force_qualify);
+
+	ReleaseSysCache(proctup);
+
+	return buf.data;
 }
 
 /*
@@ -381,7 +408,7 @@ format_procedure_qualified(Oid procedure_oid)
  * qualified if the function is not in path.
  */
 static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+format_procedure_internal(Oid procedure_oid, bool force_qualify, bool args_only)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -392,8 +419,6 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 	{
 		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
 		char	   *proname = NameStr(procform->proname);
-		int			nargs = procform->pronargs;
-		int			i;
 		char	   *nspname;
 		StringInfoData buf;
 
@@ -401,29 +426,24 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		initStringInfo(&buf);
 
-		/*
-		 * Would this proc be found (given the right args) by regprocedurein?
-		 * If not, or if caller requests it, we need to qualify it.
-		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
-			nspname = NULL;
-		else
-			nspname = get_namespace_name(procform->pronamespace);
-
-		appendStringInfo(&buf, "%s(",
-						 quote_qualified_identifier(nspname, proname));
-		for (i = 0; i < nargs; i++)
+		if (!args_only)
 		{
-			Oid			thisargtype = procform->proargtypes.values[i];
-
-			if (i > 0)
-				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf,
-								   force_qualify ?
-								   format_type_be_qualified(thisargtype) :
-								   format_type_be(thisargtype));
+			/*
+			 * Would this proc be found (given the right args) by
+			 * regprocedurein?  If not, or if caller requests it, we need to
+			 * qualify it.
+			 */
+			if (!force_qualify && FunctionIsVisible(procedure_oid))
+				nspname = NULL;
+			else
+				nspname = get_namespace_name(procform->pronamespace);
+
+			appendStringInfo(&buf, "%s",
+							 quote_qualified_identifier(nspname, proname));
 		}
-		appendStringInfoChar(&buf, ')');
+
+		/* add the attributes */
+		format_procedure_args_internal(procform, &buf, force_qualify);
 
 		result = buf.data;
 
@@ -440,6 +460,33 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 }
 
 /*
+ * Append the parenthised arguments of the given pg_proc row into the output
+ * buffer.  force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+							   bool force_qualify)
+{
+	int			i;
+	int			nargs = procform->pronargs;
+
+	appendStringInfoChar(buf, '(');
+	for (i = 0; i < nargs; i++)
+	{
+		Oid			thisargtype = procform->proargtypes.values[i];
+
+		if (i > 0)
+			appendStringInfoChar(buf, ',');
+		appendStringInfoString(buf,
+							   force_qualify ?
+							   format_type_be_qualified(thisargtype) :
+							   format_type_be(thisargtype));
+	}
+	appendStringInfoChar(buf, ')');
+}
+
+/*
  * Output an objname/objargs representation for the procedure with the
  * given OID.  If it doesn't exist, an error is thrown.
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 51391f6..19ce290 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -289,9 +289,6 @@ bool		quote_all_identifiers = false;
  * as a parameter, and append their text output to its contents.
  * ----------
  */
-static char *deparse_expression_pretty(Node *expr, List *dpcontext,
-						  bool forceprefix, bool showimplicit,
-						  int prettyFlags, int startIndent);
 static char *pg_get_viewdef_worker(Oid viewoid,
 					  int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
@@ -415,8 +412,6 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
 						   deparse_context *context);
 static void get_tablesample_def(TableSampleClause *tablesample,
 					deparse_context *context);
-static void get_opclass_name(Oid opclass, Oid actual_datatype,
-				 StringInfo buf);
 static Node *processIndirection(Node *node, deparse_context *context,
 				   bool printit);
 static void printSubscripts(ArrayRef *aref, deparse_context *context);
@@ -428,7 +423,6 @@ static char *generate_function_name(Oid funcid, int nargs,
 					   ParseExprKind special_exprkind);
 static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
 static text *string_to_text(char *str);
-static char *flatten_reloptions(Oid relid);
 
 #define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")
 
@@ -461,6 +455,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_detailed(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)
@@ -611,6 +676,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
  */
@@ -685,6 +757,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def	- Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * get_triggerdef			- Get the definition of a trigger
  * ----------
@@ -835,60 +922,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char	   *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.ctes = NIL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-		context.special_exprkind = EXPR_KIND_NONE;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
@@ -929,6 +968,64 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char		relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+	initStringInfo(&buf);
+
+	relkind = get_rel_relkind(trigrec->tgrelid);
+
+	/* Build minimal OLD and NEW RTEs for the rel */
+	oldrte = makeNode(RangeTblEntry);
+	oldrte->rtekind = RTE_RELATION;
+	oldrte->relid = trigrec->tgrelid;
+	oldrte->relkind = relkind;
+	oldrte->alias = makeAlias("old", NIL);
+	oldrte->eref = oldrte->alias;
+	oldrte->lateral = false;
+	oldrte->inh = false;
+	oldrte->inFromCl = true;
+
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_RELATION;
+	newrte->relid = trigrec->tgrelid;
+	newrte->relkind = relkind;
+	newrte->alias = makeAlias("new", NIL);
+	newrte->eref = newrte->alias;
+	newrte->lateral = false;
+	newrte->inh = false;
+	newrte->inFromCl = true;
+
+	/* Build two-element rtable */
+	memset(&dpns, 0, sizeof(dpns));
+	dpns.rtable = list_make2(oldrte, newrte);
+	dpns.ctes = NIL;
+	set_rtable_names(&dpns, NIL, NULL);
+	set_simple_column_names(&dpns);
+
+	/* Set up context with one-deep namespace stack */
+	context.buf = &buf;
+	context.namespaces = list_make1(&dpns);
+	context.windowClause = NIL;
+	context.windowTList = NIL;
+	context.varprefix = true;
+	context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+	context.wrapColumn = WRAP_COLUMN_DEFAULT;
+	context.indentLevel = PRETTYINDENT_STD;
+	context.special_exprkind = EXPR_KIND_NONE;
+
+	get_rule_expr(whenClause, &context, false);
+
+	return buf.data;
+}
+
 /* ----------
  * get_indexdef			- Get the definition of an index
  *
@@ -992,6 +1089,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,
@@ -1271,7 +1370,6 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
-
 /*
  * pg_get_constraintdef
  *
@@ -1305,9 +1403,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);
 }
 
 /*
@@ -2494,7 +2592,7 @@ deparse_expression(Node *expr, List *dpcontext,
  * The result is a palloc'd string.
  * ----------
  */
-static char *
+char *
 deparse_expression_pretty(Node *expr, List *dpcontext,
 						  bool forceprefix, bool showimplicit,
 						  int prettyFlags, int startIndent)
@@ -9165,7 +9263,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
  * actual_datatype.  (If you don't want this behavior, just pass
  * InvalidOid for actual_datatype.)
  */
-static void
+void
 get_opclass_name(Oid opclass, Oid actual_datatype,
 				 StringInfo buf)
 {
@@ -9713,7 +9811,7 @@ string_to_text(char *str)
 /*
  * Generate a C string representing a relation's reloptions, or NULL if none.
  */
-static char *
+char *
 flatten_reloptions(Oid relid)
 {
 	char	   *result = NULL;
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fbcf904..b400095 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -143,6 +143,7 @@ typedef enum ObjectClass
 	OCLASS_TSDICT,				/* pg_ts_dict */
 	OCLASS_TSTEMPLATE,			/* pg_ts_template */
 	OCLASS_TSCONFIG,			/* pg_ts_config */
+	OCLASS_TSCONFIG_MAP,		/* pg_ts_config_map */
 	OCLASS_ROLE,				/* pg_authid */
 	OCLASS_DATABASE,			/* pg_database */
 	OCLASS_TBLSPACE,			/* pg_tablespace */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 44862bb..8d8e556 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -72,6 +72,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 ObjectAddress DefineSequence(CreateSeqStmt *stmt);
 extern ObjectAddress AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 151c93a..a151412 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1408,6 +1408,7 @@ typedef enum ObjectType
 	OBJECT_TRANSFORM,
 	OBJECT_TRIGGER,
 	OBJECT_TSCONFIGURATION,
+	OBJECT_TSCONFIG_MAPPING,
 	OBJECT_TSDICTIONARY,
 	OBJECT_TSPARSER,
 	OBJECT_TSTEMPLATE,
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fc1679e..c3ff3d7 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -662,6 +662,7 @@ extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
 					   List **objargs);
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 3494b13..73b19d2 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,15 +13,25 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
 
 
+extern char *deparse_expression_pretty(Node *expr, List *dpcontext,
+						  bool forceprefix, bool showimplicit,
+						  int prettyFlags, int startIndent);
 extern char *pg_get_indexdef_string(Oid indexrelid);
 extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
+extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+						  Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+extern void pg_get_ruledef_detailed(Datum ev_qual, Datum ev_action,
+					   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
 
-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);
@@ -30,6 +40,9 @@ extern List *set_deparse_context_planstate(List *dpcontext,
 							  Node *planstate, List *ancestors);
 extern List *select_rtable_names_for_explain(List *rtable,
 								Bitmapset *rels_used);
+extern void get_opclass_name(Oid opclass, Oid actual_datatype,
+				 StringInfo buf);
 extern char *generate_collation_name(Oid collid);
+extern char *flatten_reloptions(Oid relid);
 
 #endif   /* RULEUTILS_H */
diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile
index 4d6849e..1bcad70 100644
--- a/src/test/regress/GNUmakefile
+++ b/src/test/regress/GNUmakefile
@@ -123,6 +123,16 @@ tablespace-setup:
 	rm -rf ./testtablespace
 	mkdir ./testtablespace
 
+# DDL-deparse setup
+
+.PHONY: generate-files
+generate-files:
+	$(top_builddir)/src/test/regress/pg_regress --generate-files-only --inputdir=$(srcdir)/input
+
+ddl_deparse_schedule: serial_schedule
+	echo "test: deparse_init" > $@
+	grep -v tablespace $(srcdir)/serial_schedule >> $@
+	echo "test: deparse_test" >> $@
 
 ##
 ## Run tests
@@ -148,6 +158,18 @@ installcheck-tests: all tablespace-setup
 standbycheck: all
 	$(pg_regress_installcheck) $(REGRESS_OPTS) --schedule=$(srcdir)/standby_schedule --use-existing
 
+deparsecheck: REGRESS_OPTS += --keep-instance --load-extension=ddl_deparse
+deparsecheck: all temp-install generate-files ddl_deparse_schedule
+	$(MAKE) -C $(top_builddir)/contrib/ddl_deparse DESTDIR=$(abs_top_builddir)/tmp_install install
+	$(pg_regress_check) $(REGRESS_OPTS) --schedule=ddl_deparse_schedule
+	grep ERROR results/deparse_dump.out > results/deparse_dump.errors
+	$(abs_top_builddir)/tmp_install/$(bindir)/pg_ctl -w start -D ./tmp_check/data -l ./tmp_check/log/secondary.log -o "-p 49152"
+	$(abs_top_builddir)/tmp_install/$(bindir)/pg_dump -p 49152 -d regression_deparse -s -f results/deparse.dump
+	$(abs_top_builddir)/tmp_install/$(bindir)/pg_dump -p 49152 -d regression -s -f results/regression.dump
+	$(abs_top_builddir)/tmp_install/$(bindir)/pg_ctl -D ./tmp_check/data stop
+	diff -c results/deparse.dump results/regression.dump
+	diff -c results/deparse_dump.errors $(srcdir)/expected/deparse_dump.errors
+
 # old interfaces follow...
 
 runcheck: check
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 28422ea..24d2675 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1862,7 +1862,9 @@ where virtualtransaction = (
         from pg_locks
         where transactionid = txid_current()::integer)
 and locktype = 'relation'
-and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
+and relnamespace NOT IN (
+	select oid from pg_namespace
+	where nspname IN ('pg_catalog', 'pg_deparse'))
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
diff --git a/src/test/regress/expected/create_function_ddl_demo.out b/src/test/regress/expected/create_function_ddl_demo.out
new file mode 100644
index 0000000..ff8a1d9
--- /dev/null
+++ b/src/test/regress/expected/create_function_ddl_demo.out
@@ -0,0 +1,4 @@
+CREATE FUNCTION check_foreign_key ()
+	RETURNS trigger
+	AS '/space/sda1/ibarwick/2ndquadrant_bdr/src/test/regress/refint.so'
+	LANGUAGE C;
diff --git a/src/test/regress/expected/deparse_init.out b/src/test/regress/expected/deparse_init.out
new file mode 100644
index 0000000..f288be1
--- /dev/null
+++ b/src/test/regress/expected/deparse_init.out
@@ -0,0 +1,178 @@
+--
+-- DEPARSE_INIT
+--
+CREATE SCHEMA deparse;
+UPDATE pg_namespace SET nspname = 'pg_deparse' WHERE nspname = 'deparse';
+CREATE UNLOGGED TABLE pg_deparse.deparse_test_commands (
+  backend_id int,
+  backend_start timestamptz,
+  lsn pg_lsn,
+  ord integer,
+  command TEXT
+);
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_ddl_command_end()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+BEGIN
+	BEGIN
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, command, ord, lsn)
+		SELECT id, pg_stat_get_backend_start(id),
+		     public.ddl_deparse_expand_command(public.ddl_deparse_to_json(command)),
+                     ordinality, lsn
+		FROM pg_event_trigger_dll_commands() WITH ORDINALITY,
+		pg_current_xlog_insert_location() lsn,
+		pg_stat_get_backend_idset() id
+		 WHERE pg_stat_get_backend_pid(id) = pg_backend_pid() AND
+		NOT command_tag = 'CREATE TABLE AS EXECUTE';
+	EXCEPTION WHEN OTHERS THEN 
+			RAISE WARNING 'state: % errm: %', sqlstate, sqlerrm;
+	END;
+END;
+$fn$;
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_sql_drop()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+DECLARE
+fmt	TEXT;
+obj RECORD;
+i	integer = 1;
+BEGIN
+
+	/* This function runs in the sql_drop event trigger.
+     *
+	 * When it runs, we know that all objects reported by the
+	 * pg_event_trigger_dropped_objects() function marked as "original" have
+	 * been mentioned in the DROP command, either directly by name or
+	 * indirectly by owner (DROP OWNED BY).  Since no objects that depend on
+	 * them can persist after that, we can replicate the effect of that by
+	 * executing an equivalent "DROP IF EXISTS object ... CASCADE".  CASCADE
+	 * lets the deletion work even in presence of objects that appear further
+	 * down in the return set of pg_event_trigger_dropped_objects, while IF
+	 * EXISTS let the deletion silently do nothing if the object was already
+	 * dropped because it was dependent on another object before it in the same
+	 * result set.
+     *
+     * (In general, it is impossible to reorder the result set in a way that
+     * would be completely free of dependency issues.)
+     */
+
+	FOR obj IN
+	SELECT object_type, address_names, address_args, object_identity
+	  FROM pg_event_trigger_dropped_objects()
+	 WHERE original
+	  LOOP
+
+		-- special case for default acls: ignore them.
+		IF obj.object_type = 'default acl' THEN
+			CONTINUE;
+		END IF;
+
+		/*
+		 * special cases for objects that are part of other objects: drop
+		 * each in a separate command.  Since we only deal with "original"
+		 * objects, these would not be reported in the complex case of
+		 * DROP OWNED.
+		 */
+		IF obj.object_type = 'table column' OR obj.object_type = 'foreign table column' THEN
+			fmt = format('ALTER TABLE %I.%I DROP COLUMN %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'composite type column' THEN
+			fmt = format('ALTER TYPE %I.%I DROP ATTRIBUTE %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TYPE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'table constraint' THEN
+			fmt = format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'domain constraint' THEN
+			fmt = format('ALTER DOMAIN %s DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_args[1]);
+		ELSIF obj.object_type = 'default value' THEN
+			fmt = format('ALTER TABLE %I.%I ALTER COLUMN %I DROP DEFAULT',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+		ELSIF obj.object_type = 'foreign-data wrapper' THEN
+			fmt = format('DROP FOREIGN DATA WRAPPER IF EXISTS %s CASCADE',
+				obj.object_identity);
+		ELSIF obj.object_type = 'user mapping' THEN
+			fmt = format('DROP USER MAPPING FOR %I SERVER %I',
+				obj.address_names[1], obj.address_args[1]);
+		ELSIF obj.object_type = 'operator of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP OPERATOR %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'function of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP FUNCTION %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSE
+			-- all other cases
+			fmt := format('DROP %s IF EXISTS %s CASCADE',
+				obj.object_type, obj.object_identity);
+		END IF;
+
+		IF fmt IS NULL THEN
+			CONTINUE;
+		END IF;
+
+		fmt := fmt || ' /* DROP support */';
+
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, lsn, ord, command)
+			 SELECT id, pg_stat_get_backend_start(id),
+			        pg_current_xlog_insert_location(), i, fmt
+			   FROM pg_stat_get_backend_idset() id
+			  WHERE pg_stat_get_backend_pid(id) = pg_backend_pid();
+		i := i + 1;
+	END LOOP;
+END;
+$fn$;
+CREATE OR REPLACE FUNCTION pg_deparse.output_commands() RETURNS SETOF text LANGUAGE PLPGSQL AS $$
+DECLARE
+        cmd text;
+        prev_id int = -1;
+        prev_start timestamptz = '-infinity';
+        sess_id int;
+        sess_start timestamptz;
+BEGIN
+   FOR cmd, sess_id, sess_start IN
+			   SELECT command, backend_id, backend_start
+                 FROM pg_deparse.deparse_test_commands
+			 ORDER BY lsn, ord
+   LOOP
+          IF (sess_id, sess_start) <> (prev_id, prev_start) THEN
+                prev_id := sess_id;
+                prev_start := sess_start;
+                RETURN NEXT '\c';
+          END IF;
+      RETURN NEXT cmd || ';' ;
+   END LOOP;
+END;
+$$;
+CREATE EVENT TRIGGER deparse_test_trg_sql_drop
+  ON sql_drop
+  EXECUTE PROCEDURE pg_deparse.deparse_test_sql_drop();
+CREATE EVENT TRIGGER deparse_test_trg_ddl_command_end
+  ON ddl_command_end
+  EXECUTE PROCEDURE pg_deparse.deparse_test_ddl_command_end();
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 034c0b1..0da2a34 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -12,6 +12,7 @@ CREATE FOREIGN DATA WRAPPER addr_fdw;
 CREATE SERVER addr_fserv FOREIGN DATA WRAPPER addr_fdw;
 CREATE TEXT SEARCH DICTIONARY addr_ts_dict (template=simple);
 CREATE TEXT SEARCH CONFIGURATION addr_ts_conf (copy=english);
+ALTER TEXT SEARCH CONFIGURATION addr_ts_conf ALTER MAPPING FOR asciiword WITH addr_ts_dict;
 CREATE TEXT SEARCH TEMPLATE addr_ts_temp (lexize=dsimple_lexize);
 CREATE TEXT SEARCH PARSER addr_ts_prs
     (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype);
@@ -79,6 +80,7 @@ BEGIN
 		('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
 		('text search parser'), ('text search dictionary'),
 		('text search template'), ('text search configuration'),
+		('text search configuration mapping'),
 		('policy'), ('user mapping'), ('default acl'), ('transform'),
 		('operator of access method'), ('function of access method')
 	LOOP
@@ -246,6 +248,12 @@ WARNING:  error for text search configuration,{addr_nsp,zwei},{}: text search co
 WARNING:  error for text search configuration,{addr_nsp,zwei},{integer}: text search configuration "addr_nsp.zwei" does not exist
 WARNING:  error for text search configuration,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
 WARNING:  error for text search configuration,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for text search configuration mapping,{eins},{}: text search configuration "eins" does not exist
+WARNING:  error for text search configuration mapping,{eins},{integer}: text search configuration "eins" does not exist
+WARNING:  error for text search configuration mapping,{addr_nsp,zwei},{}: text search configuration "addr_nsp.zwei" does not exist
+WARNING:  error for text search configuration mapping,{addr_nsp,zwei},{integer}: text search configuration "addr_nsp.zwei" does not exist
+WARNING:  error for text search configuration mapping,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for text search configuration mapping,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
 WARNING:  error for policy,{eins},{}: must specify relation and object name
 WARNING:  error for policy,{eins},{integer}: must specify relation and object name
 WARNING:  error for policy,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist
@@ -362,6 +370,7 @@ WITH objects (type, name, args) AS (VALUES
 				('text search dictionary', '{addr_ts_dict}', '{}'),
 				('text search template', '{addr_ts_temp}', '{}'),
 				('text search configuration', '{addr_ts_conf}', '{}'),
+				('text search configuration mapping', '{addr_ts_conf}', '{}'),
 				('role', '{regtest_addr_user}', '{}'),
 				-- database
 				-- tablespace
@@ -383,50 +392,51 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*,
 			pg_identify_object_as_address(classid, objid, subobjid) ioa(typ,nms,args),
 			pg_get_object_address(typ, nms, ioa.args) as addr2
 	ORDER BY addr1.classid, addr1.objid, addr1.subobjid;
-           type            |   schema   |       name        |                               identity                               | ?column? 
----------------------------+------------+-------------------+----------------------------------------------------------------------+----------
- default acl               |            |                   | for role regtest_addr_user in schema public on tables                | t
- default acl               |            |                   | for role regtest_addr_user on tables                                 | t
- type                      | pg_catalog | _int4             | integer[]                                                            | t
- type                      | addr_nsp   | gencomptype       | addr_nsp.gencomptype                                                 | t
- type                      | addr_nsp   | genenum           | addr_nsp.genenum                                                     | t
- type                      | addr_nsp   | gendomain         | addr_nsp.gendomain                                                   | t
- function                  | pg_catalog |                   | pg_catalog.pg_identify_object(pg_catalog.oid,pg_catalog.oid,integer) | t
- aggregate                 | addr_nsp   |                   | addr_nsp.genaggr(integer)                                            | t
- sequence                  | addr_nsp   | gentable_a_seq    | addr_nsp.gentable_a_seq                                              | t
- table                     | addr_nsp   | gentable          | addr_nsp.gentable                                                    | t
- table column              | addr_nsp   | gentable          | addr_nsp.gentable.b                                                  | t
- index                     | addr_nsp   | gentable_pkey     | addr_nsp.gentable_pkey                                               | t
- view                      | addr_nsp   | genview           | addr_nsp.genview                                                     | t
- materialized view         | addr_nsp   | genmatview        | addr_nsp.genmatview                                                  | t
- foreign table             | addr_nsp   | genftable         | addr_nsp.genftable                                                   | t
- foreign table column      | addr_nsp   | genftable         | addr_nsp.genftable.a                                                 | t
- role                      |            | regtest_addr_user | regtest_addr_user                                                    | t
- server                    |            | addr_fserv        | addr_fserv                                                           | t
- user mapping              |            |                   | regtest_addr_user on server integer                                  | t
- foreign-data wrapper      |            | addr_fdw          | addr_fdw                                                             | t
- operator of access method |            |                   | operator 1 (integer, integer) of pg_catalog.integer_ops USING btree  | t
- function of access method |            |                   | function 2 (integer, integer) of pg_catalog.integer_ops USING btree  | t
- default value             |            |                   | for addr_nsp.gentable.b                                              | t
- cast                      |            |                   | (bigint AS integer)                                                  | t
- table constraint          | addr_nsp   |                   | a_chk on addr_nsp.gentable                                           | t
- domain constraint         | addr_nsp   |                   | domconstr on addr_nsp.gendomain                                      | t
- conversion                | pg_catalog | ascii_to_mic      | pg_catalog.ascii_to_mic                                              | t
- language                  |            | plpgsql           | plpgsql                                                              | t
- schema                    |            | addr_nsp          | addr_nsp                                                             | t
- operator class            | pg_catalog | int4_ops          | pg_catalog.int4_ops USING btree                                      | t
- operator                  | pg_catalog |                   | pg_catalog.+(integer,integer)                                        | t
- rule                      |            |                   | "_RETURN" on addr_nsp.genview                                        | t
- trigger                   |            |                   | t on addr_nsp.gentable                                               | t
- operator family           | pg_catalog | integer_ops       | pg_catalog.integer_ops USING btree                                   | t
- policy                    |            |                   | genpol on addr_nsp.gentable                                          | t
- collation                 | pg_catalog | "default"         | pg_catalog."default"                                                 | t
- transform                 |            |                   | for integer on language sql                                          | t
- text search dictionary    | addr_nsp   | addr_ts_dict      | addr_nsp.addr_ts_dict                                                | t
- text search parser        | addr_nsp   | addr_ts_prs       | addr_nsp.addr_ts_prs                                                 | t
- text search configuration | addr_nsp   | addr_ts_conf      | addr_nsp.addr_ts_conf                                                | t
- text search template      | addr_nsp   | addr_ts_temp      | addr_nsp.addr_ts_temp                                                | t
-(41 rows)
+               type                |   schema   |       name        |                               identity                               | ?column? 
+-----------------------------------+------------+-------------------+----------------------------------------------------------------------+----------
+ default acl                       |            |                   | for role regtest_addr_user in schema public on tables                | t
+ default acl                       |            |                   | for role regtest_addr_user on tables                                 | t
+ type                              | pg_catalog | _int4             | integer[]                                                            | t
+ type                              | addr_nsp   | gencomptype       | addr_nsp.gencomptype                                                 | t
+ type                              | addr_nsp   | genenum           | addr_nsp.genenum                                                     | t
+ type                              | addr_nsp   | gendomain         | addr_nsp.gendomain                                                   | t
+ function                          | pg_catalog |                   | pg_catalog.pg_identify_object(pg_catalog.oid,pg_catalog.oid,integer) | t
+ aggregate                         | addr_nsp   |                   | addr_nsp.genaggr(integer)                                            | t
+ sequence                          | addr_nsp   | gentable_a_seq    | addr_nsp.gentable_a_seq                                              | t
+ table                             | addr_nsp   | gentable          | addr_nsp.gentable                                                    | t
+ table column                      | addr_nsp   | gentable          | addr_nsp.gentable.b                                                  | t
+ index                             | addr_nsp   | gentable_pkey     | addr_nsp.gentable_pkey                                               | t
+ view                              | addr_nsp   | genview           | addr_nsp.genview                                                     | t
+ materialized view                 | addr_nsp   | genmatview        | addr_nsp.genmatview                                                  | t
+ foreign table                     | addr_nsp   | genftable         | addr_nsp.genftable                                                   | t
+ foreign table column              | addr_nsp   | genftable         | addr_nsp.genftable.a                                                 | t
+ role                              |            | regtest_addr_user | regtest_addr_user                                                    | t
+ server                            |            | addr_fserv        | addr_fserv                                                           | t
+ user mapping                      |            |                   | regtest_addr_user on server integer                                  | t
+ foreign-data wrapper              |            | addr_fdw          | addr_fdw                                                             | t
+ operator of access method         |            |                   | operator 1 (integer, integer) of pg_catalog.integer_ops USING btree  | t
+ function of access method         |            |                   | function 2 (integer, integer) of pg_catalog.integer_ops USING btree  | t
+ default value                     |            |                   | for addr_nsp.gentable.b                                              | t
+ cast                              |            |                   | (bigint AS integer)                                                  | t
+ table constraint                  | addr_nsp   |                   | a_chk on addr_nsp.gentable                                           | t
+ domain constraint                 | addr_nsp   |                   | domconstr on addr_nsp.gendomain                                      | t
+ conversion                        | pg_catalog | ascii_to_mic      | pg_catalog.ascii_to_mic                                              | t
+ language                          |            | plpgsql           | plpgsql                                                              | t
+ schema                            |            | addr_nsp          | addr_nsp                                                             | t
+ operator class                    | pg_catalog | int4_ops          | pg_catalog.int4_ops USING btree                                      | t
+ operator                          | pg_catalog |                   | pg_catalog.+(integer,integer)                                        | t
+ rule                              |            |                   | "_RETURN" on addr_nsp.genview                                        | t
+ trigger                           |            |                   | t on addr_nsp.gentable                                               | t
+ operator family                   | pg_catalog | integer_ops       | pg_catalog.integer_ops USING btree                                   | t
+ policy                            |            |                   | genpol on addr_nsp.gentable                                          | t
+ collation                         | pg_catalog | "default"         | pg_catalog."default"                                                 | t
+ transform                         |            |                   | for integer on language sql                                          | t
+ text search dictionary            | addr_nsp   | addr_ts_dict      | addr_nsp.addr_ts_dict                                                | t
+ text search parser                | addr_nsp   | addr_ts_prs       | addr_nsp.addr_ts_prs                                                 | t
+ text search configuration         | addr_nsp   | addr_ts_conf      | addr_nsp.addr_ts_conf                                                | t
+ text search configuration mapping |            |                   | addr_nsp.addr_ts_conf                                                | t
+ text search template              | addr_nsp   | addr_ts_temp      | addr_nsp.addr_ts_temp                                                | t
+(42 rows)
 
 ---
 --- Cleanup resources
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..ab9af544 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -3,13 +3,14 @@ VACUUM;
 -- sanity check, if we don't have indices the test will take years to
 -- complete.  But skip TOAST relations (since they will have varying
 -- names depending on the current OID counter) as well as temp tables
--- of other backends (to avoid timing-dependent behavior).
+-- of other backends (to avoid timing-dependent behavior).  Also exclude
+-- the schema used for the deparse test, as it might not be there at all.
 --
 -- temporarily disable fancy output, so catalog changes create less diff noise
 \a\t
 SELECT relname, relhasindex
    FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace
-   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_') IS NOT TRUE
+   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_' OR nspname ~ '^pg_deparse') IS NOT TRUE
    ORDER BY relname;
 a|f
 a_star|f
diff --git a/src/test/regress/input/deparse_test.source b/src/test/regress/input/deparse_test.source
new file mode 100644
index 0000000..e7ba0dd
--- /dev/null
+++ b/src/test/regress/input/deparse_test.source
@@ -0,0 +1,57 @@
+---
+--- DEPARSE_TEST
+---
+
+-- create roles used throughout the tests
+create role clstr_user;
+create role "current_user";
+create role foreign_data_user;
+create role "Public";
+create role regressgroup1;
+create role regressgroup2;
+create role regression_bob;
+create role regression_group;
+create role regression_user1;
+create role regression_user2;
+create role regression_user3;
+create role regression_user;
+create role regresslo;
+create role regress_rol_lock1;
+create role regress_test_indirect;
+create role regress_test_role;
+create role regress_test_role2;
+create role regress_test_role_super superuser;
+create role regressuser1;
+create role regressuser2;
+create role regressuser3;
+create role regressuser4;
+create role regressuser5;
+create role regtest_unpriv_user;
+create role regtest_addr_user;
+create role regtest_alter_user1;
+create role regtest_alter_user2;
+create role regtest_alter_user3;
+create role rls_regress_group1;
+create role rls_regress_group2;
+create role rls_regress_user0;
+create role rls_regress_user1;
+create role rls_regress_user2;
+create role rls_regress_exempt_user;
+create role schemauser2;
+create role seclabel_user1;
+create role seclabel_user2;
+create role selinto_user;
+create role testrol1;
+create role testrol2;
+create role testrolx;
+create role unprivileged_role;
+create role "user";
+create role view_user2;
+
+\pset format unaligned
+\pset tuples_only
+\o ./sql/deparse_dump.sql
+
+SELECT * FROM pg_deparse.output_commands();
+
+\! @abs_builddir@/../../bin/psql/psql --dbname=@deparse_test_db@ -e < ./sql/deparse_dump.sql > results/deparse_dump.out 2>&1
diff --git a/src/test/regress/output/deparse_test.source b/src/test/regress/output/deparse_test.source
new file mode 100644
index 0000000..f083e7c
--- /dev/null
+++ b/src/test/regress/output/deparse_test.source
@@ -0,0 +1,53 @@
+---
+--- DEPARSE_TEST
+---
+-- create roles used throughout the tests
+create role clstr_user;
+create role "current_user";
+create role foreign_data_user;
+create role "Public";
+create role regressgroup1;
+create role regressgroup2;
+create role regression_bob;
+create role regression_group;
+create role regression_user1;
+create role regression_user2;
+create role regression_user3;
+create role regression_user;
+create role regresslo;
+create role regress_rol_lock1;
+create role regress_test_indirect;
+create role regress_test_role;
+create role regress_test_role2;
+create role regress_test_role_super superuser;
+create role regressuser1;
+create role regressuser2;
+create role regressuser3;
+create role regressuser4;
+create role regressuser5;
+create role regtest_unpriv_user;
+create role regtest_addr_user;
+create role regtest_alter_user1;
+create role regtest_alter_user2;
+create role regtest_alter_user3;
+create role rls_regress_group1;
+create role rls_regress_group2;
+create role rls_regress_user0;
+create role rls_regress_user1;
+create role rls_regress_user2;
+create role rls_regress_exempt_user;
+create role schemauser2;
+create role seclabel_user1;
+create role seclabel_user2;
+create role selinto_user;
+create role testrol1;
+create role testrol2;
+create role testrolx;
+create role unprivileged_role;
+create role "user";
+create role view_user2;
+\pset format unaligned
+\pset tuples_only
+\o ./sql/deparse_dump.sql
+SELECT * FROM pg_deparse.output_commands();
+\! @abs_builddir@/../../bin/psql/psql --dbname=@deparse_test_db@ -e < ./sql/deparse_dump.sql > results/deparse_dump.out 2>&1
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index dd65ab5..fe00929 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -68,6 +68,7 @@ const char *pretty_diff_opts = "-w -C3";
 
 /* options settable from command line */
 _stringlist *dblist = NULL;
+char	    deparse_test_db[NAMEDATALEN];
 bool		debug = false;
 char	   *inputdir = ".";
 char	   *outputdir = ".";
@@ -90,6 +91,8 @@ static char *dlpath = PKGLIBDIR;
 static char *user = NULL;
 static _stringlist *extraroles = NULL;
 static char *config_auth_datadir = NULL;
+static bool generate_files_only = false;
+static bool keep_instance = false;
 
 /* internal variables */
 static const char *progname;
@@ -554,6 +557,7 @@ convert_sourcefiles_in(char *source_subdir, char *dest_dir, char *dest_subdir, c
 			replace_string(line, "@testtablespace@", testtablespace);
 			replace_string(line, "@libdir@", dlpath);
 			replace_string(line, "@DLSUFFIX@", DLSUFFIX);
+			replace_string(line, "@deparse_test_db@", deparse_test_db);
 			fputs(line, outfile);
 		}
 		fclose(infile);
@@ -1958,6 +1962,8 @@ help(void)
 	printf(_("  --config-auth=DATADIR     update authentication settings for DATADIR\n"));
 	printf(_("  --create-role=ROLE        create the specified role before testing\n"));
 	printf(_("  --dbname=DB               use database DB (default \"regression\")\n"));
+	printf(_("  --dbname-deparse=DB       use database DB for DDL deparse test (default\n"));
+	printf(_("                            \"regression_deparse\")\n"));
 	printf(_("  --debug                   turn on debug mode in programs that are run\n"));
 	printf(_("  --dlpath=DIR              look for dynamic libraries in DIR\n"));
 	printf(_("  --encoding=ENCODING       use ENCODING as the encoding\n"));
@@ -1974,6 +1980,7 @@ help(void)
 	printf(_("                            (can be used multiple times to concatenate)\n"));
 	printf(_("  --temp-instance=DIR       create a temporary instance in DIR\n"));
 	printf(_("  --use-existing            use an existing installation\n"));
+	printf(_("  --keep-instance           don't destroy nor stop the temp instance\n"));
 	printf(_("\n"));
 	printf(_("Options for \"temp-instance\" mode:\n"));
 	printf(_("  --no-locale               use C locale\n"));
@@ -2018,6 +2025,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 		{"launcher", required_argument, NULL, 21},
 		{"load-extension", required_argument, NULL, 22},
 		{"config-auth", required_argument, NULL, 24},
+		{"dbname-deparse", required_argument, NULL, 25},
+		{"generate-files-only", no_argument, NULL, 26},
+		{"keep-instance", no_argument, NULL, 27},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -2065,6 +2075,14 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 				 */
 				free_stringlist(&dblist);
 				split_to_stringlist(strdup(optarg), ", ", &dblist);
+
+				/*
+				 * We need to update the default deparse test db name
+				 * to be a suffix of the provided database name
+				 */
+				snprintf(deparse_test_db, sizeof(deparse_test_db),
+						 "%s_deparse",
+						 dblist->str);
 				break;
 			case 2:
 				debug = true;
@@ -2131,6 +2149,15 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 			case 24:
 				config_auth_datadir = pstrdup(optarg);
 				break;
+			case 25:
+				strncpy(deparse_test_db, optarg, sizeof(deparse_test_db));
+				break;
+			case 26:
+				generate_files_only = true;
+				break;
+			case 27:
+				keep_instance = true;
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
@@ -2171,6 +2198,12 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	outputdir = make_absolute_path(outputdir);
 	dlpath = make_absolute_path(dlpath);
 
+	if (generate_files_only)
+	{
+		convert_sourcefiles();
+		exit(0);
+	}
+
 	/*
 	 * Initialization
 	 */
@@ -2404,6 +2437,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 		{
 			for (sl = dblist; sl; sl = sl->next)
 				drop_database_if_exists(sl->str);
+			drop_database_if_exists(deparse_test_db);
 			for (sl = extraroles; sl; sl = sl->next)
 				drop_role_if_exists(sl->str);
 		}
@@ -2416,6 +2450,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	{
 		for (sl = dblist; sl; sl = sl->next)
 			create_database(sl->str);
+		create_database(deparse_test_db);
 		for (sl = extraroles; sl; sl = sl->next)
 			create_role(sl->str, dblist);
 	}
@@ -2438,7 +2473,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	/*
 	 * Shut down temp installation's postmaster
 	 */
-	if (temp_instance)
+	if (temp_instance && !keep_instance)
 	{
 		header(_("shutting down postmaster"));
 		stop_postmaster();
@@ -2449,7 +2484,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	 * conserve disk space.  (If there were errors, we leave the instance in
 	 * place for possible manual investigation.)
 	 */
-	if (temp_instance && fail_count == 0 && fail_ignore_count == 0)
+	if (temp_instance && fail_count == 0 && fail_ignore_count == 0 && !keep_instance)
 	{
 		header(_("removing temporary instance"));
 		if (!rmtree(temp_instance, true))
diff --git a/src/test/regress/pg_regress.h b/src/test/regress/pg_regress.h
index 0fc9db7..8411d4f 100644
--- a/src/test/regress/pg_regress.h
+++ b/src/test/regress/pg_regress.h
@@ -38,6 +38,7 @@ extern char *datadir;
 extern char *host_platform;
 
 extern _stringlist *dblist;
+extern char deparse_test_db[NAMEDATALEN];
 extern bool debug;
 extern char *inputdir;
 extern char *outputdir;
diff --git a/src/test/regress/pg_regress_main.c b/src/test/regress/pg_regress_main.c
index 860431b..dd78805 100644
--- a/src/test/regress/pg_regress_main.c
+++ b/src/test/regress/pg_regress_main.c
@@ -88,6 +88,7 @@ psql_init(int argc, char **argv)
 {
 	/* set default regression database name */
 	add_stringlist_item(&dblist, "regression");
+	strlcpy(deparse_test_db, "regression_deparse", NAMEDATALEN);
 }
 
 int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 3ef55d9..01726a1 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1291,7 +1291,9 @@ where virtualtransaction = (
         from pg_locks
         where transactionid = txid_current()::integer)
 and locktype = 'relation'
-and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
+and relnamespace NOT IN (
+	select oid from pg_namespace
+	where nspname IN ('pg_catalog', 'pg_deparse'))
 and c.relname != 'my_locks'
 group by c.relname;
 
diff --git a/src/test/regress/sql/create_function_ddl_demo.sql b/src/test/regress/sql/create_function_ddl_demo.sql
new file mode 100644
index 0000000..ac29426
--- /dev/null
+++ b/src/test/regress/sql/create_function_ddl_demo.sql
@@ -0,0 +1,4 @@
+CREATE FUNCTION check_foreign_key ()
+	RETURNS trigger
+	AS '/space/sda1/ibarwick/2ndquadrant_bdr/src/test/regress/refint.so'
+	LANGUAGE C;
\ No newline at end of file
diff --git a/src/test/regress/sql/deparse_init.sql b/src/test/regress/sql/deparse_init.sql
new file mode 100644
index 0000000..996c914
--- /dev/null
+++ b/src/test/regress/sql/deparse_init.sql
@@ -0,0 +1,182 @@
+--
+-- DEPARSE_INIT
+--
+CREATE SCHEMA deparse;
+UPDATE pg_namespace SET nspname = 'pg_deparse' WHERE nspname = 'deparse';
+CREATE UNLOGGED TABLE pg_deparse.deparse_test_commands (
+  backend_id int,
+  backend_start timestamptz,
+  lsn pg_lsn,
+  ord integer,
+  command TEXT
+);
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_ddl_command_end()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+BEGIN
+	BEGIN
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, command, ord, lsn)
+		SELECT id, pg_stat_get_backend_start(id),
+		     public.ddl_deparse_expand_command(public.ddl_deparse_to_json(command)),
+                     ordinality, lsn
+		FROM pg_event_trigger_ddl_commands() WITH ORDINALITY,
+		pg_current_xlog_insert_location() lsn,
+		pg_stat_get_backend_idset() id
+		 WHERE pg_stat_get_backend_pid(id) = pg_backend_pid() AND
+		NOT command_tag = 'CREATE TABLE AS EXECUTE';
+	EXCEPTION WHEN OTHERS THEN 
+			RAISE WARNING 'state: % errm: %', sqlstate, sqlerrm;
+	END;
+END;
+$fn$;
+
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_sql_drop()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+DECLARE
+fmt	TEXT;
+obj RECORD;
+i	integer = 1;
+BEGIN
+
+	/* This function runs in the sql_drop event trigger.
+     *
+	 * When it runs, we know that all objects reported by the
+	 * pg_event_trigger_dropped_objects() function marked as "original" have
+	 * been mentioned in the DROP command, either directly by name or
+	 * indirectly by owner (DROP OWNED BY).  Since no objects that depend on
+	 * them can persist after that, we can replicate the effect of that by
+	 * executing an equivalent "DROP IF EXISTS object ... CASCADE".  CASCADE
+	 * lets the deletion work even in presence of objects that appear further
+	 * down in the return set of pg_event_trigger_dropped_objects, while IF
+	 * EXISTS let the deletion silently do nothing if the object was already
+	 * dropped because it was dependent on another object before it in the same
+	 * result set.
+     *
+     * (In general, it is impossible to reorder the result set in a way that
+     * would be completely free of dependency issues.)
+     */
+
+	FOR obj IN
+	SELECT object_type, address_names, address_args, object_identity
+	  FROM pg_event_trigger_dropped_objects()
+	 WHERE original
+	  LOOP
+
+		-- special case for default acls: ignore them.
+		IF obj.object_type = 'default acl' THEN
+			CONTINUE;
+		END IF;
+
+		/*
+		 * special cases for objects that are part of other objects: drop
+		 * each in a separate command.  Since we only deal with "original"
+		 * objects, these would not be reported in the complex case of
+		 * DROP OWNED.
+		 */
+		IF obj.object_type = 'table column' OR obj.object_type = 'foreign table column' THEN
+			fmt = format('ALTER TABLE %I.%I DROP COLUMN %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'composite type column' THEN
+			fmt = format('ALTER TYPE %I.%I DROP ATTRIBUTE %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TYPE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'table constraint' THEN
+			fmt = format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'domain constraint' THEN
+			fmt = format('ALTER DOMAIN %s DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_args[1]);
+		ELSIF obj.object_type = 'default value' THEN
+			fmt = format('ALTER TABLE %I.%I ALTER COLUMN %I DROP DEFAULT',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+		ELSIF obj.object_type = 'foreign-data wrapper' THEN
+			fmt = format('DROP FOREIGN DATA WRAPPER IF EXISTS %s CASCADE',
+				obj.object_identity);
+		ELSIF obj.object_type = 'user mapping' THEN
+			fmt = format('DROP USER MAPPING FOR %I SERVER %I',
+				obj.address_names[1], obj.address_args[1]);
+		ELSIF obj.object_type = 'operator of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP OPERATOR %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'function of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP FUNCTION %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSE
+			-- all other cases
+			fmt := format('DROP %s IF EXISTS %s CASCADE',
+				obj.object_type, obj.object_identity);
+		END IF;
+
+		IF fmt IS NULL THEN
+			CONTINUE;
+		END IF;
+
+		fmt := fmt || ' /* DROP support */';
+
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, lsn, ord, command)
+			 SELECT id, pg_stat_get_backend_start(id),
+			        pg_current_xlog_insert_location(), i, fmt
+			   FROM pg_stat_get_backend_idset() id
+			  WHERE pg_stat_get_backend_pid(id) = pg_backend_pid();
+		i := i + 1;
+	END LOOP;
+END;
+$fn$;
+
+CREATE OR REPLACE FUNCTION pg_deparse.output_commands() RETURNS SETOF text LANGUAGE PLPGSQL AS $$
+DECLARE
+        cmd text;
+        prev_id int = -1;
+        prev_start timestamptz = '-infinity';
+        sess_id int;
+        sess_start timestamptz;
+BEGIN
+   FOR cmd, sess_id, sess_start IN
+			   SELECT command, backend_id, backend_start
+                 FROM pg_deparse.deparse_test_commands
+			 ORDER BY lsn, ord
+   LOOP
+          IF (sess_id, sess_start) <> (prev_id, prev_start) THEN
+                prev_id := sess_id;
+                prev_start := sess_start;
+                RETURN NEXT '\c';
+          END IF;
+      RETURN NEXT cmd || ';' ;
+   END LOOP;
+END;
+$$;
+
+CREATE EVENT TRIGGER deparse_test_trg_sql_drop
+  ON sql_drop
+  EXECUTE PROCEDURE pg_deparse.deparse_test_sql_drop();
+
+CREATE EVENT TRIGGER deparse_test_trg_ddl_command_end
+  ON ddl_command_end
+  EXECUTE PROCEDURE pg_deparse.deparse_test_ddl_command_end();
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 68e7cb0..a4f0aee 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -16,6 +16,7 @@ CREATE FOREIGN DATA WRAPPER addr_fdw;
 CREATE SERVER addr_fserv FOREIGN DATA WRAPPER addr_fdw;
 CREATE TEXT SEARCH DICTIONARY addr_ts_dict (template=simple);
 CREATE TEXT SEARCH CONFIGURATION addr_ts_conf (copy=english);
+ALTER TEXT SEARCH CONFIGURATION addr_ts_conf ALTER MAPPING FOR asciiword WITH addr_ts_dict;
 CREATE TEXT SEARCH TEMPLATE addr_ts_temp (lexize=dsimple_lexize);
 CREATE TEXT SEARCH PARSER addr_ts_prs
     (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype);
@@ -77,6 +78,7 @@ BEGIN
 		('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
 		('text search parser'), ('text search dictionary'),
 		('text search template'), ('text search configuration'),
+		('text search configuration mapping'),
 		('policy'), ('user mapping'), ('default acl'), ('transform'),
 		('operator of access method'), ('function of access method')
 	LOOP
@@ -155,6 +157,7 @@ WITH objects (type, name, args) AS (VALUES
 				('text search dictionary', '{addr_ts_dict}', '{}'),
 				('text search template', '{addr_ts_temp}', '{}'),
 				('text search configuration', '{addr_ts_conf}', '{}'),
+				('text search configuration mapping', '{addr_ts_conf}', '{}'),
 				('role', '{regtest_addr_user}', '{}'),
 				-- database
 				-- tablespace
diff --git a/src/test/regress/sql/sanity_check.sql b/src/test/regress/sql/sanity_check.sql
index 0da838e..1d7a276 100644
--- a/src/test/regress/sql/sanity_check.sql
+++ b/src/test/regress/sql/sanity_check.sql
@@ -4,7 +4,8 @@ VACUUM;
 -- sanity check, if we don't have indices the test will take years to
 -- complete.  But skip TOAST relations (since they will have varying
 -- names depending on the current OID counter) as well as temp tables
--- of other backends (to avoid timing-dependent behavior).
+-- of other backends (to avoid timing-dependent behavior).  Also exclude
+-- the schema used for the deparse test, as it might not be there at all.
 --
 
 -- temporarily disable fancy output, so catalog changes create less diff noise
@@ -12,7 +13,7 @@ VACUUM;
 
 SELECT relname, relhasindex
    FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace
-   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_') IS NOT TRUE
+   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_' OR nspname ~ '^pg_deparse') IS NOT TRUE
    ORDER BY relname;
 
 -- restore normal output mode
ddl_deparse-v2.tar.gzapplication/x-gzip; name=ddl_deparse-v2.tar.gzDownload
#60Shulgin, Oleksandr
oleksandr.shulgin@zalando.de
In reply to: Shulgin, Oleksandr (#59)
Re: deparsing utility commands

On Thu, Aug 20, 2015 at 4:28 PM, Shulgin, Oleksandr <
oleksandr.shulgin@zalando.de> wrote:

Which gets deparsed as:

ALTER TABLE cwi_test DROP CONSTRAINT cwi_uniq_idx,
ADD CONSTRAINT cwi_replaced_pkey PRIMARY KEY
USING INDEX cwi_replaced_pkey;

The problem is that during constraint transformation, the index is being
renamed to match the constraint name and at the deparse stage the original
index name appears to be lost completely... I haven't figure out if
there's a way around unless we introduce a new field in IndexStmt
(originalName?) to cover exactly this corner case.

The updated versions of the core-support patch and the contrib modules are
attached.

Another quirk of ALTER TABLE is that due to multi-pass processing in
ATRewriteCatalogs, the same command might be collected a number of times.
For example, in src/test/regress/sql/inherit.sql:

alter table a alter column aa type integer using bit_length(aa);

the "alter column type" then appears 4 times in the deparsed output as
identical subcommands of a single ALTER TABLE command.

#61Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Shulgin, Oleksandr (#60)
Re: deparsing utility commands

Shulgin, Oleksandr wrote:

Another quirk of ALTER TABLE is that due to multi-pass processing in
ATRewriteCatalogs, the same command might be collected a number of times.
For example, in src/test/regress/sql/inherit.sql:

alter table a alter column aa type integer using bit_length(aa);

the "alter column type" then appears 4 times in the deparsed output as
identical subcommands of a single ALTER TABLE command.

Yeah, I had a hack somewhere in the collection code that if the relation
ID was different from what was specified, then the command was ignored.
I removed that before commit because it seemed possible that for some
cases you actually want the command reported separately for each child.

I think our best option in this case is to ignore commands that are
reported for different relations during JSON deparsing. Not sure how
easy that is.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#62Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Shulgin, Oleksandr (#59)
Re: deparsing utility commands

Shulgin, Oleksandr wrote:

A particularly nasty one is:

ERROR: index "cwi_replaced_pkey" does not exist

The test statement that's causing it is this one:

ALTER TABLE cwi_test DROP CONSTRAINT cwi_uniq_idx,
ADD CONSTRAINT cwi_replaced_pkey PRIMARY KEY
USING INDEX cwi_uniq2_idx;

Which gets deparsed as:

ALTER TABLE cwi_test DROP CONSTRAINT cwi_uniq_idx,
ADD CONSTRAINT cwi_replaced_pkey PRIMARY KEY
USING INDEX cwi_replaced_pkey;

The problem is that during constraint transformation, the index is being
renamed to match the constraint name and at the deparse stage the original
index name appears to be lost completely... I haven't figure out if
there's a way around unless we introduce a new field in IndexStmt
(originalName?) to cover exactly this corner case.

Oh :-( Hmm, modifying parse nodes should normally be considered last
resort, and at the same time identifying objects based on names rather
than OIDs is frowned upon. But I don't see any way to handle this
otherwise. I'm not sure what's the best solution for this one.

The updated versions of the core-support patch and the contrib modules are
attached.

Please try to split things up, or at least don't mix more than it
already is. For instance, the tsconfig mapping stuff should be its own
patch; we can probably make a case for pushing that on its own.

Also I see you added one caller of EventTriggerInhibitCommandCollection.
I don't like that crap at all and I think we would be better off if we
were able to remove it completely. Please see whether it's possible to
handle this case in some other way.

You seem to have squashed the patches? Please keep the split out.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#63Shulgin, Oleksandr
oleksandr.shulgin@zalando.de
In reply to: Alvaro Herrera (#62)
5 attachment(s)
Re: deparsing utility commands

On Thu, Aug 20, 2015 at 6:46 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Shulgin, Oleksandr wrote:

A particularly nasty one is:

ERROR: index "cwi_replaced_pkey" does not exist

The test statement that's causing it is this one:

ALTER TABLE cwi_test DROP CONSTRAINT cwi_uniq_idx,
ADD CONSTRAINT cwi_replaced_pkey PRIMARY KEY
USING INDEX cwi_uniq2_idx;

Which gets deparsed as:

ALTER TABLE cwi_test DROP CONSTRAINT cwi_uniq_idx,
ADD CONSTRAINT cwi_replaced_pkey PRIMARY KEY
USING INDEX cwi_replaced_pkey;

The problem is that during constraint transformation, the index is being
renamed to match the constraint name and at the deparse stage the

original

index name appears to be lost completely... I haven't figure out if
there's a way around unless we introduce a new field in IndexStmt
(originalName?) to cover exactly this corner case.

Oh :-( Hmm, modifying parse nodes should normally be considered last
resort, and at the same time identifying objects based on names rather
than OIDs is frowned upon. But I don't see any way to handle this
otherwise. I'm not sure what's the best solution for this one.

Yeah, me neither. At least one can see that in the Constraint struct we
have both indexname and conname, so adding conname to IndexStmt doesn't
seem to be really in disconnect with that (especially since we're
constructing an IndexStmt from a Constraint in this case). See patch #5.

Or maybe we should drop the isconstraint/deferrable/initdeferred fields and
put there an optional pointer to Constraint struct instead? Just an idea,
I didn't check how intrusive such a change might be.

The updated versions of the core-support patch and the contrib modules are

attached.

Please try to split things up, or at least don't mix more than it
already is. For instance, the tsconfig mapping stuff should be its own
patch; we can probably make a case for pushing that on its own.

Also I see you added one caller of EventTriggerInhibitCommandCollection.
I don't like that crap at all and I think we would be better off if we
were able to remove it completely. Please see whether it's possible to
handle this case in some other way.

Well, I've only added it to suppress the extra SET NOT NULL sub-commands
produced by the original ADD CONSTRAINT PRIMARY KEY command. The deparsed
command is still correct I believe, though a bit too verbose:

ALTER TABLE public.cwi_test
ALTER COLUMN a SET NOT NULL,
ALTER COLUMN b SET NOT NULL,
ADD CONSTRAINT cwi_uniq_idx PRIMARY KEY USING INDEX cwi_uniq_idx NOT
DEFERRABLE INITIALLY IMMEDIATE;

The only other place where this inhibiting is employed is around refresh
matview command and it prevents extra plumbing output like this:

CREATE TEMPORARY TABLE pg_temp.pg_temp_32070_2 WITH (oids=OFF) AS
SELECT mv.ctid AS tid, newdata.*::pg_temp_32070 AS newdata FROM (...);

Now, what I think would make the most sense is still capture all the
intermediary low-level commands, but put them hierarchically under the
command invoking them, so that interested clients can still inspect them,
but for the purpose of reconstructing of captured DDL events one needs to
replay only the top-level commands.

From what I can see, this can be achieved by removing check on
isCompleteQuery in ProcessUtilitySlow and run all EventTrigger*()
regardless, which will stack the captured commands as they are being
captured.

The question when arises is how to reflect the command hierarchy in the
output of pg_event_trigger_ddl_commands(). Maybe simply assigning an
integer id and referencing it in an additional parent_command_id column
would do the trick.

Another quirk of ALTER TABLE is that due to multi-pass processing in

ATRewriteCatalogs, the same command might be collected a number of times.
For example, in src/test/regress/sql/inherit.sql:

alter table a alter column aa type integer using bit_length(aa);

the "alter column type" then appears 4 times in the deparsed output as
identical subcommands of a single ALTER TABLE command.

Yeah, I had a hack somewhere in the collection code that if the relation
ID was different from what was specified, then the command was ignored.
I removed that before commit because it seemed possible that for some
cases you actually want the command reported separately for each child.

I think our best option in this case is to ignore commands that are
reported for different relations during JSON deparsing. Not sure how
easy that is.

Hm, but there is no different relation in this case, it's just that we get
the "ALTER COLUMN ... SET DATA TYPE" sub-command gets repeated multiple
times:

ALTER TABLE public.a
ALTER COLUMN aa SET DATA TYPE pg_catalog.int4 USING
pg_catalog.bit_length(aa),
ALTER COLUMN aa SET DATA TYPE pg_catalog.int4 USING
pg_catalog.bit_length(aa),
ALTER COLUMN aa SET DATA TYPE pg_catalog.int4 USING
pg_catalog.bit_length(aa),
ALTER COLUMN aa SET DATA TYPE pg_catalog.int4 USING
pg_catalog.bit_length(aa),
ALTER COLUMN aa SET DATA TYPE pg_catalog.int4 USING
pg_catalog.bit_length(aa);

Oh, five times actually...

You seem to have squashed the patches? Please keep the split out.

Well, if that makes the review process easier :-)

--
Alex

Attachments:

0005-Add-optional-constraint-name-to-field-IndexStmt.patchtext/x-patch; charset=US-ASCII; name=0005-Add-optional-constraint-name-to-field-IndexStmt.patchDownload
From 6409648f3c3f9b54b20b193fbeb963281b16c895 Mon Sep 17 00:00:00 2001
From: Oleksandr Shulgin <oleksandr.shulgin@zalando.de>
Date: Fri, 21 Aug 2015 11:54:19 +0200
Subject: [PATCH 5/5] Add optional constraint name to field IndexStmt

And keep the original index name field intact when renaming.

This appears to be the only way to trace back the original index name
before it was renamed to match the constraint name.  We need this
information in the ddl_deparse module.
---
 src/backend/commands/tablecmds.c   |  4 ++--
 src/backend/nodes/copyfuncs.c      |  1 +
 src/backend/nodes/equalfuncs.c     |  1 +
 src/backend/nodes/outfuncs.c       |  1 +
 src/backend/parser/parse_utilcmd.c | 16 ++++++++++++++--
 src/include/nodes/parsenodes.h     |  1 +
 6 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 202b5fd..50fffb6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5935,7 +5935,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 
 	indexRel = index_open(index_oid, AccessShareLock);
 
-	indexName = pstrdup(RelationGetRelationName(indexRel));
+	indexName = stmt->idxname;
 
 	indexInfo = BuildIndexInfo(indexRel);
 
@@ -5950,7 +5950,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	 * explicitly gives some other name for the constraint, rename the index
 	 * to match.
 	 */
-	constraintName = stmt->idxname;
+	constraintName = stmt->conname;
 	if (constraintName == NULL)
 		constraintName = indexName;
 	else if (strcmp(constraintName, indexName) != 0)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c8425d..a2db234 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3092,6 +3092,7 @@ _copyIndexStmt(const IndexStmt *from)
 	IndexStmt  *newnode = makeNode(IndexStmt);
 
 	COPY_STRING_FIELD(idxname);
+	COPY_STRING_FIELD(conname);
 	COPY_NODE_FIELD(relation);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_STRING_FIELD(tableSpace);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1d6c43c..04a0486 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1234,6 +1234,7 @@ static bool
 _equalIndexStmt(const IndexStmt *a, const IndexStmt *b)
 {
 	COMPARE_STRING_FIELD(idxname);
+	COMPARE_STRING_FIELD(conname);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_STRING_FIELD(tableSpace);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index a878498..4909031 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2140,6 +2140,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
 	WRITE_NODE_TYPE("INDEXSTMT");
 
 	WRITE_STRING_FIELD(idxname);
+	WRITE_STRING_FIELD(conname);
 	WRITE_NODE_FIELD(relation);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_STRING_FIELD(tableSpace);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 16d40c7..c1e5bce 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1489,6 +1489,10 @@ transformIndexConstraints(CreateStmtContext *cxt)
 				 */
 				if (priorindex->idxname == NULL)
 					priorindex->idxname = index->idxname;
+
+				if (priorindex->conname == NULL)
+					priorindex->conname = index->conname;
+
 				keep = false;
 				break;
 			}
@@ -1533,10 +1537,17 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 	index->deferrable = constraint->deferrable;
 	index->initdeferred = constraint->initdeferred;
 
+	index->idxname = NULL; /* by default, DefineIndex will choose name */
+
 	if (constraint->conname != NULL)
-		index->idxname = pstrdup(constraint->conname);
+	{
+		index->conname = pstrdup(constraint->conname);
+
+		if (constraint->indexname == NULL)
+			index->idxname = pstrdup(constraint->conname);
+	}
 	else
-		index->idxname = NULL;	/* DefineIndex will choose name */
+		index->conname = NULL;
 
 	index->relation = cxt->relation;
 	index->accessMethod = constraint->access_method ? constraint->access_method : DEFAULT_INDEX_TYPE;
@@ -1717,6 +1728,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 		relation_close(index_rel, NoLock);
 
 		index->indexOid = index_oid;
+		index->idxname = pstrdup(index_name);
 	}
 
 	/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a151412..379f3f6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2411,6 +2411,7 @@ typedef struct IndexStmt
 {
 	NodeTag		type;
 	char	   *idxname;		/* name of new index, or NULL for default */
+	char	   *conname;		/* corresponding constraint name, or NULL */
 	RangeVar   *relation;		/* relation to build index on */
 	char	   *accessMethod;	/* name of access method (eg. btree) */
 	char	   *tableSpace;		/* tablespace, or NULL for default */
-- 
2.1.4

0004-Add-TSConfigMap-support-to-DDL-deparse.patchtext/x-patch; charset=US-ASCII; name=0004-Add-TSConfigMap-support-to-DDL-deparse.patchDownload
From af1bd8acd50ccf82298460145f3c575ea2c63925 Mon Sep 17 00:00:00 2001
From: Oleksandr Shulgin <oleksandr.shulgin@zalando.de>
Date: Thu, 20 Aug 2015 09:48:13 +0200
Subject: [PATCH 4/5] Add TSConfigMap support to DDL deparse.

---
 src/backend/catalog/dependency.c             |  5 ++
 src/backend/catalog/objectaddress.c          | 31 ++++++++-
 src/backend/tcop/utility.c                   |  2 +
 src/include/catalog/catversion.h             |  2 +-
 src/include/catalog/dependency.h             |  1 +
 src/include/nodes/parsenodes.h               |  1 +
 src/test/regress/expected/object_address.out | 98 +++++++++++++++-------------
 src/test/regress/sql/object_address.sql      |  3 +
 8 files changed, 97 insertions(+), 46 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 90b1cd8..08a3ea6 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
 #include "catalog/pg_transform.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"
@@ -149,6 +150,7 @@ static const Oid object_classes[] = {
 	TSDictionaryRelationId,		/* OCLASS_TSDICT */
 	TSTemplateRelationId,		/* OCLASS_TSTEMPLATE */
 	TSConfigRelationId,			/* OCLASS_TSCONFIG */
+	TSConfigMapRelationId,		/* OCLASS_TSCONFIG_MAP */
 	AuthIdRelationId,			/* OCLASS_ROLE */
 	DatabaseRelationId,			/* OCLASS_DATABASE */
 	TableSpaceRelationId,		/* OCLASS_TBLSPACE */
@@ -2382,6 +2384,9 @@ getObjectClass(const ObjectAddress *object)
 		case TSConfigRelationId:
 			return OCLASS_TSCONFIG;
 
+		case TSConfigMapRelationId:
+			return OCLASS_TSCONFIG_MAP;
+
 		case AuthIdRelationId:
 			return OCLASS_ROLE;
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 052aab1..e01caa1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_transform.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"
@@ -390,6 +391,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		true
 	},
 	{
+		TSConfigMapRelationId,
+		TSConfigMapIndexId,
+		TSCONFIGMAP,
+		TSCONFIGNAMENSP,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		ACL_KIND_TSCONFIGURATION,
+		true
+	},
+	{
 		TSDictionaryRelationId,
 		TSDictionaryOidIndexId,
 		TSDICTOID,
@@ -595,6 +608,10 @@ static const struct object_type_map
 	{
 		"text search configuration", OBJECT_TSCONFIGURATION
 	},
+	/* OCLASS_TSCONFIG_MAP */
+	{
+		"text search configuration mapping", OBJECT_TSCONFIG_MAPPING
+	},
 	/* OCLASS_ROLE */
 	{
 		"role", OBJECT_ROLE
@@ -903,6 +920,11 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 				address.objectId = get_ts_config_oid(objname, missing_ok);
 				address.objectSubId = 0;
 				break;
+			case OBJECT_TSCONFIG_MAPPING:
+				address.classId = TSConfigMapRelationId;
+				address.objectId = get_ts_config_oid(objname, missing_ok);
+				address.objectSubId = 0;
+				break;
 			case OBJECT_USER_MAPPING:
 				address = get_object_address_usermapping(objname, objargs,
 														 missing_ok);
@@ -2150,6 +2172,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 							   NameListToString(objname));
 			break;
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_TSCONFIG_MAPPING:
 			if (!pg_ts_config_ownercheck(address.objectId, roleid))
 				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
 							   NameListToString(objname));
@@ -2237,7 +2260,7 @@ get_object_namespace(const ObjectAddress *address)
 int
 read_objtype_from_string(const char *objtype)
 {
-	ObjectType	type;
+	ObjectType	type = -1;
 	int			i;
 
 	for (i = 0; i < lengthof(ObjectTypeMap); i++)
@@ -2905,6 +2928,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_TSCONFIG:
+		case OCLASS_TSCONFIG_MAP:
 			{
 				HeapTuple	tup;
 
@@ -3564,6 +3588,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "text search configuration");
 			break;
 
+		case OCLASS_TSCONFIG_MAP:
+			appendStringInfoString(&buffer, "text search configuration mapping");
+			break;
+
 		case OCLASS_ROLE:
 			appendStringInfoString(&buffer, "role");
 			break;
@@ -4296,6 +4324,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_TSCONFIG:
+		case OCLASS_TSCONFIG_MAP:
 			{
 				HeapTuple	tup;
 				Form_pg_ts_config formCfg;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index e81bbc6..b848ec6 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1454,6 +1454,8 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_AlterTSConfigurationStmt:
 				address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				/* commands are stashed in Make/DropConfigurationMapping */
+				commandCollected = true;
 				break;
 
 			case T_AlterTableMoveAllStmt:
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index b58fe46..2be1aba 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201508111
+#define CATALOG_VERSION_NO	201508211
 
 #endif
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fbcf904..b400095 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -143,6 +143,7 @@ typedef enum ObjectClass
 	OCLASS_TSDICT,				/* pg_ts_dict */
 	OCLASS_TSTEMPLATE,			/* pg_ts_template */
 	OCLASS_TSCONFIG,			/* pg_ts_config */
+	OCLASS_TSCONFIG_MAP,		/* pg_ts_config_map */
 	OCLASS_ROLE,				/* pg_authid */
 	OCLASS_DATABASE,			/* pg_database */
 	OCLASS_TBLSPACE,			/* pg_tablespace */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 151c93a..a151412 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1408,6 +1408,7 @@ typedef enum ObjectType
 	OBJECT_TRANSFORM,
 	OBJECT_TRIGGER,
 	OBJECT_TSCONFIGURATION,
+	OBJECT_TSCONFIG_MAPPING,
 	OBJECT_TSDICTIONARY,
 	OBJECT_TSPARSER,
 	OBJECT_TSTEMPLATE,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 034c0b1..0da2a34 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -12,6 +12,7 @@ CREATE FOREIGN DATA WRAPPER addr_fdw;
 CREATE SERVER addr_fserv FOREIGN DATA WRAPPER addr_fdw;
 CREATE TEXT SEARCH DICTIONARY addr_ts_dict (template=simple);
 CREATE TEXT SEARCH CONFIGURATION addr_ts_conf (copy=english);
+ALTER TEXT SEARCH CONFIGURATION addr_ts_conf ALTER MAPPING FOR asciiword WITH addr_ts_dict;
 CREATE TEXT SEARCH TEMPLATE addr_ts_temp (lexize=dsimple_lexize);
 CREATE TEXT SEARCH PARSER addr_ts_prs
     (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype);
@@ -79,6 +80,7 @@ BEGIN
 		('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
 		('text search parser'), ('text search dictionary'),
 		('text search template'), ('text search configuration'),
+		('text search configuration mapping'),
 		('policy'), ('user mapping'), ('default acl'), ('transform'),
 		('operator of access method'), ('function of access method')
 	LOOP
@@ -246,6 +248,12 @@ WARNING:  error for text search configuration,{addr_nsp,zwei},{}: text search co
 WARNING:  error for text search configuration,{addr_nsp,zwei},{integer}: text search configuration "addr_nsp.zwei" does not exist
 WARNING:  error for text search configuration,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
 WARNING:  error for text search configuration,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for text search configuration mapping,{eins},{}: text search configuration "eins" does not exist
+WARNING:  error for text search configuration mapping,{eins},{integer}: text search configuration "eins" does not exist
+WARNING:  error for text search configuration mapping,{addr_nsp,zwei},{}: text search configuration "addr_nsp.zwei" does not exist
+WARNING:  error for text search configuration mapping,{addr_nsp,zwei},{integer}: text search configuration "addr_nsp.zwei" does not exist
+WARNING:  error for text search configuration mapping,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for text search configuration mapping,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
 WARNING:  error for policy,{eins},{}: must specify relation and object name
 WARNING:  error for policy,{eins},{integer}: must specify relation and object name
 WARNING:  error for policy,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist
@@ -362,6 +370,7 @@ WITH objects (type, name, args) AS (VALUES
 				('text search dictionary', '{addr_ts_dict}', '{}'),
 				('text search template', '{addr_ts_temp}', '{}'),
 				('text search configuration', '{addr_ts_conf}', '{}'),
+				('text search configuration mapping', '{addr_ts_conf}', '{}'),
 				('role', '{regtest_addr_user}', '{}'),
 				-- database
 				-- tablespace
@@ -383,50 +392,51 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*,
 			pg_identify_object_as_address(classid, objid, subobjid) ioa(typ,nms,args),
 			pg_get_object_address(typ, nms, ioa.args) as addr2
 	ORDER BY addr1.classid, addr1.objid, addr1.subobjid;
-           type            |   schema   |       name        |                               identity                               | ?column? 
----------------------------+------------+-------------------+----------------------------------------------------------------------+----------
- default acl               |            |                   | for role regtest_addr_user in schema public on tables                | t
- default acl               |            |                   | for role regtest_addr_user on tables                                 | t
- type                      | pg_catalog | _int4             | integer[]                                                            | t
- type                      | addr_nsp   | gencomptype       | addr_nsp.gencomptype                                                 | t
- type                      | addr_nsp   | genenum           | addr_nsp.genenum                                                     | t
- type                      | addr_nsp   | gendomain         | addr_nsp.gendomain                                                   | t
- function                  | pg_catalog |                   | pg_catalog.pg_identify_object(pg_catalog.oid,pg_catalog.oid,integer) | t
- aggregate                 | addr_nsp   |                   | addr_nsp.genaggr(integer)                                            | t
- sequence                  | addr_nsp   | gentable_a_seq    | addr_nsp.gentable_a_seq                                              | t
- table                     | addr_nsp   | gentable          | addr_nsp.gentable                                                    | t
- table column              | addr_nsp   | gentable          | addr_nsp.gentable.b                                                  | t
- index                     | addr_nsp   | gentable_pkey     | addr_nsp.gentable_pkey                                               | t
- view                      | addr_nsp   | genview           | addr_nsp.genview                                                     | t
- materialized view         | addr_nsp   | genmatview        | addr_nsp.genmatview                                                  | t
- foreign table             | addr_nsp   | genftable         | addr_nsp.genftable                                                   | t
- foreign table column      | addr_nsp   | genftable         | addr_nsp.genftable.a                                                 | t
- role                      |            | regtest_addr_user | regtest_addr_user                                                    | t
- server                    |            | addr_fserv        | addr_fserv                                                           | t
- user mapping              |            |                   | regtest_addr_user on server integer                                  | t
- foreign-data wrapper      |            | addr_fdw          | addr_fdw                                                             | t
- operator of access method |            |                   | operator 1 (integer, integer) of pg_catalog.integer_ops USING btree  | t
- function of access method |            |                   | function 2 (integer, integer) of pg_catalog.integer_ops USING btree  | t
- default value             |            |                   | for addr_nsp.gentable.b                                              | t
- cast                      |            |                   | (bigint AS integer)                                                  | t
- table constraint          | addr_nsp   |                   | a_chk on addr_nsp.gentable                                           | t
- domain constraint         | addr_nsp   |                   | domconstr on addr_nsp.gendomain                                      | t
- conversion                | pg_catalog | ascii_to_mic      | pg_catalog.ascii_to_mic                                              | t
- language                  |            | plpgsql           | plpgsql                                                              | t
- schema                    |            | addr_nsp          | addr_nsp                                                             | t
- operator class            | pg_catalog | int4_ops          | pg_catalog.int4_ops USING btree                                      | t
- operator                  | pg_catalog |                   | pg_catalog.+(integer,integer)                                        | t
- rule                      |            |                   | "_RETURN" on addr_nsp.genview                                        | t
- trigger                   |            |                   | t on addr_nsp.gentable                                               | t
- operator family           | pg_catalog | integer_ops       | pg_catalog.integer_ops USING btree                                   | t
- policy                    |            |                   | genpol on addr_nsp.gentable                                          | t
- collation                 | pg_catalog | "default"         | pg_catalog."default"                                                 | t
- transform                 |            |                   | for integer on language sql                                          | t
- text search dictionary    | addr_nsp   | addr_ts_dict      | addr_nsp.addr_ts_dict                                                | t
- text search parser        | addr_nsp   | addr_ts_prs       | addr_nsp.addr_ts_prs                                                 | t
- text search configuration | addr_nsp   | addr_ts_conf      | addr_nsp.addr_ts_conf                                                | t
- text search template      | addr_nsp   | addr_ts_temp      | addr_nsp.addr_ts_temp                                                | t
-(41 rows)
+               type                |   schema   |       name        |                               identity                               | ?column? 
+-----------------------------------+------------+-------------------+----------------------------------------------------------------------+----------
+ default acl                       |            |                   | for role regtest_addr_user in schema public on tables                | t
+ default acl                       |            |                   | for role regtest_addr_user on tables                                 | t
+ type                              | pg_catalog | _int4             | integer[]                                                            | t
+ type                              | addr_nsp   | gencomptype       | addr_nsp.gencomptype                                                 | t
+ type                              | addr_nsp   | genenum           | addr_nsp.genenum                                                     | t
+ type                              | addr_nsp   | gendomain         | addr_nsp.gendomain                                                   | t
+ function                          | pg_catalog |                   | pg_catalog.pg_identify_object(pg_catalog.oid,pg_catalog.oid,integer) | t
+ aggregate                         | addr_nsp   |                   | addr_nsp.genaggr(integer)                                            | t
+ sequence                          | addr_nsp   | gentable_a_seq    | addr_nsp.gentable_a_seq                                              | t
+ table                             | addr_nsp   | gentable          | addr_nsp.gentable                                                    | t
+ table column                      | addr_nsp   | gentable          | addr_nsp.gentable.b                                                  | t
+ index                             | addr_nsp   | gentable_pkey     | addr_nsp.gentable_pkey                                               | t
+ view                              | addr_nsp   | genview           | addr_nsp.genview                                                     | t
+ materialized view                 | addr_nsp   | genmatview        | addr_nsp.genmatview                                                  | t
+ foreign table                     | addr_nsp   | genftable         | addr_nsp.genftable                                                   | t
+ foreign table column              | addr_nsp   | genftable         | addr_nsp.genftable.a                                                 | t
+ role                              |            | regtest_addr_user | regtest_addr_user                                                    | t
+ server                            |            | addr_fserv        | addr_fserv                                                           | t
+ user mapping                      |            |                   | regtest_addr_user on server integer                                  | t
+ foreign-data wrapper              |            | addr_fdw          | addr_fdw                                                             | t
+ operator of access method         |            |                   | operator 1 (integer, integer) of pg_catalog.integer_ops USING btree  | t
+ function of access method         |            |                   | function 2 (integer, integer) of pg_catalog.integer_ops USING btree  | t
+ default value                     |            |                   | for addr_nsp.gentable.b                                              | t
+ cast                              |            |                   | (bigint AS integer)                                                  | t
+ table constraint                  | addr_nsp   |                   | a_chk on addr_nsp.gentable                                           | t
+ domain constraint                 | addr_nsp   |                   | domconstr on addr_nsp.gendomain                                      | t
+ conversion                        | pg_catalog | ascii_to_mic      | pg_catalog.ascii_to_mic                                              | t
+ language                          |            | plpgsql           | plpgsql                                                              | t
+ schema                            |            | addr_nsp          | addr_nsp                                                             | t
+ operator class                    | pg_catalog | int4_ops          | pg_catalog.int4_ops USING btree                                      | t
+ operator                          | pg_catalog |                   | pg_catalog.+(integer,integer)                                        | t
+ rule                              |            |                   | "_RETURN" on addr_nsp.genview                                        | t
+ trigger                           |            |                   | t on addr_nsp.gentable                                               | t
+ operator family                   | pg_catalog | integer_ops       | pg_catalog.integer_ops USING btree                                   | t
+ policy                            |            |                   | genpol on addr_nsp.gentable                                          | t
+ collation                         | pg_catalog | "default"         | pg_catalog."default"                                                 | t
+ transform                         |            |                   | for integer on language sql                                          | t
+ text search dictionary            | addr_nsp   | addr_ts_dict      | addr_nsp.addr_ts_dict                                                | t
+ text search parser                | addr_nsp   | addr_ts_prs       | addr_nsp.addr_ts_prs                                                 | t
+ text search configuration         | addr_nsp   | addr_ts_conf      | addr_nsp.addr_ts_conf                                                | t
+ text search configuration mapping |            |                   | addr_nsp.addr_ts_conf                                                | t
+ text search template              | addr_nsp   | addr_ts_temp      | addr_nsp.addr_ts_temp                                                | t
+(42 rows)
 
 ---
 --- Cleanup resources
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 68e7cb0..a4f0aee 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -16,6 +16,7 @@ CREATE FOREIGN DATA WRAPPER addr_fdw;
 CREATE SERVER addr_fserv FOREIGN DATA WRAPPER addr_fdw;
 CREATE TEXT SEARCH DICTIONARY addr_ts_dict (template=simple);
 CREATE TEXT SEARCH CONFIGURATION addr_ts_conf (copy=english);
+ALTER TEXT SEARCH CONFIGURATION addr_ts_conf ALTER MAPPING FOR asciiword WITH addr_ts_dict;
 CREATE TEXT SEARCH TEMPLATE addr_ts_temp (lexize=dsimple_lexize);
 CREATE TEXT SEARCH PARSER addr_ts_prs
     (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype);
@@ -77,6 +78,7 @@ BEGIN
 		('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
 		('text search parser'), ('text search dictionary'),
 		('text search template'), ('text search configuration'),
+		('text search configuration mapping'),
 		('policy'), ('user mapping'), ('default acl'), ('transform'),
 		('operator of access method'), ('function of access method')
 	LOOP
@@ -155,6 +157,7 @@ WITH objects (type, name, args) AS (VALUES
 				('text search dictionary', '{addr_ts_dict}', '{}'),
 				('text search template', '{addr_ts_temp}', '{}'),
 				('text search configuration', '{addr_ts_conf}', '{}'),
+				('text search configuration mapping', '{addr_ts_conf}', '{}'),
 				('role', '{regtest_addr_user}', '{}'),
 				-- database
 				-- tablespace
-- 
2.1.4

0003-Update-pg_regress-support-for-ddl-deparse-tests.patchtext/x-patch; charset=US-ASCII; name=0003-Update-pg_regress-support-for-ddl-deparse-tests.patchDownload
From 0b7bd6f97c1d3695b50fe8f6c0913c38ca90f2bb Mon Sep 17 00:00:00 2001
From: Oleksandr Shulgin <oleksandr.shulgin@zalando.de>
Date: Fri, 31 Jul 2015 15:31:27 +0200
Subject: [PATCH 3/5] Update pg_regress support for ddl deparse tests

---
 src/test/regress/GNUmakefile                       |  22 +++
 src/test/regress/expected/alter_table.out          |   4 +-
 .../regress/expected/create_function_ddl_demo.out  |   4 +
 src/test/regress/expected/deparse_init.out         | 178 ++++++++++++++++++++
 src/test/regress/expected/sanity_check.out         |   5 +-
 src/test/regress/input/deparse_test.source         |  57 +++++++
 src/test/regress/output/deparse_test.source        |  53 ++++++
 src/test/regress/pg_regress.c                      |  39 ++++-
 src/test/regress/pg_regress.h                      |   1 +
 src/test/regress/pg_regress_main.c                 |   1 +
 src/test/regress/sql/alter_table.sql               |   4 +-
 src/test/regress/sql/create_function_ddl_demo.sql  |   4 +
 src/test/regress/sql/deparse_init.sql              | 182 +++++++++++++++++++++
 src/test/regress/sql/sanity_check.sql              |   5 +-
 14 files changed, 551 insertions(+), 8 deletions(-)
 create mode 100644 src/test/regress/expected/create_function_ddl_demo.out
 create mode 100644 src/test/regress/expected/deparse_init.out
 create mode 100644 src/test/regress/input/deparse_test.source
 create mode 100644 src/test/regress/output/deparse_test.source
 create mode 100644 src/test/regress/sql/create_function_ddl_demo.sql
 create mode 100644 src/test/regress/sql/deparse_init.sql

diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile
index 4d6849e..1bcad70 100644
--- a/src/test/regress/GNUmakefile
+++ b/src/test/regress/GNUmakefile
@@ -123,6 +123,16 @@ tablespace-setup:
 	rm -rf ./testtablespace
 	mkdir ./testtablespace
 
+# DDL-deparse setup
+
+.PHONY: generate-files
+generate-files:
+	$(top_builddir)/src/test/regress/pg_regress --generate-files-only --inputdir=$(srcdir)/input
+
+ddl_deparse_schedule: serial_schedule
+	echo "test: deparse_init" > $@
+	grep -v tablespace $(srcdir)/serial_schedule >> $@
+	echo "test: deparse_test" >> $@
 
 ##
 ## Run tests
@@ -148,6 +158,18 @@ installcheck-tests: all tablespace-setup
 standbycheck: all
 	$(pg_regress_installcheck) $(REGRESS_OPTS) --schedule=$(srcdir)/standby_schedule --use-existing
 
+deparsecheck: REGRESS_OPTS += --keep-instance --load-extension=ddl_deparse
+deparsecheck: all temp-install generate-files ddl_deparse_schedule
+	$(MAKE) -C $(top_builddir)/contrib/ddl_deparse DESTDIR=$(abs_top_builddir)/tmp_install install
+	$(pg_regress_check) $(REGRESS_OPTS) --schedule=ddl_deparse_schedule
+	grep ERROR results/deparse_dump.out > results/deparse_dump.errors
+	$(abs_top_builddir)/tmp_install/$(bindir)/pg_ctl -w start -D ./tmp_check/data -l ./tmp_check/log/secondary.log -o "-p 49152"
+	$(abs_top_builddir)/tmp_install/$(bindir)/pg_dump -p 49152 -d regression_deparse -s -f results/deparse.dump
+	$(abs_top_builddir)/tmp_install/$(bindir)/pg_dump -p 49152 -d regression -s -f results/regression.dump
+	$(abs_top_builddir)/tmp_install/$(bindir)/pg_ctl -D ./tmp_check/data stop
+	diff -c results/deparse.dump results/regression.dump
+	diff -c results/deparse_dump.errors $(srcdir)/expected/deparse_dump.errors
+
 # old interfaces follow...
 
 runcheck: check
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 28422ea..24d2675 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1862,7 +1862,9 @@ where virtualtransaction = (
         from pg_locks
         where transactionid = txid_current()::integer)
 and locktype = 'relation'
-and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
+and relnamespace NOT IN (
+	select oid from pg_namespace
+	where nspname IN ('pg_catalog', 'pg_deparse'))
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
diff --git a/src/test/regress/expected/create_function_ddl_demo.out b/src/test/regress/expected/create_function_ddl_demo.out
new file mode 100644
index 0000000..ff8a1d9
--- /dev/null
+++ b/src/test/regress/expected/create_function_ddl_demo.out
@@ -0,0 +1,4 @@
+CREATE FUNCTION check_foreign_key ()
+	RETURNS trigger
+	AS '/space/sda1/ibarwick/2ndquadrant_bdr/src/test/regress/refint.so'
+	LANGUAGE C;
diff --git a/src/test/regress/expected/deparse_init.out b/src/test/regress/expected/deparse_init.out
new file mode 100644
index 0000000..7c8ed42
--- /dev/null
+++ b/src/test/regress/expected/deparse_init.out
@@ -0,0 +1,178 @@
+--
+-- DEPARSE_INIT
+--
+CREATE SCHEMA deparse;
+UPDATE pg_namespace SET nspname = 'pg_deparse' WHERE nspname = 'deparse';
+CREATE UNLOGGED TABLE pg_deparse.deparse_test_commands (
+  backend_id int,
+  backend_start timestamptz,
+  lsn pg_lsn,
+  ord integer,
+  command TEXT
+);
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_ddl_command_end()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+BEGIN
+	BEGIN
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, command, ord, lsn)
+		SELECT id, pg_stat_get_backend_start(id),
+		     public.ddl_deparse_expand_command(public.ddl_deparse_to_json(command)),
+                     ordinality, lsn
+		FROM pg_event_trigger_ddl_commands() WITH ORDINALITY,
+		pg_current_xlog_insert_location() lsn,
+		pg_stat_get_backend_idset() id
+		 WHERE pg_stat_get_backend_pid(id) = pg_backend_pid() AND
+		NOT command_tag = 'CREATE TABLE AS EXECUTE';
+	EXCEPTION WHEN OTHERS THEN 
+			RAISE WARNING 'state: % errm: %', sqlstate, sqlerrm;
+	END;
+END;
+$fn$;
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_sql_drop()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+DECLARE
+fmt	TEXT;
+obj RECORD;
+i	integer = 1;
+BEGIN
+
+	/* This function runs in the sql_drop event trigger.
+     *
+	 * When it runs, we know that all objects reported by the
+	 * pg_event_trigger_dropped_objects() function marked as "original" have
+	 * been mentioned in the DROP command, either directly by name or
+	 * indirectly by owner (DROP OWNED BY).  Since no objects that depend on
+	 * them can persist after that, we can replicate the effect of that by
+	 * executing an equivalent "DROP IF EXISTS object ... CASCADE".  CASCADE
+	 * lets the deletion work even in presence of objects that appear further
+	 * down in the return set of pg_event_trigger_dropped_objects, while IF
+	 * EXISTS let the deletion silently do nothing if the object was already
+	 * dropped because it was dependent on another object before it in the same
+	 * result set.
+     *
+     * (In general, it is impossible to reorder the result set in a way that
+     * would be completely free of dependency issues.)
+     */
+
+	FOR obj IN
+	SELECT object_type, address_names, address_args, object_identity
+	  FROM pg_event_trigger_dropped_objects()
+	 WHERE original
+	  LOOP
+
+		-- special case for default acls: ignore them.
+		IF obj.object_type = 'default acl' THEN
+			CONTINUE;
+		END IF;
+
+		/*
+		 * special cases for objects that are part of other objects: drop
+		 * each in a separate command.  Since we only deal with "original"
+		 * objects, these would not be reported in the complex case of
+		 * DROP OWNED.
+		 */
+		IF obj.object_type = 'table column' OR obj.object_type = 'foreign table column' THEN
+			fmt = format('ALTER TABLE %I.%I DROP COLUMN %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'composite type column' THEN
+			fmt = format('ALTER TYPE %I.%I DROP ATTRIBUTE %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TYPE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'table constraint' THEN
+			fmt = format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'domain constraint' THEN
+			fmt = format('ALTER DOMAIN %s DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_args[1]);
+		ELSIF obj.object_type = 'default value' THEN
+			fmt = format('ALTER TABLE %I.%I ALTER COLUMN %I DROP DEFAULT',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+		ELSIF obj.object_type = 'foreign-data wrapper' THEN
+			fmt = format('DROP FOREIGN DATA WRAPPER IF EXISTS %s CASCADE',
+				obj.object_identity);
+		ELSIF obj.object_type = 'user mapping' THEN
+			fmt = format('DROP USER MAPPING FOR %I SERVER %I',
+				obj.address_names[1], obj.address_args[1]);
+		ELSIF obj.object_type = 'operator of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP OPERATOR %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'function of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP FUNCTION %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSE
+			-- all other cases
+			fmt := format('DROP %s IF EXISTS %s CASCADE',
+				obj.object_type, obj.object_identity);
+		END IF;
+
+		IF fmt IS NULL THEN
+			CONTINUE;
+		END IF;
+
+		fmt := fmt || ' /* DROP support */';
+
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, lsn, ord, command)
+			 SELECT id, pg_stat_get_backend_start(id),
+			        pg_current_xlog_insert_location(), i, fmt
+			   FROM pg_stat_get_backend_idset() id
+			  WHERE pg_stat_get_backend_pid(id) = pg_backend_pid();
+		i := i + 1;
+	END LOOP;
+END;
+$fn$;
+CREATE OR REPLACE FUNCTION pg_deparse.output_commands() RETURNS SETOF text LANGUAGE PLPGSQL AS $$
+DECLARE
+        cmd text;
+        prev_id int = -1;
+        prev_start timestamptz = '-infinity';
+        sess_id int;
+        sess_start timestamptz;
+BEGIN
+   FOR cmd, sess_id, sess_start IN
+			   SELECT command, backend_id, backend_start
+                 FROM pg_deparse.deparse_test_commands
+			 ORDER BY lsn, ord
+   LOOP
+          IF (sess_id, sess_start) <> (prev_id, prev_start) THEN
+                prev_id := sess_id;
+                prev_start := sess_start;
+                RETURN NEXT '\c';
+          END IF;
+      RETURN NEXT cmd || ';' ;
+   END LOOP;
+END;
+$$;
+CREATE EVENT TRIGGER deparse_test_trg_sql_drop
+  ON sql_drop
+  EXECUTE PROCEDURE pg_deparse.deparse_test_sql_drop();
+CREATE EVENT TRIGGER deparse_test_trg_ddl_command_end
+  ON ddl_command_end
+  EXECUTE PROCEDURE pg_deparse.deparse_test_ddl_command_end();
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..ab9af544 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -3,13 +3,14 @@ VACUUM;
 -- sanity check, if we don't have indices the test will take years to
 -- complete.  But skip TOAST relations (since they will have varying
 -- names depending on the current OID counter) as well as temp tables
--- of other backends (to avoid timing-dependent behavior).
+-- of other backends (to avoid timing-dependent behavior).  Also exclude
+-- the schema used for the deparse test, as it might not be there at all.
 --
 -- temporarily disable fancy output, so catalog changes create less diff noise
 \a\t
 SELECT relname, relhasindex
    FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace
-   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_') IS NOT TRUE
+   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_' OR nspname ~ '^pg_deparse') IS NOT TRUE
    ORDER BY relname;
 a|f
 a_star|f
diff --git a/src/test/regress/input/deparse_test.source b/src/test/regress/input/deparse_test.source
new file mode 100644
index 0000000..e7ba0dd
--- /dev/null
+++ b/src/test/regress/input/deparse_test.source
@@ -0,0 +1,57 @@
+---
+--- DEPARSE_TEST
+---
+
+-- create roles used throughout the tests
+create role clstr_user;
+create role "current_user";
+create role foreign_data_user;
+create role "Public";
+create role regressgroup1;
+create role regressgroup2;
+create role regression_bob;
+create role regression_group;
+create role regression_user1;
+create role regression_user2;
+create role regression_user3;
+create role regression_user;
+create role regresslo;
+create role regress_rol_lock1;
+create role regress_test_indirect;
+create role regress_test_role;
+create role regress_test_role2;
+create role regress_test_role_super superuser;
+create role regressuser1;
+create role regressuser2;
+create role regressuser3;
+create role regressuser4;
+create role regressuser5;
+create role regtest_unpriv_user;
+create role regtest_addr_user;
+create role regtest_alter_user1;
+create role regtest_alter_user2;
+create role regtest_alter_user3;
+create role rls_regress_group1;
+create role rls_regress_group2;
+create role rls_regress_user0;
+create role rls_regress_user1;
+create role rls_regress_user2;
+create role rls_regress_exempt_user;
+create role schemauser2;
+create role seclabel_user1;
+create role seclabel_user2;
+create role selinto_user;
+create role testrol1;
+create role testrol2;
+create role testrolx;
+create role unprivileged_role;
+create role "user";
+create role view_user2;
+
+\pset format unaligned
+\pset tuples_only
+\o ./sql/deparse_dump.sql
+
+SELECT * FROM pg_deparse.output_commands();
+
+\! @abs_builddir@/../../bin/psql/psql --dbname=@deparse_test_db@ -e < ./sql/deparse_dump.sql > results/deparse_dump.out 2>&1
diff --git a/src/test/regress/output/deparse_test.source b/src/test/regress/output/deparse_test.source
new file mode 100644
index 0000000..f083e7c
--- /dev/null
+++ b/src/test/regress/output/deparse_test.source
@@ -0,0 +1,53 @@
+---
+--- DEPARSE_TEST
+---
+-- create roles used throughout the tests
+create role clstr_user;
+create role "current_user";
+create role foreign_data_user;
+create role "Public";
+create role regressgroup1;
+create role regressgroup2;
+create role regression_bob;
+create role regression_group;
+create role regression_user1;
+create role regression_user2;
+create role regression_user3;
+create role regression_user;
+create role regresslo;
+create role regress_rol_lock1;
+create role regress_test_indirect;
+create role regress_test_role;
+create role regress_test_role2;
+create role regress_test_role_super superuser;
+create role regressuser1;
+create role regressuser2;
+create role regressuser3;
+create role regressuser4;
+create role regressuser5;
+create role regtest_unpriv_user;
+create role regtest_addr_user;
+create role regtest_alter_user1;
+create role regtest_alter_user2;
+create role regtest_alter_user3;
+create role rls_regress_group1;
+create role rls_regress_group2;
+create role rls_regress_user0;
+create role rls_regress_user1;
+create role rls_regress_user2;
+create role rls_regress_exempt_user;
+create role schemauser2;
+create role seclabel_user1;
+create role seclabel_user2;
+create role selinto_user;
+create role testrol1;
+create role testrol2;
+create role testrolx;
+create role unprivileged_role;
+create role "user";
+create role view_user2;
+\pset format unaligned
+\pset tuples_only
+\o ./sql/deparse_dump.sql
+SELECT * FROM pg_deparse.output_commands();
+\! @abs_builddir@/../../bin/psql/psql --dbname=@deparse_test_db@ -e < ./sql/deparse_dump.sql > results/deparse_dump.out 2>&1
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index dd65ab5..fe00929 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -68,6 +68,7 @@ const char *pretty_diff_opts = "-w -C3";
 
 /* options settable from command line */
 _stringlist *dblist = NULL;
+char	    deparse_test_db[NAMEDATALEN];
 bool		debug = false;
 char	   *inputdir = ".";
 char	   *outputdir = ".";
@@ -90,6 +91,8 @@ static char *dlpath = PKGLIBDIR;
 static char *user = NULL;
 static _stringlist *extraroles = NULL;
 static char *config_auth_datadir = NULL;
+static bool generate_files_only = false;
+static bool keep_instance = false;
 
 /* internal variables */
 static const char *progname;
@@ -554,6 +557,7 @@ convert_sourcefiles_in(char *source_subdir, char *dest_dir, char *dest_subdir, c
 			replace_string(line, "@testtablespace@", testtablespace);
 			replace_string(line, "@libdir@", dlpath);
 			replace_string(line, "@DLSUFFIX@", DLSUFFIX);
+			replace_string(line, "@deparse_test_db@", deparse_test_db);
 			fputs(line, outfile);
 		}
 		fclose(infile);
@@ -1958,6 +1962,8 @@ help(void)
 	printf(_("  --config-auth=DATADIR     update authentication settings for DATADIR\n"));
 	printf(_("  --create-role=ROLE        create the specified role before testing\n"));
 	printf(_("  --dbname=DB               use database DB (default \"regression\")\n"));
+	printf(_("  --dbname-deparse=DB       use database DB for DDL deparse test (default\n"));
+	printf(_("                            \"regression_deparse\")\n"));
 	printf(_("  --debug                   turn on debug mode in programs that are run\n"));
 	printf(_("  --dlpath=DIR              look for dynamic libraries in DIR\n"));
 	printf(_("  --encoding=ENCODING       use ENCODING as the encoding\n"));
@@ -1974,6 +1980,7 @@ help(void)
 	printf(_("                            (can be used multiple times to concatenate)\n"));
 	printf(_("  --temp-instance=DIR       create a temporary instance in DIR\n"));
 	printf(_("  --use-existing            use an existing installation\n"));
+	printf(_("  --keep-instance           don't destroy nor stop the temp instance\n"));
 	printf(_("\n"));
 	printf(_("Options for \"temp-instance\" mode:\n"));
 	printf(_("  --no-locale               use C locale\n"));
@@ -2018,6 +2025,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 		{"launcher", required_argument, NULL, 21},
 		{"load-extension", required_argument, NULL, 22},
 		{"config-auth", required_argument, NULL, 24},
+		{"dbname-deparse", required_argument, NULL, 25},
+		{"generate-files-only", no_argument, NULL, 26},
+		{"keep-instance", no_argument, NULL, 27},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -2065,6 +2075,14 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 				 */
 				free_stringlist(&dblist);
 				split_to_stringlist(strdup(optarg), ", ", &dblist);
+
+				/*
+				 * We need to update the default deparse test db name
+				 * to be a suffix of the provided database name
+				 */
+				snprintf(deparse_test_db, sizeof(deparse_test_db),
+						 "%s_deparse",
+						 dblist->str);
 				break;
 			case 2:
 				debug = true;
@@ -2131,6 +2149,15 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 			case 24:
 				config_auth_datadir = pstrdup(optarg);
 				break;
+			case 25:
+				strncpy(deparse_test_db, optarg, sizeof(deparse_test_db));
+				break;
+			case 26:
+				generate_files_only = true;
+				break;
+			case 27:
+				keep_instance = true;
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
@@ -2171,6 +2198,12 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	outputdir = make_absolute_path(outputdir);
 	dlpath = make_absolute_path(dlpath);
 
+	if (generate_files_only)
+	{
+		convert_sourcefiles();
+		exit(0);
+	}
+
 	/*
 	 * Initialization
 	 */
@@ -2404,6 +2437,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 		{
 			for (sl = dblist; sl; sl = sl->next)
 				drop_database_if_exists(sl->str);
+			drop_database_if_exists(deparse_test_db);
 			for (sl = extraroles; sl; sl = sl->next)
 				drop_role_if_exists(sl->str);
 		}
@@ -2416,6 +2450,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	{
 		for (sl = dblist; sl; sl = sl->next)
 			create_database(sl->str);
+		create_database(deparse_test_db);
 		for (sl = extraroles; sl; sl = sl->next)
 			create_role(sl->str, dblist);
 	}
@@ -2438,7 +2473,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	/*
 	 * Shut down temp installation's postmaster
 	 */
-	if (temp_instance)
+	if (temp_instance && !keep_instance)
 	{
 		header(_("shutting down postmaster"));
 		stop_postmaster();
@@ -2449,7 +2484,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 	 * conserve disk space.  (If there were errors, we leave the instance in
 	 * place for possible manual investigation.)
 	 */
-	if (temp_instance && fail_count == 0 && fail_ignore_count == 0)
+	if (temp_instance && fail_count == 0 && fail_ignore_count == 0 && !keep_instance)
 	{
 		header(_("removing temporary instance"));
 		if (!rmtree(temp_instance, true))
diff --git a/src/test/regress/pg_regress.h b/src/test/regress/pg_regress.h
index 0fc9db7..8411d4f 100644
--- a/src/test/regress/pg_regress.h
+++ b/src/test/regress/pg_regress.h
@@ -38,6 +38,7 @@ extern char *datadir;
 extern char *host_platform;
 
 extern _stringlist *dblist;
+extern char deparse_test_db[NAMEDATALEN];
 extern bool debug;
 extern char *inputdir;
 extern char *outputdir;
diff --git a/src/test/regress/pg_regress_main.c b/src/test/regress/pg_regress_main.c
index 860431b..dd78805 100644
--- a/src/test/regress/pg_regress_main.c
+++ b/src/test/regress/pg_regress_main.c
@@ -88,6 +88,7 @@ psql_init(int argc, char **argv)
 {
 	/* set default regression database name */
 	add_stringlist_item(&dblist, "regression");
+	strlcpy(deparse_test_db, "regression_deparse", NAMEDATALEN);
 }
 
 int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 3ef55d9..01726a1 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1291,7 +1291,9 @@ where virtualtransaction = (
         from pg_locks
         where transactionid = txid_current()::integer)
 and locktype = 'relation'
-and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
+and relnamespace NOT IN (
+	select oid from pg_namespace
+	where nspname IN ('pg_catalog', 'pg_deparse'))
 and c.relname != 'my_locks'
 group by c.relname;
 
diff --git a/src/test/regress/sql/create_function_ddl_demo.sql b/src/test/regress/sql/create_function_ddl_demo.sql
new file mode 100644
index 0000000..ac29426
--- /dev/null
+++ b/src/test/regress/sql/create_function_ddl_demo.sql
@@ -0,0 +1,4 @@
+CREATE FUNCTION check_foreign_key ()
+	RETURNS trigger
+	AS '/space/sda1/ibarwick/2ndquadrant_bdr/src/test/regress/refint.so'
+	LANGUAGE C;
\ No newline at end of file
diff --git a/src/test/regress/sql/deparse_init.sql b/src/test/regress/sql/deparse_init.sql
new file mode 100644
index 0000000..996c914
--- /dev/null
+++ b/src/test/regress/sql/deparse_init.sql
@@ -0,0 +1,182 @@
+--
+-- DEPARSE_INIT
+--
+CREATE SCHEMA deparse;
+UPDATE pg_namespace SET nspname = 'pg_deparse' WHERE nspname = 'deparse';
+CREATE UNLOGGED TABLE pg_deparse.deparse_test_commands (
+  backend_id int,
+  backend_start timestamptz,
+  lsn pg_lsn,
+  ord integer,
+  command TEXT
+);
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_ddl_command_end()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+BEGIN
+	BEGIN
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, command, ord, lsn)
+		SELECT id, pg_stat_get_backend_start(id),
+		     public.ddl_deparse_expand_command(public.ddl_deparse_to_json(command)),
+                     ordinality, lsn
+		FROM pg_event_trigger_ddl_commands() WITH ORDINALITY,
+		pg_current_xlog_insert_location() lsn,
+		pg_stat_get_backend_idset() id
+		 WHERE pg_stat_get_backend_pid(id) = pg_backend_pid() AND
+		NOT command_tag = 'CREATE TABLE AS EXECUTE';
+	EXCEPTION WHEN OTHERS THEN 
+			RAISE WARNING 'state: % errm: %', sqlstate, sqlerrm;
+	END;
+END;
+$fn$;
+
+CREATE OR REPLACE FUNCTION pg_deparse.deparse_test_sql_drop()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+DECLARE
+fmt	TEXT;
+obj RECORD;
+i	integer = 1;
+BEGIN
+
+	/* This function runs in the sql_drop event trigger.
+     *
+	 * When it runs, we know that all objects reported by the
+	 * pg_event_trigger_dropped_objects() function marked as "original" have
+	 * been mentioned in the DROP command, either directly by name or
+	 * indirectly by owner (DROP OWNED BY).  Since no objects that depend on
+	 * them can persist after that, we can replicate the effect of that by
+	 * executing an equivalent "DROP IF EXISTS object ... CASCADE".  CASCADE
+	 * lets the deletion work even in presence of objects that appear further
+	 * down in the return set of pg_event_trigger_dropped_objects, while IF
+	 * EXISTS let the deletion silently do nothing if the object was already
+	 * dropped because it was dependent on another object before it in the same
+	 * result set.
+     *
+     * (In general, it is impossible to reorder the result set in a way that
+     * would be completely free of dependency issues.)
+     */
+
+	FOR obj IN
+	SELECT object_type, address_names, address_args, object_identity
+	  FROM pg_event_trigger_dropped_objects()
+	 WHERE original
+	  LOOP
+
+		-- special case for default acls: ignore them.
+		IF obj.object_type = 'default acl' THEN
+			CONTINUE;
+		END IF;
+
+		/*
+		 * special cases for objects that are part of other objects: drop
+		 * each in a separate command.  Since we only deal with "original"
+		 * objects, these would not be reported in the complex case of
+		 * DROP OWNED.
+		 */
+		IF obj.object_type = 'table column' OR obj.object_type = 'foreign table column' THEN
+			fmt = format('ALTER TABLE %I.%I DROP COLUMN %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'composite type column' THEN
+			fmt = format('ALTER TYPE %I.%I DROP ATTRIBUTE %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TYPE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'table constraint' THEN
+			fmt = format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+			-- ignore these; they are output by ALTER TABLE itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'domain constraint' THEN
+			fmt = format('ALTER DOMAIN %s DROP CONSTRAINT %I CASCADE',
+				obj.address_names[1],
+				obj.address_args[1]);
+		ELSIF obj.object_type = 'default value' THEN
+			fmt = format('ALTER TABLE %I.%I ALTER COLUMN %I DROP DEFAULT',
+				obj.address_names[1],
+				obj.address_names[2],
+				obj.address_names[3]);
+		ELSIF obj.object_type = 'foreign-data wrapper' THEN
+			fmt = format('DROP FOREIGN DATA WRAPPER IF EXISTS %s CASCADE',
+				obj.object_identity);
+		ELSIF obj.object_type = 'user mapping' THEN
+			fmt = format('DROP USER MAPPING FOR %I SERVER %I',
+				obj.address_names[1], obj.address_args[1]);
+		ELSIF obj.object_type = 'operator of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP OPERATOR %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSIF obj.object_type = 'function of access method' THEN
+			fmt = format('ALTER OPERATOR FAMILY %I.%I USING %I DROP FUNCTION %s (%s, %s)',
+				obj.address_names[2], obj.address_names[3], obj.address_names[1], obj.address_names[4],
+				obj.address_args[1], obj.address_args[2]);
+			-- ignore these; they are output by ALTER OPERATOR FAMILY itself
+			fmt := NULL;
+		ELSE
+			-- all other cases
+			fmt := format('DROP %s IF EXISTS %s CASCADE',
+				obj.object_type, obj.object_identity);
+		END IF;
+
+		IF fmt IS NULL THEN
+			CONTINUE;
+		END IF;
+
+		fmt := fmt || ' /* DROP support */';
+
+		INSERT INTO pg_deparse.deparse_test_commands
+		            (backend_id, backend_start, lsn, ord, command)
+			 SELECT id, pg_stat_get_backend_start(id),
+			        pg_current_xlog_insert_location(), i, fmt
+			   FROM pg_stat_get_backend_idset() id
+			  WHERE pg_stat_get_backend_pid(id) = pg_backend_pid();
+		i := i + 1;
+	END LOOP;
+END;
+$fn$;
+
+CREATE OR REPLACE FUNCTION pg_deparse.output_commands() RETURNS SETOF text LANGUAGE PLPGSQL AS $$
+DECLARE
+        cmd text;
+        prev_id int = -1;
+        prev_start timestamptz = '-infinity';
+        sess_id int;
+        sess_start timestamptz;
+BEGIN
+   FOR cmd, sess_id, sess_start IN
+			   SELECT command, backend_id, backend_start
+                 FROM pg_deparse.deparse_test_commands
+			 ORDER BY lsn, ord
+   LOOP
+          IF (sess_id, sess_start) <> (prev_id, prev_start) THEN
+                prev_id := sess_id;
+                prev_start := sess_start;
+                RETURN NEXT '\c';
+          END IF;
+      RETURN NEXT cmd || ';' ;
+   END LOOP;
+END;
+$$;
+
+CREATE EVENT TRIGGER deparse_test_trg_sql_drop
+  ON sql_drop
+  EXECUTE PROCEDURE pg_deparse.deparse_test_sql_drop();
+
+CREATE EVENT TRIGGER deparse_test_trg_ddl_command_end
+  ON ddl_command_end
+  EXECUTE PROCEDURE pg_deparse.deparse_test_ddl_command_end();
diff --git a/src/test/regress/sql/sanity_check.sql b/src/test/regress/sql/sanity_check.sql
index 0da838e..1d7a276 100644
--- a/src/test/regress/sql/sanity_check.sql
+++ b/src/test/regress/sql/sanity_check.sql
@@ -4,7 +4,8 @@ VACUUM;
 -- sanity check, if we don't have indices the test will take years to
 -- complete.  But skip TOAST relations (since they will have varying
 -- names depending on the current OID counter) as well as temp tables
--- of other backends (to avoid timing-dependent behavior).
+-- of other backends (to avoid timing-dependent behavior).  Also exclude
+-- the schema used for the deparse test, as it might not be there at all.
 --
 
 -- temporarily disable fancy output, so catalog changes create less diff noise
@@ -12,7 +13,7 @@ VACUUM;
 
 SELECT relname, relhasindex
    FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace
-   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_') IS NOT TRUE
+   WHERE relkind = 'r' AND (nspname ~ '^pg_temp_' OR nspname ~ '^pg_deparse') IS NOT TRUE
    ORDER BY relname;
 
 -- restore normal output mode
-- 
2.1.4

0002-Move-some-ddl-deparsing-code-from-ruleutils-to-exten.patchtext/x-patch; charset=US-ASCII; name=0002-Move-some-ddl-deparsing-code-from-ruleutils-to-exten.patchDownload
From bacc5fb61d9e787f077e4db9f185cf8e7bf2afa3 Mon Sep 17 00:00:00 2001
From: Oleksandr Shulgin <oleksandr.shulgin@zalando.de>
Date: Thu, 30 Jul 2015 12:07:16 +0200
Subject: [PATCH 2/5] Move some ddl-deparsing code from ruleutils to extension.

---
 src/backend/utils/adt/format_type.c | 127 --------------
 src/backend/utils/adt/ruleutils.c   | 322 +-----------------------------------
 src/include/utils/builtins.h        |   3 -
 src/include/utils/ruleutils.h       |  20 +--
 4 files changed, 12 insertions(+), 460 deletions(-)

diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index a8aacfb..a851983 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -328,133 +328,6 @@ format_type_internal(Oid type_oid, int32 typemod,
 
 
 /*
- * Similar to format_type_internal, except we return each bit of information
- * separately:
- *
- * - nspid is the schema OID.  For certain SQL-standard types which have weird
- *   typmod rules, we return InvalidOid; caller is expected to not schema-
- *   qualify the name nor add quotes to the type name in this case.
- *
- * - typename is set to the type name, without quotes
- *
- * - typmod is set to the typemod, if any, as a string with parens
- *
- * - typarray indicates whether []s must be added
- *
- * We don't try to decode type names to their standard-mandated names, except
- * in the cases of types with unusual typmod rules.
- */
-void
-format_type_detailed(Oid type_oid, int32 typemod,
-					 Oid *nspid, char **typname, char **typemodstr,
-					 bool *typarray)
-{
-	HeapTuple	tuple;
-	Form_pg_type typeform;
-	Oid			array_base_type;
-
-	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "cache lookup failed for type %u", type_oid);
-
-	typeform = (Form_pg_type) GETSTRUCT(tuple);
-
-	/*
-	 * Special-case crock for types with strange typmod rules.
-	 */
-	if (type_oid == INTERVALOID ||
-		type_oid == TIMESTAMPOID ||
-		type_oid == TIMESTAMPTZOID ||
-		type_oid == TIMEOID ||
-		type_oid == TIMETZOID)
-	{
-		*typarray = false;
-
-peculiar_typmod:
-		switch (type_oid)
-		{
-			case INTERVALOID:
-				*typname = pstrdup("INTERVAL");
-				break;
-			case TIMESTAMPTZOID:
-				if (typemod < 0)
-				{
-					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
-					break;
-				}
-				/* otherwise, WITH TZ is added by typmod, so fall through */
-			case TIMESTAMPOID:
-				*typname = pstrdup("TIMESTAMP");
-				break;
-			case TIMETZOID:
-				if (typemod < 0)
-				{
-					*typname = pstrdup("TIME WITH TIME ZONE");
-					break;
-				}
-				/* otherwise, WITH TZ is added by typmode, so fall through */
-			case TIMEOID:
-				*typname = pstrdup("TIME");
-				break;
-		}
-		*nspid = InvalidOid;
-
-		if (typemod >= 0)
-			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
-		else
-			*typemodstr = pstrdup("");
-
-		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;
-		*typarray = true;
-
-		/*
-		 * If it's an array of one of the types with special typmod rules,
-		 * have the element type be processed as above, but now with typarray
-		 * set to true.
-		 */
-		if (type_oid == INTERVALOID ||
-			type_oid == TIMESTAMPTZOID ||
-			type_oid == TIMESTAMPOID ||
-			type_oid == TIMETZOID ||
-			type_oid == TIMEOID)
-			goto peculiar_typmod;
-	}
-	else
-		*typarray = false;
-
-	*nspid = typeform->typnamespace;
-	*typname = pstrdup(NameStr(typeform->typname));
-
-	if (typemod >= 0)
-		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
-	else
-		*typemodstr = pstrdup("");
-
-	ReleaseSysCache(tuple);
-}
-
-
-/*
  * Add typmod decoration to the basic type name
  */
 static char *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 28de88b..19ce290 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -289,9 +289,6 @@ bool		quote_all_identifiers = false;
  * as a parameter, and append their text output to its contents.
  * ----------
  */
-static char *deparse_expression_pretty(Node *expr, List *dpcontext,
-						  bool forceprefix, bool showimplicit,
-						  int prettyFlags, int startIndent);
 static char *pg_get_viewdef_worker(Oid viewoid,
 					  int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
@@ -415,8 +412,6 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
 						   deparse_context *context);
 static void get_tablesample_def(TableSampleClause *tablesample,
 					deparse_context *context);
-static void get_opclass_name(Oid opclass, Oid actual_datatype,
-				 StringInfo buf);
 static Node *processIndirection(Node *node, deparse_context *context,
 				   bool printit);
 static void printSubscripts(ArrayRef *aref, deparse_context *context);
@@ -428,7 +423,6 @@ static char *generate_function_name(Oid funcid, int nargs,
 					   ParseExprKind special_exprkind);
 static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
 static text *string_to_text(char *str);
-static char *flatten_reloptions(Oid relid);
 
 #define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")
 
@@ -468,8 +462,8 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS)
  * NIL, signalling NOTHING) in actions.
  */
 void
-pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
-					   char **whereClause, List **actions)
+pg_get_ruledef_detailed(Datum ev_qual, Datum ev_action,
+						char **whereClause, List **actions)
 {
 	int prettyFlags = 0;
 	char *qualstr = TextDatumGetCString(ev_qual);
@@ -1377,246 +1371,6 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 }
 
 /*
- * 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
  *
  * Returns the definition for the constraint, ie, everything that needs to
@@ -2838,7 +2592,7 @@ deparse_expression(Node *expr, List *dpcontext,
  * The result is a palloc'd string.
  * ----------
  */
-static char *
+char *
 deparse_expression_pretty(Node *expr, List *dpcontext,
 						  bool forceprefix, bool showimplicit,
 						  int prettyFlags, int startIndent)
@@ -9509,7 +9263,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
  * actual_datatype.  (If you don't want this behavior, just pass
  * InvalidOid for actual_datatype.)
  */
-static void
+void
 get_opclass_name(Oid opclass, Oid actual_datatype,
 				 StringInfo buf)
 {
@@ -10057,7 +9811,7 @@ string_to_text(char *str)
 /*
  * Generate a C string representing a relation's reloptions, or NULL if none.
  */
-static char *
+char *
 flatten_reloptions(Oid relid)
 {
 	char	   *result = NULL;
@@ -10091,69 +9845,3 @@ 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;
-}
-
-/*
- * Return the default value of a domain.
- */
-char *
-DomainGetDefault(HeapTuple domTup)
-{
-	Datum	def;
-	Node   *defval;
-	char   *defstr;
-	bool	isnull;
-
-	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
-							 &isnull);
-	if (isnull)
-		elog(ERROR, "domain \"%s\" does not have a default value",
-			 NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
-	defval = stringToNode(TextDatumGetCString(def));
-	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
-									   false, false, 0, 0);
-
-	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/builtins.h b/src/include/utils/builtins.h
index 16e51dc..c3ff3d7 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1108,9 +1108,6 @@ 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);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 8ff6a0c..73b19d2 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -19,18 +19,15 @@
 #include "nodes/pg_list.h"
 
 
+extern char *deparse_expression_pretty(Node *expr, List *dpcontext,
+						  bool forceprefix, bool showimplicit,
+						  int prettyFlags, int startIndent);
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 						  Node *whenClause, bool pretty);
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
-extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+extern void pg_get_ruledef_detailed(Datum ev_qual, Datum ev_action,
 					   char **whereClause, List **actions);
 extern char *pg_get_viewdef_internal(Oid viewoid);
 extern char *pg_get_createtableas_def(Query *query);
@@ -43,12 +40,9 @@ extern List *set_deparse_context_planstate(List *dpcontext,
 							  Node *planstate, List *ancestors);
 extern List *select_rtable_names_for_explain(List *rtable,
 								Bitmapset *rels_used);
+extern void get_opclass_name(Oid opclass, Oid actual_datatype,
+				 StringInfo buf);
 extern char *generate_collation_name(Oid collid);
-extern List *FunctionGetDefaults(text *proargdefaults);
-
-extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
-						 List *dpcontext);
-
-extern char *DomainGetDefault(HeapTuple domTup);
+extern char *flatten_reloptions(Oid relid);
 
 #endif   /* RULEUTILS_H */
-- 
2.1.4

0001-ddl_deparse-core-support.patchtext/x-patch; charset=US-ASCII; name=0001-ddl_deparse-core-support.patchDownload
From 60b1a53d599656b9ab7015c042ff8ca495308a87 Mon Sep 17 00:00:00 2001
From: Oleksandr Shulgin <oleksandr.shulgin@zalando.de>
Date: Wed, 29 Jul 2015 11:09:56 +0200
Subject: [PATCH 1/5] ddl_deparse core support

---
 src/backend/commands/seclabel.c     |   5 +
 src/backend/commands/sequence.c     |  34 +++
 src/backend/commands/tablecmds.c    |   3 +-
 src/backend/utils/adt/format_type.c | 127 +++++++++
 src/backend/utils/adt/regproc.c     | 101 +++++--
 src/backend/utils/adt/ruleutils.c   | 516 ++++++++++++++++++++++++++++++++----
 src/include/commands/sequence.h     |   1 +
 src/include/utils/builtins.h        |   4 +
 src/include/utils/ruleutils.h       |  21 +-
 9 files changed, 730 insertions(+), 82 deletions(-)

diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 1ef98ce..aa4de97 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -63,6 +63,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("must specify provider when multiple security label providers have been loaded")));
 		provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
 	}
 	else
 	{
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 9c1037f..b0f8003 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1517,6 +1517,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/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 126b119..202b5fd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8192,7 +8192,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/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index a851983..a8aacfb 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -328,6 +328,133 @@ format_type_internal(Oid type_oid, int32 typemod,
 
 
 /*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name in this case.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *typarray)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID ||
+		type_oid == TIMEOID ||
+		type_oid == TIMETZOID)
+	{
+		*typarray = false;
+
+peculiar_typmod:
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPTZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmod, so fall through */
+			case TIMESTAMPOID:
+				*typname = pstrdup("TIMESTAMP");
+				break;
+			case TIMETZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIME WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmode, so fall through */
+			case TIMEOID:
+				*typname = pstrdup("TIME");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		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;
+		*typarray = true;
+
+		/*
+		 * If it's an array of one of the types with special typmod rules,
+		 * have the element type be processed as above, but now with typarray
+		 * set to true.
+		 */
+		if (type_oid == INTERVALOID ||
+			type_oid == TIMESTAMPTZOID ||
+			type_oid == TIMESTAMPOID ||
+			type_oid == TIMETZOID ||
+			type_oid == TIMEOID)
+			goto peculiar_typmod;
+	}
+	else
+		*typarray = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
+
+/*
  * Add typmod decoration to the basic type name
  */
 static char *
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 0bfeb5e..5efa5ea 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -43,7 +43,11 @@
 #include "utils/acl.h"
 
 static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
+static char *format_procedure_internal(Oid procedure_oid, bool force_qualify,
+						  bool args_only);
+static void format_procedure_args_internal(Form_pg_proc procform,
+							   StringInfo buf, bool force_qualify);
+
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -364,13 +368,36 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_internal(procedure_oid, false, false);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_internal(procedure_oid, true, false);
+}
+
+/*
+ * format_procedure_args	- converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+	StringInfoData buf;
+	HeapTuple	proctup;
+	Form_pg_proc procform;
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+	initStringInfo(&buf);
+	format_procedure_args_internal(procform, &buf, force_qualify);
+
+	ReleaseSysCache(proctup);
+
+	return buf.data;
 }
 
 /*
@@ -381,7 +408,7 @@ format_procedure_qualified(Oid procedure_oid)
  * qualified if the function is not in path.
  */
 static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+format_procedure_internal(Oid procedure_oid, bool force_qualify, bool args_only)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -392,8 +419,6 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 	{
 		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
 		char	   *proname = NameStr(procform->proname);
-		int			nargs = procform->pronargs;
-		int			i;
 		char	   *nspname;
 		StringInfoData buf;
 
@@ -401,29 +426,24 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		initStringInfo(&buf);
 
-		/*
-		 * Would this proc be found (given the right args) by regprocedurein?
-		 * If not, or if caller requests it, we need to qualify it.
-		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
-			nspname = NULL;
-		else
-			nspname = get_namespace_name(procform->pronamespace);
-
-		appendStringInfo(&buf, "%s(",
-						 quote_qualified_identifier(nspname, proname));
-		for (i = 0; i < nargs; i++)
+		if (!args_only)
 		{
-			Oid			thisargtype = procform->proargtypes.values[i];
-
-			if (i > 0)
-				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf,
-								   force_qualify ?
-								   format_type_be_qualified(thisargtype) :
-								   format_type_be(thisargtype));
+			/*
+			 * Would this proc be found (given the right args) by
+			 * regprocedurein?  If not, or if caller requests it, we need to
+			 * qualify it.
+			 */
+			if (!force_qualify && FunctionIsVisible(procedure_oid))
+				nspname = NULL;
+			else
+				nspname = get_namespace_name(procform->pronamespace);
+
+			appendStringInfo(&buf, "%s",
+							 quote_qualified_identifier(nspname, proname));
 		}
-		appendStringInfoChar(&buf, ')');
+
+		/* add the attributes */
+		format_procedure_args_internal(procform, &buf, force_qualify);
 
 		result = buf.data;
 
@@ -440,6 +460,33 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 }
 
 /*
+ * Append the parenthised arguments of the given pg_proc row into the output
+ * buffer.  force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+							   bool force_qualify)
+{
+	int			i;
+	int			nargs = procform->pronargs;
+
+	appendStringInfoChar(buf, '(');
+	for (i = 0; i < nargs; i++)
+	{
+		Oid			thisargtype = procform->proargtypes.values[i];
+
+		if (i > 0)
+			appendStringInfoChar(buf, ',');
+		appendStringInfoString(buf,
+							   force_qualify ?
+							   format_type_be_qualified(thisargtype) :
+							   format_type_be(thisargtype));
+	}
+	appendStringInfoChar(buf, ')');
+}
+
+/*
  * Output an objname/objargs representation for the procedure with the
  * given OID.  If it doesn't exist, an error is thrown.
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 51391f6..28de88b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -461,6 +461,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)
@@ -611,6 +682,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
  */
@@ -685,6 +763,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def	- Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * get_triggerdef			- Get the definition of a trigger
  * ----------
@@ -835,60 +928,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char	   *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.ctes = NIL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-		context.special_exprkind = EXPR_KIND_NONE;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
@@ -929,6 +974,64 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char		relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+	initStringInfo(&buf);
+
+	relkind = get_rel_relkind(trigrec->tgrelid);
+
+	/* Build minimal OLD and NEW RTEs for the rel */
+	oldrte = makeNode(RangeTblEntry);
+	oldrte->rtekind = RTE_RELATION;
+	oldrte->relid = trigrec->tgrelid;
+	oldrte->relkind = relkind;
+	oldrte->alias = makeAlias("old", NIL);
+	oldrte->eref = oldrte->alias;
+	oldrte->lateral = false;
+	oldrte->inh = false;
+	oldrte->inFromCl = true;
+
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_RELATION;
+	newrte->relid = trigrec->tgrelid;
+	newrte->relkind = relkind;
+	newrte->alias = makeAlias("new", NIL);
+	newrte->eref = newrte->alias;
+	newrte->lateral = false;
+	newrte->inh = false;
+	newrte->inFromCl = true;
+
+	/* Build two-element rtable */
+	memset(&dpns, 0, sizeof(dpns));
+	dpns.rtable = list_make2(oldrte, newrte);
+	dpns.ctes = NIL;
+	set_rtable_names(&dpns, NIL, NULL);
+	set_simple_column_names(&dpns);
+
+	/* Set up context with one-deep namespace stack */
+	context.buf = &buf;
+	context.namespaces = list_make1(&dpns);
+	context.windowClause = NIL;
+	context.windowTList = NIL;
+	context.varprefix = true;
+	context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+	context.wrapColumn = WRAP_COLUMN_DEFAULT;
+	context.indentLevel = PRETTYINDENT_STD;
+	context.special_exprkind = EXPR_KIND_NONE;
+
+	get_rule_expr(whenClause, &context, false);
+
+	return buf.data;
+}
+
 /* ----------
  * get_indexdef			- Get the definition of an index
  *
@@ -992,6 +1095,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,
@@ -1271,6 +1376,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
@@ -1305,9 +1649,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);
 }
 
 /*
@@ -9747,3 +10091,69 @@ 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;
+}
+
+/*
+ * Return the default value of a domain.
+ */
+char *
+DomainGetDefault(HeapTuple domTup)
+{
+	Datum	def;
+	Node   *defval;
+	char   *defstr;
+	bool	isnull;
+
+	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+							 &isnull);
+	if (isnull)
+		elog(ERROR, "domain \"%s\" does not have a default value",
+			 NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+	defval = stringToNode(TextDatumGetCString(def));
+	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
+									   false, false, 0, 0);
+
+	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/commands/sequence.h b/src/include/commands/sequence.h
index 44862bb..8d8e556 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -72,6 +72,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 ObjectAddress DefineSequence(CreateSeqStmt *stmt);
 extern ObjectAddress AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fc1679e..16e51dc 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -662,6 +662,7 @@ extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
 					   List **objargs);
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
@@ -1107,6 +1108,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);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 3494b13..8ff6a0c 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,6 +13,7 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
@@ -20,8 +21,20 @@
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+						  Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+					   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
 
-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);
@@ -31,5 +44,11 @@ extern List *set_deparse_context_planstate(List *dpcontext,
 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);
+
+extern char *DomainGetDefault(HeapTuple domTup);
 
 #endif   /* RULEUTILS_H */
-- 
2.1.4

#64Ajin Cherian
itsajin@gmail.com
In reply to: Shulgin, Oleksandr (#63)
1 attachment(s)
Re: deparsing utility commands

On Wed, Apr 13, 2022 at 2:12 PM Shulgin, Oleksandr
<oleksandr.shulgin@zalando.de> wrote:

You seem to have squashed the patches? Please keep the split out.

Well, if that makes the review process easier :-)

--
Alex

This patch-set has not been rebased for some time. I see that there is
interest in this patch from the logical
replication of DDL thread [1].

I will take a stab at rebasing this patch-set, I have already rebased
the first patch and will work on the other
patches in the coming days. Do review and give me feedback.

regards,
Ajin Cherian
Fujitsu Australia

Attachments:

0001-ddl_deparse-core-support.patchapplication/octet-stream; name=0001-ddl_deparse-core-support.patchDownload
From e3b589472cc8e299646f2bef3fbaeb486df37f9b Mon Sep 17 00:00:00 2001
From: Ajin Cherian <ajinc@fast.au.fujitsu.com>
Date: Wed, 13 Apr 2022 00:08:10 -0400
Subject: [PATCH] ddl_deparse core support

---
 src/backend/catalog/objectaddress.c |   4 +-
 src/backend/commands/seclabel.c     |   5 +
 src/backend/commands/sequence.c     |  35 +++
 src/backend/utils/adt/format_type.c | 125 +++++++++
 src/backend/utils/adt/regproc.c     | 104 +++++--
 src/backend/utils/adt/ruleutils.c   | 529 ++++++++++++++++++++++++++++++++----
 src/include/commands/sequence.h     |   1 +
 src/include/utils/builtins.h        |   5 +
 src/include/utils/regproc.h         |   3 +-
 src/include/utils/ruleutils.h       |  23 +-
 10 files changed, 746 insertions(+), 88 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index ac60435..36dfb0f 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2980,7 +2980,7 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 			{
 				bits16		flags = FORMAT_PROC_INVALID_AS_NULL;
 				char	   *proname = format_procedure_extended(object->objectId,
-																flags);
+																flags, false);
 
 				if (proname == NULL)
 					break;
@@ -4826,7 +4826,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				bits16		flags = FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_INVALID_AS_NULL;
 				char	   *proname = format_procedure_extended(object->objectId,
-																flags);
+																flags, false);
 
 				if (proname == NULL)
 					break;
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7ae19b9..c1530a1 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -134,6 +134,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("must specify provider when multiple security label providers have been loaded")));
 		provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
 	}
 	else
 	{
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ddf219b..bdd081c 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1699,6 +1699,41 @@ process_owned_by(Relation seqrel, List *owned_by, bool for_identity)
 		relation_close(tablerel, NoLock);
 }
 
+/*
+ * Return sequence parameters, detailed
+ */
+
+Form_pg_sequence_data
+get_sequence_values(Oid sequenceId)
+{
+	Buffer      buf;
+	SeqTable    elm;
+	Relation    seqrel;
+	HeapTupleData seqtuple;
+	Form_pg_sequence_data seq;
+	Form_pg_sequence_data 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(seqrel, &buf, &seqtuple);
+
+	memcpy(retSeq, seq, sizeof(FormData_pg_sequence_data));
+
+	UnlockReleaseBuffer(buf);
+	relation_close(seqrel, NoLock);
+
+	return retSeq;
+}
 
 /*
  * Return sequence parameters in a list of the form created by the parser.
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 060fd7e..42e3af3 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -334,6 +334,131 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 }
 
 /*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name in this case.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *typarray)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID ||
+		type_oid == TIMEOID ||
+		type_oid == TIMETZOID)
+	{
+		*typarray = false;
+
+peculiar_typmod:
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPTZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmod, so fall through */
+			case TIMESTAMPOID:
+				*typname = pstrdup("TIMESTAMP");
+				break;
+			case TIMETZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIME WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmode, so fall through */
+			case TIMEOID:
+				*typname = pstrdup("TIME");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		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;
+		*typarray = true;
+
+		/*
+		 * If it's an array of one of the types with special typmod rules,
+		 * have the element type be processed as above, but now with typarray
+		 * set to true.
+		 */
+		if (type_oid == INTERVALOID ||
+			type_oid == TIMESTAMPTZOID ||
+			type_oid == TIMESTAMPOID ||
+			type_oid == TIMETZOID ||
+			type_oid == TIMEOID)
+			goto peculiar_typmod;
+	}
+	else
+		*typarray = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
+/*
  * This version is for use within the backend in error messages, etc.
  * One difference is that it will fail for an invalid type.
  *
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 6d4c1c2..4df3256 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -43,7 +43,8 @@
 
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 								 List **names, int *nargs, Oid *argtypes);
-
+static void format_procedure_args_internal(Form_pg_proc procform,
+							StringInfo buf, bool force_qualify);
 
 /*****************************************************************************
  *	 USER I/O ROUTINES														 *
@@ -322,13 +323,37 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_extended(procedure_oid, 0);
+	return format_procedure_extended(procedure_oid, 0, false);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
+	return format_procedure_extended(procedure_oid, true, false);
+}
+
+/*
+ * format_procedure_args   - converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+	StringInfoData buf;
+	HeapTuple   proctup;
+	Form_pg_proc procform;
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+
+	initStringInfo(&buf);
+	format_procedure_args_internal(procform, &buf, force_qualify);
+
+	ReleaseSysCache(proctup);
+
+	return buf.data;
 }
 
 /*
@@ -347,10 +372,13 @@ format_procedure_qualified(Oid procedure_oid)
  *			always schema-qualify procedure names, regardless of search_path
  */
 char *
-format_procedure_extended(Oid procedure_oid, bits16 flags)
+format_procedure_extended(Oid procedure_oid, bits16 flags, bool args_only)
 {
 	char	   *result;
 	HeapTuple	proctup;
+	bool		force_qualify;
+
+	force_qualify = (flags & FORMAT_PROC_FORCE_QUALIFY) != 0;
 
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
@@ -358,8 +386,6 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
 	{
 		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
 		char	   *proname = NameStr(procform->proname);
-		int			nargs = procform->pronargs;
-		int			i;
 		char	   *nspname;
 		StringInfoData buf;
 
@@ -368,30 +394,25 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
 
 		initStringInfo(&buf);
 
-		/*
-		 * Would this proc be found (given the right args) by regprocedurein?
-		 * If not, or if caller requests it, we need to qualify it.
-		 */
-		if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
-			FunctionIsVisible(procedure_oid))
-			nspname = NULL;
-		else
-			nspname = get_namespace_name(procform->pronamespace);
-
-		appendStringInfo(&buf, "%s(",
-						 quote_qualified_identifier(nspname, proname));
-		for (i = 0; i < nargs; i++)
+		if (!args_only)
 		{
-			Oid			thisargtype = procform->proargtypes.values[i];
-
-			if (i > 0)
-				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf,
-								   (flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ?
-								   format_type_be_qualified(thisargtype) :
-								   format_type_be(thisargtype));
+
+			/*
+		 	 * Would this proc be found (given the right args) by regprocedurein?
+		 	 * If not, or if caller requests it, we need to qualify it.
+		 	 */
+			if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
+				FunctionIsVisible(procedure_oid))
+				nspname = NULL;
+			else
+				nspname = get_namespace_name(procform->pronamespace);
+
+			appendStringInfo(&buf, "%s",
+							 quote_qualified_identifier(nspname, proname));
 		}
-		appendStringInfoChar(&buf, ')');
+
+		/* add the attributes */
+		format_procedure_args_internal(procform, &buf, force_qualify);
 
 		result = buf.data;
 
@@ -413,6 +434,33 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
 }
 
 /*
+ * Append the parenthised arguments of the given pg_proc row into the output
+ * buffer.  force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+							   bool force_qualify)
+{
+	int			i;
+	int			nargs = procform->pronargs;
+
+	appendStringInfoChar(buf, '(');
+	for (i = 0; i < nargs; i++)
+	{
+		Oid			thisargtype = procform->proargtypes.values[i];
+
+		if (i > 0)
+			appendStringInfoChar(buf, ',');
+		appendStringInfoString(buf,
+							   force_qualify ?
+							   format_type_be_qualified(thisargtype) :
+							   format_type_be(thisargtype));
+	}
+	appendStringInfoChar(buf, ')');
+}
+
+/*
  * Output an objname/objargs representation for the procedure with the
  * given OID.  If it doesn't exist, an error is thrown.
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3296ad0..f1d6e9a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -551,6 +551,78 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(string_to_text(res));
 }
 
+/*
+ * 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)
@@ -742,6 +814,14 @@ pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(string_to_text(res));
 }
 
+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
  */
@@ -824,6 +904,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def   - Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * pg_get_triggerdef		- Get the definition of a trigger
  * ----------
@@ -1021,65 +1116,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char       *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->rellockmode = AccessShareLock;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->rellockmode = AccessShareLock;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.subplans = NIL;
-		dpns.ctes = NIL;
-		dpns.appendrels = NULL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = GET_PRETTY_FLAGS(pretty);
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-		context.special_exprkind = EXPR_KIND_NONE;
-		context.appendparents = NULL;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE FUNCTION %s(",
@@ -1120,6 +1162,64 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char        relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+   initStringInfo(&buf);
+
+   relkind = get_rel_relkind(trigrec->tgrelid);
+
+   /* Build minimal OLD and NEW RTEs for the rel */
+   oldrte = makeNode(RangeTblEntry);
+   oldrte->rtekind = RTE_RELATION;
+   oldrte->relid = trigrec->tgrelid;
+   oldrte->relkind = relkind;
+   oldrte->alias = makeAlias("old", NIL);
+   oldrte->eref = oldrte->alias;
+   oldrte->lateral = false;
+   oldrte->inh = false;
+   oldrte->inFromCl = true;
+
+   newrte = makeNode(RangeTblEntry);
+   newrte->rtekind = RTE_RELATION;
+   newrte->relid = trigrec->tgrelid;
+   newrte->relkind = relkind;
+   newrte->alias = makeAlias("new", NIL);
+   newrte->eref = newrte->alias;
+   newrte->lateral = false;
+   newrte->inh = false;
+   newrte->inFromCl = true;
+
+   /* Build two-element rtable */
+   memset(&dpns, 0, sizeof(dpns));
+   dpns.rtable = list_make2(oldrte, newrte);
+   dpns.ctes = NIL;
+   set_rtable_names(&dpns, NIL, NULL);
+   set_simple_column_names(&dpns);
+
+   /* Set up context with one-deep namespace stack */
+   context.buf = &buf;
+   context.namespaces = list_make1(&dpns);
+   context.windowClause = NIL;
+   context.windowTList = NIL;
+   context.varprefix = true;
+   context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+   context.wrapColumn = WRAP_COLUMN_DEFAULT;
+   context.indentLevel = PRETTYINDENT_STD;
+   context.special_exprkind = EXPR_KIND_NONE;
+
+   get_rule_expr(whenClause, &context, false);
+
+   return buf.data;
+}
+
 /* ----------
  * pg_get_indexdef			- Get the definition of an index
  *
@@ -1207,6 +1307,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,
@@ -1525,6 +1627,251 @@ 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;
+	IndexAmRoutine *amroutine;
+	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);
+
+	/* Fetch the index AM's API struct */
+	amroutine = GetIndexAmRoutine(amrec->amhandler);
+
+	/*
+	 * 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, NULL))
+	{
+		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_attname(indrelid, attnum, false);
+			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(indexprs, 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 (amroutine->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, NULL))
+	{
+		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_querydef
  *
@@ -12833,3 +13180,73 @@ get_range_partbound_string(List *bound_datums)
 
 	return buf->data;
 }
+
+
+/*
+ * 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;
+}
+
+/*
+ * Return the default value of a domain.
+ */
+char *
+DomainGetDefault(HeapTuple domTup)
+{
+	Datum   def;
+	Node   *defval;
+	char   *defstr;
+	bool    isnull;
+
+	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "domain \"%s\" does not have a default value",
+			NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+	defval = stringToNode(TextDatumGetCString(def));
+	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
+									   false, false, 0, 0);
+
+	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/commands/sequence.h b/src/include/commands/sequence.h
index 9da2300..2875106 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -54,6 +54,7 @@ typedef struct xl_seq_rec
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
+extern Form_pg_sequence_data get_sequence_values(Oid sequenceId);
 
 extern ObjectAddress DefineSequence(ParseState *pstate, CreateSeqStmt *stmt);
 extern ObjectAddress AlterSequence(ParseState *pstate, AlterSeqStmt *stmt);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545..cde659f 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -115,11 +115,16 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 #define FORMAT_TYPE_INVALID_AS_NULL	0x08	/* NULL if undefined */
 extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
 extern char *format_type_be(Oid type_oid);
 extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 
 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 char *quote_literal_cstr(const char *rawstr);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index a36ceba..f4d543d 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -18,7 +18,8 @@
 /* Control flags for format_procedure_extended */
 #define FORMAT_PROC_INVALID_AS_NULL	0x01	/* NULL if undefined */
 #define FORMAT_PROC_FORCE_QUALIFY	0x02	/* force qualification */
-extern char *format_procedure_extended(Oid procedure_oid, bits16 flags);
+extern char *format_procedure_extended(Oid procedure_oid, bits16 flags,
+	   			bool args_only);
 
 /* Control flags for format_operator_extended */
 #define FORMAT_OPERATOR_INVALID_AS_NULL	0x01	/* NULL if undefined */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 7d48971..83c242d 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,6 +13,7 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
@@ -23,12 +24,26 @@ struct PlannedStmt;
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+									   Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+extern char *pg_get_constraintdef_command(Oid constraintId);
+extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+								   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
+
 extern char *pg_get_querydef(Query *query, bool pretty);
 
 extern char *pg_get_partkeydef_columns(Oid relid, bool pretty);
 extern char *pg_get_partconstrdef_string(Oid partitionId, char *aliasname);
 
-extern char *pg_get_constraintdef_command(Oid constraintId);
 extern char *deparse_expression(Node *expr, List *dpcontext,
 								bool forceprefix, bool showimplicit);
 extern List *deparse_context_for(const char *aliasname, Oid relid);
@@ -39,6 +54,12 @@ extern List *set_deparse_context_plan(List *dpcontext,
 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);
+
+extern char *DomainGetDefault(HeapTuple domTup);
 extern char *generate_opclass_name(Oid opclass);
 extern char *get_range_partbound_string(List *bound_datums);
 
-- 
1.8.3.1

#65Ajin Cherian
itsajin@gmail.com
In reply to: Ajin Cherian (#64)
Re: deparsing utility commands

On Wed, Apr 13, 2022 at 2:29 PM Ajin Cherian <itsajin@gmail.com> wrote:

This patch-set has not been rebased for some time. I see that there is
interest in this patch from the logical
replication of DDL thread [1].

Forgot to add the link to the thread.

[1]: /messages/by-id/202203162206.7spggyktx63e@alvherre.pgsql

#66Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Ajin Cherian (#64)
Re: deparsing utility commands

On 13.04.22 06:29, Ajin Cherian wrote:

This patch-set has not been rebased for some time. I see that there is
interest in this patch from the logical
replication of DDL thread [1].

I will take a stab at rebasing this patch-set, I have already rebased
the first patch and will work on the other
patches in the coming days. Do review and give me feedback.

The patch you posted contains neither a detailed commit message nor
documentation or test changes, so it's impossible to tell what it's
supposed to do.

#67Ajin Cherian
itsajin@gmail.com
In reply to: Shulgin, Oleksandr (#63)
3 attachment(s)
Re: deparsing utility commands

On Wed, Apr 13, 2022 at 2:12 PM Shulgin, Oleksandr
<oleksandr.shulgin@zalando.de> wrote:

You seem to have squashed the patches? Please keep the split out.

Well, if that makes the review process easier :-)

--
Alex

I've rebased patches 1, 2 and 5 (now 1,2 and 3). Patches 3 and 4 seem
to be related to the testing of the extension and contrib module
ddl_deparse which is not in this patch-set, so I have left those
patches out, as I have no way of testing the test cases added as part
of these patches.

regards,
Ajin Cherian
Fujitsu Australia

Attachments:

0002-Move-some-ddl-deparsing-code-from-ruleutils-to-exten.patchapplication/octet-stream; name=0002-Move-some-ddl-deparsing-code-from-ruleutils-to-exten.patchDownload
From d471ba92638dbf59a53a3a9e8a68dbce2db70692 Mon Sep 17 00:00:00 2001
From: Ajin Cherian <ajinc@fast.au.fujitsu.com>
Date: Wed, 13 Apr 2022 09:13:28 -0400
Subject: [PATCH 2/3] Move some ddl-deparsing code from ruleutils to extension.

---
 src/backend/utils/adt/format_type.c | 125 --------------
 src/backend/utils/adt/ruleutils.c   | 325 +-----------------------------------
 src/include/utils/builtins.h        |   4 -
 src/include/utils/ruleutils.h       |  17 +-
 4 files changed, 8 insertions(+), 463 deletions(-)

diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 42e3af3..060fd7e 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -334,131 +334,6 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 }
 
 /*
- * Similar to format_type_internal, except we return each bit of information
- * separately:
- *
- * - nspid is the schema OID.  For certain SQL-standard types which have weird
- *   typmod rules, we return InvalidOid; caller is expected to not schema-
- *   qualify the name nor add quotes to the type name in this case.
- *
- * - typename is set to the type name, without quotes
- *
- * - typmod is set to the typemod, if any, as a string with parens
- *
- * - typarray indicates whether []s must be added
- *
- * We don't try to decode type names to their standard-mandated names, except
- * in the cases of types with unusual typmod rules.
- */
-void
-format_type_detailed(Oid type_oid, int32 typemod,
-					 Oid *nspid, char **typname, char **typemodstr,
-					 bool *typarray)
-{
-	HeapTuple	tuple;
-	Form_pg_type typeform;
-	Oid			array_base_type;
-
-	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "cache lookup failed for type %u", type_oid);
-
-	typeform = (Form_pg_type) GETSTRUCT(tuple);
-
-	/*	 * Special-case crock for types with strange typmod rules.
-	 */
-	if (type_oid == INTERVALOID ||
-		type_oid == TIMESTAMPOID ||
-		type_oid == TIMESTAMPTZOID ||
-		type_oid == TIMEOID ||
-		type_oid == TIMETZOID)
-	{
-		*typarray = false;
-
-peculiar_typmod:
-		switch (type_oid)
-		{
-			case INTERVALOID:
-				*typname = pstrdup("INTERVAL");
-				break;
-			case TIMESTAMPTZOID:
-				if (typemod < 0)
-				{
-					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
-					break;
-				}
-				/* otherwise, WITH TZ is added by typmod, so fall through */
-			case TIMESTAMPOID:
-				*typname = pstrdup("TIMESTAMP");
-				break;
-			case TIMETZOID:
-				if (typemod < 0)
-				{
-					*typname = pstrdup("TIME WITH TIME ZONE");
-					break;
-				}
-				/* otherwise, WITH TZ is added by typmode, so fall through */
-			case TIMEOID:
-				*typname = pstrdup("TIME");
-				break;
-		}
-		*nspid = InvalidOid;
-
-		if (typemod >= 0)
-			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
-		else
-			*typemodstr = pstrdup("");
-
-		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;
-		*typarray = true;
-
-		/*
-		 * If it's an array of one of the types with special typmod rules,
-		 * have the element type be processed as above, but now with typarray
-		 * set to true.
-		 */
-		if (type_oid == INTERVALOID ||
-			type_oid == TIMESTAMPTZOID ||
-			type_oid == TIMESTAMPOID ||
-			type_oid == TIMETZOID ||
-			type_oid == TIMEOID)
-			goto peculiar_typmod;
-	}
-	else
-		*typarray = false;
-
-	*nspid = typeform->typnamespace;
-	*typname = pstrdup(NameStr(typeform->typname));
-
-	if (typemod >= 0)
-		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
-	else
-		*typemodstr = pstrdup("");
-
-	ReleaseSysCache(tuple);
-}
-
-/*
  * This version is for use within the backend in error messages, etc.
  * One difference is that it will fail for an invalid type.
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f1d6e9a..77a5b79 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -484,8 +484,6 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
 									   deparse_context *context);
 static void get_tablesample_def(TableSampleClause *tablesample,
 								deparse_context *context);
-static void get_opclass_name(Oid opclass, Oid actual_datatype,
-							 StringInfo buf);
 static Node *processIndirection(Node *node, deparse_context *context);
 static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context);
 static char *get_relation_name(Oid relid);
@@ -499,7 +497,6 @@ static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
 static void add_cast_to(StringInfo buf, Oid typid);
 static char *generate_qualified_type_name(Oid typid);
 static text *string_to_text(char *str);
-static char *flatten_reloptions(Oid relid);
 static void get_reloptions(StringInfo buf, Datum reloptions);
 static void get_json_path_spec(Node *path_spec, deparse_context *context,
 							   bool showimplicit);
@@ -558,8 +555,8 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS)
  * NIL, signalling NOTHING) in actions.
  */
 void
-pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
-					   char **whereClause, List **actions)
+pg_get_ruledef_detailed(Datum ev_qual, Datum ev_action,
+						char **whereClause, List **actions)
 {
 	int prettyFlags = 0;
 	char *qualstr = TextDatumGetCString(ev_qual);
@@ -1627,250 +1624,6 @@ 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;
-	IndexAmRoutine *amroutine;
-	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);
-
-	/* Fetch the index AM's API struct */
-	amroutine = GetIndexAmRoutine(amrec->amhandler);
-
-	/*
-	 * 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, NULL))
-	{
-		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_attname(indrelid, attnum, false);
-			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(indexprs, 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 (amroutine->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, NULL))
-	{
-		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_querydef
@@ -12331,7 +12084,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
  * actual_datatype.  (If you don't want this behavior, just pass
  * InvalidOid for actual_datatype.)
  */
-static void
+void
 get_opclass_name(Oid opclass, Oid actual_datatype,
 				 StringInfo buf)
 {
@@ -13112,7 +12865,7 @@ get_reloptions(StringInfo buf, Datum reloptions)
 /*
  * Generate a C string representing a relation's reloptions, or NULL if none.
  */
-static char *
+char *
 flatten_reloptions(Oid relid)
 {
 	char	   *result = NULL;
@@ -13180,73 +12933,3 @@ get_range_partbound_string(List *bound_datums)
 
 	return buf->data;
 }
-
-
-/*
- * 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;
-}
-
-/*
- * Return the default value of a domain.
- */
-char *
-DomainGetDefault(HeapTuple domTup)
-{
-	Datum   def;
-	Node   *defval;
-	char   *defstr;
-	bool    isnull;
-
-	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
-						  &isnull);
-	if (isnull)
-		elog(ERROR, "domain \"%s\" does not have a default value",
-			NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
-	defval = stringToNode(TextDatumGetCString(def));
-	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
-									   false, false, 0, 0);
-
-	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/builtins.h b/src/include/utils/builtins.h
index cde659f..d14bb8f 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -121,10 +121,6 @@ extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 
 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 char *quote_literal_cstr(const char *rawstr);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 83c242d..e359c90 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -24,17 +24,11 @@ struct PlannedStmt;
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 									   Node *whenClause, bool pretty);
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 extern char *pg_get_constraintdef_command(Oid constraintId);
-extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+extern void pg_get_ruledef_detailed(Datum ev_qual, Datum ev_action,
 								   char **whereClause, List **actions);
 extern char *pg_get_viewdef_internal(Oid viewoid);
 extern char *pg_get_createtableas_def(Query *query);
@@ -53,13 +47,10 @@ extern List *set_deparse_context_plan(List *dpcontext,
 									  struct Plan *plan, List *ancestors);
 extern List *select_rtable_names_for_explain(List *rtable,
 											 Bitmapset *rels_used);
+extern void get_opclass_name(Oid opclass, Oid actual_datatype,
+							 StringInfo buf);
 extern char *generate_collation_name(Oid collid);
-extern List *FunctionGetDefaults(text *proargdefaults);
-
-extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
-                        List *dpcontext);
-
-extern char *DomainGetDefault(HeapTuple domTup);
+extern char *flatten_reloptions(Oid relid);
 extern char *generate_opclass_name(Oid opclass);
 extern char *get_range_partbound_string(List *bound_datums);
 
-- 
1.8.3.1

0003-Add-optional-constraint-name-to-field-IndexStmt.patchapplication/octet-stream; name=0003-Add-optional-constraint-name-to-field-IndexStmt.patchDownload
From c21130a0c1818cc70c2b5f82c6deec40bbd01534 Mon Sep 17 00:00:00 2001
From: Ajin Cherian <ajinc@fast.au.fujitsu.com>
Date: Fri, 15 Apr 2022 11:17:39 -0400
Subject: [PATCH 3/3] Add optional constraint name to field IndexStmt

And keep the original index name field intact when renaming.

This appears to be the only way to trace back the original index name
before it was renamed to match the constraint name.  We need this
information in the ddl_deparse module.
---
 src/backend/commands/tablecmds.c   |  4 ++--
 src/backend/nodes/copyfuncs.c      |  1 +
 src/backend/nodes/equalfuncs.c     |  1 +
 src/backend/nodes/outfuncs.c       |  1 +
 src/backend/parser/parse_utilcmd.c | 16 ++++++++++++++--
 src/include/nodes/parsenodes.h     |  1 +
 6 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 90edd0b..af2c963 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8692,7 +8692,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 
 	indexRel = index_open(index_oid, AccessShareLock);
 
-	indexName = pstrdup(RelationGetRelationName(indexRel));
+	indexName = stmt->idxname;
 
 	indexInfo = BuildIndexInfo(indexRel);
 
@@ -8707,7 +8707,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	 * explicitly gives some other name for the constraint, rename the index
 	 * to match.
 	 */
-	constraintName = stmt->idxname;
+	constraintName = stmt->conname;
 	if (constraintName == NULL)
 		constraintName = indexName;
 	else if (strcmp(constraintName, indexName) != 0)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 836f427..88254a5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4182,6 +4182,7 @@ _copyIndexStmt(const IndexStmt *from)
 	IndexStmt  *newnode = makeNode(IndexStmt);
 
 	COPY_STRING_FIELD(idxname);
+	COPY_STRING_FIELD(conname);
 	COPY_NODE_FIELD(relation);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_STRING_FIELD(tableSpace);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e013c1b..888ab32 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1740,6 +1740,7 @@ static bool
 _equalIndexStmt(const IndexStmt *a, const IndexStmt *b)
 {
 	COMPARE_STRING_FIELD(idxname);
+	COMPARE_STRING_FIELD(conname);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_STRING_FIELD(tableSpace);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d5f5e76..a8dedc4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2909,6 +2909,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
 	WRITE_NODE_TYPE("INDEXSTMT");
 
 	WRITE_STRING_FIELD(idxname);
+	WRITE_STRING_FIELD(conname);
 	WRITE_NODE_FIELD(relation);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_STRING_FIELD(tableSpace);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2826559..9139643 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2128,6 +2128,10 @@ transformIndexConstraints(CreateStmtContext *cxt)
 				 */
 				if (priorindex->idxname == NULL)
 					priorindex->idxname = index->idxname;
+
+				if (priorindex->conname == NULL)
+					priorindex->conname = index->conname;
+
 				keep = false;
 				break;
 			}
@@ -2186,10 +2190,17 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 	index->deferrable = constraint->deferrable;
 	index->initdeferred = constraint->initdeferred;
 
+	index->idxname = NULL; /* by default, DefineIndex will choose name */
+
 	if (constraint->conname != NULL)
-		index->idxname = pstrdup(constraint->conname);
+	{
+		index->conname = pstrdup(constraint->conname);
+
+		if (constraint->indexname == NULL)
+			index->idxname = pstrdup(constraint->conname);
+	}
 	else
-		index->idxname = NULL;	/* DefineIndex will choose name */
+		index->conname = NULL;
 
 	index->relation = cxt->relation;
 	index->accessMethod = constraint->access_method ? constraint->access_method : DEFAULT_INDEX_TYPE;
@@ -2384,6 +2395,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 		relation_close(index_rel, NoLock);
 
 		index->indexOid = index_oid;
+		index->idxname = pstrdup(index_name);
 	}
 
 	/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index da02658..dba0ab4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3240,6 +3240,7 @@ typedef struct IndexStmt
 {
 	NodeTag		type;
 	char	   *idxname;		/* name of new index, or NULL for default */
+	char	   *conname;		/* corresponding constraint name, or NULL */
 	RangeVar   *relation;		/* relation to build index on */
 	char	   *accessMethod;	/* name of access method (eg. btree) */
 	char	   *tableSpace;		/* tablespace, or NULL for default */
-- 
1.8.3.1

0001-ddl_deparse-core-support.patchapplication/octet-stream; name=0001-ddl_deparse-core-support.patchDownload
From e3b589472cc8e299646f2bef3fbaeb486df37f9b Mon Sep 17 00:00:00 2001
From: Ajin Cherian <ajinc@fast.au.fujitsu.com>
Date: Wed, 13 Apr 2022 00:08:10 -0400
Subject: [PATCH 1/3] ddl_deparse core support

---
 src/backend/catalog/objectaddress.c |   4 +-
 src/backend/commands/seclabel.c     |   5 +
 src/backend/commands/sequence.c     |  35 +++
 src/backend/utils/adt/format_type.c | 125 +++++++++
 src/backend/utils/adt/regproc.c     | 104 +++++--
 src/backend/utils/adt/ruleutils.c   | 529 ++++++++++++++++++++++++++++++++----
 src/include/commands/sequence.h     |   1 +
 src/include/utils/builtins.h        |   5 +
 src/include/utils/regproc.h         |   3 +-
 src/include/utils/ruleutils.h       |  23 +-
 10 files changed, 746 insertions(+), 88 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index ac60435..36dfb0f 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2980,7 +2980,7 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 			{
 				bits16		flags = FORMAT_PROC_INVALID_AS_NULL;
 				char	   *proname = format_procedure_extended(object->objectId,
-																flags);
+																flags, false);
 
 				if (proname == NULL)
 					break;
@@ -4826,7 +4826,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				bits16		flags = FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_INVALID_AS_NULL;
 				char	   *proname = format_procedure_extended(object->objectId,
-																flags);
+																flags, false);
 
 				if (proname == NULL)
 					break;
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7ae19b9..c1530a1 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -134,6 +134,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("must specify provider when multiple security label providers have been loaded")));
 		provider = (LabelProvider *) linitial(label_provider_list);
+		/*
+		 * Set the provider in the statement so that DDL deparse can use
+		 * provider explicitly in generated statement.
+		 */
+		stmt->provider = (char *) provider->provider_name;
 	}
 	else
 	{
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ddf219b..bdd081c 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1699,6 +1699,41 @@ process_owned_by(Relation seqrel, List *owned_by, bool for_identity)
 		relation_close(tablerel, NoLock);
 }
 
+/*
+ * Return sequence parameters, detailed
+ */
+
+Form_pg_sequence_data
+get_sequence_values(Oid sequenceId)
+{
+	Buffer      buf;
+	SeqTable    elm;
+	Relation    seqrel;
+	HeapTupleData seqtuple;
+	Form_pg_sequence_data seq;
+	Form_pg_sequence_data 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(seqrel, &buf, &seqtuple);
+
+	memcpy(retSeq, seq, sizeof(FormData_pg_sequence_data));
+
+	UnlockReleaseBuffer(buf);
+	relation_close(seqrel, NoLock);
+
+	return retSeq;
+}
 
 /*
  * Return sequence parameters in a list of the form created by the parser.
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 060fd7e..42e3af3 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -334,6 +334,131 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 }
 
 /*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name in this case.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *typarray)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID ||
+		type_oid == TIMEOID ||
+		type_oid == TIMETZOID)
+	{
+		*typarray = false;
+
+peculiar_typmod:
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPTZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmod, so fall through */
+			case TIMESTAMPOID:
+				*typname = pstrdup("TIMESTAMP");
+				break;
+			case TIMETZOID:
+				if (typemod < 0)
+				{
+					*typname = pstrdup("TIME WITH TIME ZONE");
+					break;
+				}
+				/* otherwise, WITH TZ is added by typmode, so fall through */
+			case TIMEOID:
+				*typname = pstrdup("TIME");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		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;
+		*typarray = true;
+
+		/*
+		 * If it's an array of one of the types with special typmod rules,
+		 * have the element type be processed as above, but now with typarray
+		 * set to true.
+		 */
+		if (type_oid == INTERVALOID ||
+			type_oid == TIMESTAMPTZOID ||
+			type_oid == TIMESTAMPOID ||
+			type_oid == TIMETZOID ||
+			type_oid == TIMEOID)
+			goto peculiar_typmod;
+	}
+	else
+		*typarray = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
+/*
  * This version is for use within the backend in error messages, etc.
  * One difference is that it will fail for an invalid type.
  *
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 6d4c1c2..4df3256 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -43,7 +43,8 @@
 
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 								 List **names, int *nargs, Oid *argtypes);
-
+static void format_procedure_args_internal(Form_pg_proc procform,
+							StringInfo buf, bool force_qualify);
 
 /*****************************************************************************
  *	 USER I/O ROUTINES														 *
@@ -322,13 +323,37 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_extended(procedure_oid, 0);
+	return format_procedure_extended(procedure_oid, 0, false);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
+	return format_procedure_extended(procedure_oid, true, false);
+}
+
+/*
+ * format_procedure_args   - converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+	StringInfoData buf;
+	HeapTuple   proctup;
+	Form_pg_proc procform;
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+
+	initStringInfo(&buf);
+	format_procedure_args_internal(procform, &buf, force_qualify);
+
+	ReleaseSysCache(proctup);
+
+	return buf.data;
 }
 
 /*
@@ -347,10 +372,13 @@ format_procedure_qualified(Oid procedure_oid)
  *			always schema-qualify procedure names, regardless of search_path
  */
 char *
-format_procedure_extended(Oid procedure_oid, bits16 flags)
+format_procedure_extended(Oid procedure_oid, bits16 flags, bool args_only)
 {
 	char	   *result;
 	HeapTuple	proctup;
+	bool		force_qualify;
+
+	force_qualify = (flags & FORMAT_PROC_FORCE_QUALIFY) != 0;
 
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
@@ -358,8 +386,6 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
 	{
 		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
 		char	   *proname = NameStr(procform->proname);
-		int			nargs = procform->pronargs;
-		int			i;
 		char	   *nspname;
 		StringInfoData buf;
 
@@ -368,30 +394,25 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
 
 		initStringInfo(&buf);
 
-		/*
-		 * Would this proc be found (given the right args) by regprocedurein?
-		 * If not, or if caller requests it, we need to qualify it.
-		 */
-		if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
-			FunctionIsVisible(procedure_oid))
-			nspname = NULL;
-		else
-			nspname = get_namespace_name(procform->pronamespace);
-
-		appendStringInfo(&buf, "%s(",
-						 quote_qualified_identifier(nspname, proname));
-		for (i = 0; i < nargs; i++)
+		if (!args_only)
 		{
-			Oid			thisargtype = procform->proargtypes.values[i];
-
-			if (i > 0)
-				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf,
-								   (flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ?
-								   format_type_be_qualified(thisargtype) :
-								   format_type_be(thisargtype));
+
+			/*
+		 	 * Would this proc be found (given the right args) by regprocedurein?
+		 	 * If not, or if caller requests it, we need to qualify it.
+		 	 */
+			if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
+				FunctionIsVisible(procedure_oid))
+				nspname = NULL;
+			else
+				nspname = get_namespace_name(procform->pronamespace);
+
+			appendStringInfo(&buf, "%s",
+							 quote_qualified_identifier(nspname, proname));
 		}
-		appendStringInfoChar(&buf, ')');
+
+		/* add the attributes */
+		format_procedure_args_internal(procform, &buf, force_qualify);
 
 		result = buf.data;
 
@@ -413,6 +434,33 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
 }
 
 /*
+ * Append the parenthised arguments of the given pg_proc row into the output
+ * buffer.  force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+							   bool force_qualify)
+{
+	int			i;
+	int			nargs = procform->pronargs;
+
+	appendStringInfoChar(buf, '(');
+	for (i = 0; i < nargs; i++)
+	{
+		Oid			thisargtype = procform->proargtypes.values[i];
+
+		if (i > 0)
+			appendStringInfoChar(buf, ',');
+		appendStringInfoString(buf,
+							   force_qualify ?
+							   format_type_be_qualified(thisargtype) :
+							   format_type_be(thisargtype));
+	}
+	appendStringInfoChar(buf, ')');
+}
+
+/*
  * Output an objname/objargs representation for the procedure with the
  * given OID.  If it doesn't exist, an error is thrown.
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3296ad0..f1d6e9a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -551,6 +551,78 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(string_to_text(res));
 }
 
+/*
+ * 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)
@@ -742,6 +814,14 @@ pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(string_to_text(res));
 }
 
+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
  */
@@ -824,6 +904,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	return buf.data;
 }
 
+/*
+ * get_createtableas_def   - Get the accompanying query for a CREATE TABLE AS
+ */
+char *
+pg_get_createtableas_def(Query *query)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(query, &buf, NIL, NULL, 0, 0, 0);
+
+	return buf.data;
+}
+
 /* ----------
  * pg_get_triggerdef		- Get the definition of a trigger
  * ----------
@@ -1021,65 +1116,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	if (!isnull)
 	{
 		Node	   *qual;
-		char		relkind;
-		deparse_context context;
-		deparse_namespace dpns;
-		RangeTblEntry *oldrte;
-		RangeTblEntry *newrte;
-
-		appendStringInfoString(&buf, "WHEN (");
+		char       *qualstr;
 
 		qual = stringToNode(TextDatumGetCString(value));
+		qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
 
-		relkind = get_rel_relkind(trigrec->tgrelid);
-
-		/* Build minimal OLD and NEW RTEs for the rel */
-		oldrte = makeNode(RangeTblEntry);
-		oldrte->rtekind = RTE_RELATION;
-		oldrte->relid = trigrec->tgrelid;
-		oldrte->relkind = relkind;
-		oldrte->rellockmode = AccessShareLock;
-		oldrte->alias = makeAlias("old", NIL);
-		oldrte->eref = oldrte->alias;
-		oldrte->lateral = false;
-		oldrte->inh = false;
-		oldrte->inFromCl = true;
-
-		newrte = makeNode(RangeTblEntry);
-		newrte->rtekind = RTE_RELATION;
-		newrte->relid = trigrec->tgrelid;
-		newrte->relkind = relkind;
-		newrte->rellockmode = AccessShareLock;
-		newrte->alias = makeAlias("new", NIL);
-		newrte->eref = newrte->alias;
-		newrte->lateral = false;
-		newrte->inh = false;
-		newrte->inFromCl = true;
-
-		/* Build two-element rtable */
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = list_make2(oldrte, newrte);
-		dpns.subplans = NIL;
-		dpns.ctes = NIL;
-		dpns.appendrels = NULL;
-		set_rtable_names(&dpns, NIL, NULL);
-		set_simple_column_names(&dpns);
-
-		/* Set up context with one-deep namespace stack */
-		context.buf = &buf;
-		context.namespaces = list_make1(&dpns);
-		context.windowClause = NIL;
-		context.windowTList = NIL;
-		context.varprefix = true;
-		context.prettyFlags = GET_PRETTY_FLAGS(pretty);
-		context.wrapColumn = WRAP_COLUMN_DEFAULT;
-		context.indentLevel = PRETTYINDENT_STD;
-		context.special_exprkind = EXPR_KIND_NONE;
-		context.appendparents = NULL;
-
-		get_rule_expr(qual, &context, false);
-
-		appendStringInfoString(&buf, ") ");
+		appendStringInfo(&buf, "WHEN (%s) ", qualstr);
 	}
 
 	appendStringInfo(&buf, "EXECUTE FUNCTION %s(",
@@ -1120,6 +1162,64 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	return buf.data;
 }
 
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+	StringInfoData buf;
+	char        relkind;
+	deparse_context context;
+	deparse_namespace dpns;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
+
+   initStringInfo(&buf);
+
+   relkind = get_rel_relkind(trigrec->tgrelid);
+
+   /* Build minimal OLD and NEW RTEs for the rel */
+   oldrte = makeNode(RangeTblEntry);
+   oldrte->rtekind = RTE_RELATION;
+   oldrte->relid = trigrec->tgrelid;
+   oldrte->relkind = relkind;
+   oldrte->alias = makeAlias("old", NIL);
+   oldrte->eref = oldrte->alias;
+   oldrte->lateral = false;
+   oldrte->inh = false;
+   oldrte->inFromCl = true;
+
+   newrte = makeNode(RangeTblEntry);
+   newrte->rtekind = RTE_RELATION;
+   newrte->relid = trigrec->tgrelid;
+   newrte->relkind = relkind;
+   newrte->alias = makeAlias("new", NIL);
+   newrte->eref = newrte->alias;
+   newrte->lateral = false;
+   newrte->inh = false;
+   newrte->inFromCl = true;
+
+   /* Build two-element rtable */
+   memset(&dpns, 0, sizeof(dpns));
+   dpns.rtable = list_make2(oldrte, newrte);
+   dpns.ctes = NIL;
+   set_rtable_names(&dpns, NIL, NULL);
+   set_simple_column_names(&dpns);
+
+   /* Set up context with one-deep namespace stack */
+   context.buf = &buf;
+   context.namespaces = list_make1(&dpns);
+   context.windowClause = NIL;
+   context.windowTList = NIL;
+   context.varprefix = true;
+   context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+   context.wrapColumn = WRAP_COLUMN_DEFAULT;
+   context.indentLevel = PRETTYINDENT_STD;
+   context.special_exprkind = EXPR_KIND_NONE;
+
+   get_rule_expr(whenClause, &context, false);
+
+   return buf.data;
+}
+
 /* ----------
  * pg_get_indexdef			- Get the definition of an index
  *
@@ -1207,6 +1307,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,
@@ -1525,6 +1627,251 @@ 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;
+	IndexAmRoutine *amroutine;
+	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);
+
+	/* Fetch the index AM's API struct */
+	amroutine = GetIndexAmRoutine(amrec->amhandler);
+
+	/*
+	 * 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, NULL))
+	{
+		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_attname(indrelid, attnum, false);
+			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(indexprs, 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 (amroutine->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, NULL))
+	{
+		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_querydef
  *
@@ -12833,3 +13180,73 @@ get_range_partbound_string(List *bound_datums)
 
 	return buf->data;
 }
+
+
+/*
+ * 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;
+}
+
+/*
+ * Return the default value of a domain.
+ */
+char *
+DomainGetDefault(HeapTuple domTup)
+{
+	Datum   def;
+	Node   *defval;
+	char   *defstr;
+	bool    isnull;
+
+	def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "domain \"%s\" does not have a default value",
+			NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+	defval = stringToNode(TextDatumGetCString(def));
+	defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */,
+									   false, false, 0, 0);
+
+	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/commands/sequence.h b/src/include/commands/sequence.h
index 9da2300..2875106 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -54,6 +54,7 @@ typedef struct xl_seq_rec
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
+extern Form_pg_sequence_data get_sequence_values(Oid sequenceId);
 
 extern ObjectAddress DefineSequence(ParseState *pstate, CreateSeqStmt *stmt);
 extern ObjectAddress AlterSequence(ParseState *pstate, AlterSeqStmt *stmt);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545..cde659f 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -115,11 +115,16 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 #define FORMAT_TYPE_INVALID_AS_NULL	0x08	/* NULL if undefined */
 extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
 extern char *format_type_be(Oid type_oid);
 extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 
 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 char *quote_literal_cstr(const char *rawstr);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index a36ceba..f4d543d 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -18,7 +18,8 @@
 /* Control flags for format_procedure_extended */
 #define FORMAT_PROC_INVALID_AS_NULL	0x01	/* NULL if undefined */
 #define FORMAT_PROC_FORCE_QUALIFY	0x02	/* force qualification */
-extern char *format_procedure_extended(Oid procedure_oid, bits16 flags);
+extern char *format_procedure_extended(Oid procedure_oid, bits16 flags,
+	   			bool args_only);
 
 /* Control flags for format_operator_extended */
 #define FORMAT_OPERATOR_INVALID_AS_NULL	0x01	/* NULL if undefined */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 7d48971..83c242d 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,6 +13,7 @@
 #ifndef RULEUTILS_H
 #define RULEUTILS_H
 
+#include "catalog/pg_trigger.h"
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
@@ -23,12 +24,26 @@ struct PlannedStmt;
 
 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 char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+									   Node *whenClause, bool pretty);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+extern char *pg_get_constraintdef_command(Oid constraintId);
+extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+								   char **whereClause, List **actions);
+extern char *pg_get_viewdef_internal(Oid viewoid);
+extern char *pg_get_createtableas_def(Query *query);
+
 extern char *pg_get_querydef(Query *query, bool pretty);
 
 extern char *pg_get_partkeydef_columns(Oid relid, bool pretty);
 extern char *pg_get_partconstrdef_string(Oid partitionId, char *aliasname);
 
-extern char *pg_get_constraintdef_command(Oid constraintId);
 extern char *deparse_expression(Node *expr, List *dpcontext,
 								bool forceprefix, bool showimplicit);
 extern List *deparse_context_for(const char *aliasname, Oid relid);
@@ -39,6 +54,12 @@ extern List *set_deparse_context_plan(List *dpcontext,
 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);
+
+extern char *DomainGetDefault(HeapTuple domTup);
 extern char *generate_opclass_name(Oid opclass);
 extern char *get_range_partbound_string(List *bound_datums);
 
-- 
1.8.3.1

#68Ajin Cherian
itsajin@gmail.com
In reply to: Peter Eisentraut (#66)
Re: deparsing utility commands

On Thu, Apr 14, 2022 at 12:19 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:

The patch you posted contains neither a detailed commit message nor
documentation or test changes, so it's impossible to tell what it's
supposed to do.

Sorry, I was only rebasing the patches and have kept the same commit
messages as previously present.

regards,
Ajin Cherian