Re: Add CREATE support to event triggers

Started by Alvaro Herreraover 11 years ago45 messages
#1Alvaro Herrera
alvherre@2ndquadrant.com
30 attachment(s)

Michael Paquier wrote:

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

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

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

You're right. Dropped this patch.

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

I had it that way initially, but decided against it, because it creates
too much churn; there are way too many .c files that depend on the
quoting stuff. I don't want to repeat the htup_details.h disaster ..

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

Fixed.

Patch 9:
1) It needs a rebase, AlterTableMoveAllStmt has been renamed to
AlterTableMoveAllStmt by commit 3c4cf08

Fixed.

2) Table summarizing event trigger firing needs to be updated with the
new command supported (src/sgml/event-trigger.sgml)

Will add.

Patch 10, similar problems as patch 9:
1) Needs a rebase

Done.

2) table summarizing supported commands should be updated.

Will add.

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

Actually, I think I would commit most of this stuff not in the same way
I'm submitting; I've split it up to ease review only.

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

Not real sure about this. Being able to do the whole thing in one fell
swoop seems more sensible to me. Did you need each of those things
separately, or together?

Patch 12: patch applies correctly.
Form_pg_sequence is already exposed in sequence.h even if it is only
used in sequence.c, so yes it seems to be the correct way to do it
here assuming that we need this data to rebuild a DDL. Why is
ACL_UPDATE needed when checking permissions? This new routine only
reads the values and does not update it. And a confirmation: ACL_USAGE
is used to make this routine usable for PL languages in this case,
right?

Hm, I guess I just copied it from some other routine. Will fix.

I think that you should mention at the top of get_sequence_values that
it returns a palloc'd result, and that it is the responsibility of
caller to free it.

Will add.

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

It's needed in MSVC build; I merged it with the next patch, which
actually uses it. The point is to detect whether a command is being
executed as part of an extension; we set a flag in the output for this.
In BDR, we emit only the CREATE EXTENSION command, not the individual
commands that the extension creates. I would guess that most other
replication systems will want to do the same.

I attach a rebased version of this. The patch structure is a bit
different; patch 4 (which used to be 14) introduces the infrastructure
for DDL deparsing to JSON, but no users of it; patch 5 introduces the
first few commands that actually produce deparse output.

I will add this patch series to the next commitfest. Thanks for
reviewing.

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

Attachments:

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

---
 src/backend/tcop/deparse_utility.c | 114 +++++++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c         |   1 +
 2 files changed, 115 insertions(+)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index c88b893..de63baa 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2933,6 +2933,116 @@ 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 char *
+deparse_AlterFunction(Oid objectId, Node *parsetree)
+{
+	AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree;
+	ObjTree	   *alterFunc;
+	ObjTree	   *sign;
+	char	   *command;
+	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(NULL, 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}s", 1,
+								 "cost", ObjTypeString,
+								 psprintf("%f", defGetNumeric(defel)));
+		}
+		else if (strcmp(defel->defname, "rows") == 0)
+		{
+			tmp = new_objtree_VA("ROWS %{rows}s", 0);
+			if (defGetNumeric(defel) == 0)
+				append_bool_object(tmp, "present", false);
+			else
+				append_string_object(tmp, "rows",
+									 psprintf("%f", defGetNumeric(defel)));
+		}
+
+		if (tmp)
+			elems = lappend(elems, new_object_object(NULL, tmp));
+	}
+
+	append_array_object(alterFunc, "definition", elems);
+
+	ReleaseSysCache(procTup);
+	command = jsonize_objtree(alterFunc);
+	free_objtree(alterFunc);
+
+	return command;
+}
+
+/*
  * Return the given object type as a string.
  */
 static const char *
@@ -4551,6 +4661,10 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			command = deparse_CreateFunction(objectId, parsetree);
 			break;
 
+		case T_AlterFunctionStmt:
+			command = deparse_AlterFunction(objectId, parsetree);
+			break;
+
 		case T_RuleStmt:
 			command = deparse_RuleStmt(objectId, parsetree);
 			break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 92fb46e..7c0b271 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1281,6 +1281,7 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
 				objectId = AlterFunction((AlterFunctionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-- 
1.9.1

0029-deparse-Support-COMMENT-ON.patchtext/x-diff; charset=us-asciiDownload
>From 9fec4a4e81313e08c002ddbb38207fa41213dee8 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 23 Sep 2014 17:39:03 -0300
Subject: [PATCH 29/30] deparse: Support COMMENT ON

---
 src/backend/commands/comment.c       |  5 ++++-
 src/backend/commands/event_trigger.c |  1 +
 src/backend/tcop/deparse_utility.c   | 31 +++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           | 28 ++++++++++++++++++++++++----
 src/include/commands/comment.h       |  2 +-
 5 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index 34f6cae..59781e5 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 b7d879a..0c0f926 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -257,6 +257,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/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index de63baa..9c02626 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3886,6 +3886,33 @@ deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	ObjectAddress addr;
+	char	   *fmt;
+	char	   *command;
+
+	fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}L",
+				   stringify_objtype(node->objtype));
+	comment = new_objtree_VA(fmt, 0);
+	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));
+
+	command = jsonize_objtree(comment);
+	free_objtree(comment);
+
+	return command;
+}
+
+static char *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
 	HeapTuple   conTup;
@@ -4725,6 +4752,10 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			command = deparse_AlterOwnerStmt(objectId, parsetree);
 			break;
 
+		case T_CommentStmt:
+			command = deparse_CommentStmt(objectId, objectSubId, parsetree);
+			break;
+
 		case T_GrantStmt:
 			/* GRANT/REVOKE are implemented separately */
 			elog(ERROR, "unexpected node type T_GrantStmt");
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 7c0b271..388157f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -512,10 +512,6 @@ standard_ProcessUtility(Node *parsetree,
 			ExecuteTruncate((TruncateStmt *) parsetree);
 			break;
 
-		case T_CommentStmt:
-			CommentObject((CommentStmt *) parsetree);
-			break;
-
 		case T_SecLabelStmt:
 			ExecSecLabelStmt((SecLabelStmt *) parsetree);
 			break;
@@ -828,6 +824,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,
@@ -1390,6 +1399,17 @@ ProcessUtilitySlow(Node *parsetree,
 										 parsetree);
 				break;
 
+			case T_CommentStmt:
+				{
+					uint32	objectSubId;
+
+					objectId = CommentObject((CommentStmt *) parsetree, &objectSubId);
+					EventTriggerStashCommand(objectId, objectSubId,
+											 ((CommentStmt *) parsetree)->objtype,
+											 parsetree);
+					break;
+				}
+
 			case T_GrantStmt:
 				/* command is stashed in ExecuteGrantStmt_oids */
 				ExecuteGrantStmt((GrantStmt *) parsetree);
diff --git a/src/include/commands/comment.h b/src/include/commands/comment.h
index 05fe0c6..46a6fa9 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);
 
-- 
1.9.1

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

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

diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 850008b..8849c08 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -28,6 +28,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/tqual.h"
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8d9eeb9..c8434f4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -69,6 +69,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..49963ff 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -28,6 +28,7 @@
 #include "utils/json.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/tuplesort.h"
 #include "utils/xml.h"
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0385404..0dee462 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -85,6 +85,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/relcache.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index ad364ef..55a6881 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -72,6 +72,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 4eeb631..67539ec 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -35,6 +35,7 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/timestamp.h"
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9ed7b71..18d9b63 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -55,6 +55,7 @@
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index d88e7a3..fb1b4a4 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -676,13 +676,10 @@ extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
-extern char *pg_get_indexdef_string(Oid indexrelid);
-extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS);
-extern char *pg_get_constraintdef_string(Oid constraintId);
 extern Datum pg_get_expr(PG_FUNCTION_ARGS);
 extern Datum pg_get_expr_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_userbyid(PG_FUNCTION_ARGS);
@@ -692,17 +689,9 @@ extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_arg_default(PG_FUNCTION_ARGS);
-extern char *deparse_expression(Node *expr, List *dpcontext,
-				   bool forceprefix, bool showimplicit);
-extern List *deparse_context_for(const char *aliasname, Oid relid);
-extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
-							  List *rtable, List *rtable_names);
-extern List *select_rtable_names_for_explain(List *rtable,
-								Bitmapset *rels_used);
 extern const char *quote_identifier(const char *ident);
 extern char *quote_qualified_identifier(const char *qualifier,
 						   const char *ident);
-extern char *generate_collation_name(Oid collid);
 
 
 /* tid.c */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
new file mode 100644
index 0000000..520b066
--- /dev/null
+++ b/src/include/utils/ruleutils.h
@@ -0,0 +1,34 @@
+/*-------------------------------------------------------------------------
+ *
+ * ruleutils.h
+ *		Declarations for ruleutils.c
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/ruleutils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef RULEUTILS_H
+#define RULEUTILS_H
+
+#include "nodes/nodes.h"
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+
+extern char *pg_get_indexdef_string(Oid indexrelid);
+extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
+
+extern char *pg_get_constraintdef_string(Oid constraintId);
+extern char *deparse_expression(Node *expr, List *dpcontext,
+				   bool forceprefix, bool showimplicit);
+extern List *deparse_context_for(const char *aliasname, Oid relid);
+extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
+							  List *rtable, List *rtable_names);
+extern List *select_rtable_names_for_explain(List *rtable,
+								Bitmapset *rels_used);
+extern char *generate_collation_name(Oid collid);
+
+#endif	/* RULEUTILS_H */
-- 
1.9.1

0002-deparse-core-have-ALTER-TABLE-return-OIDs-and-col-of.patchtext/x-diff; charset=us-asciiDownload
>From 4f1e98ed834d55adaf7833d928e9c2014c110dc2 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/30] deparse/core: have ALTER TABLE return OIDs and col# of
 affected objs

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

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c8434f4..02cba59 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -96,7 +96,7 @@ static Oid AddNewRelationType(const char *typeName,
 				   Oid new_row_type,
 				   Oid new_array_type);
 static void RelationRemoveInheritance(Oid relid);
-static void StoreRelCheck(Relation rel, char *ccname, Node *expr,
+static Oid StoreRelCheck(Relation rel, char *ccname, Node *expr,
 			  bool is_validated, bool is_local, int inhcount,
 			  bool is_no_inherit, bool is_internal);
 static void StoreConstraints(Relation rel, List *cooked_constraints,
@@ -1843,7 +1843,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)
 {
@@ -1948,6 +1948,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	 */
 	InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
 								  RelationGetRelid(rel), attnum, is_internal);
+
+	return attrdefOid;
 }
 
 /*
@@ -1955,8 +1957,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)
@@ -1966,6 +1970,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
+	Oid			constrOid;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2017,7 +2022,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 */
@@ -2048,11 +2053,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
@@ -2064,7 +2073,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 */
 
 	/*
@@ -2081,12 +2090,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:
@@ -2174,6 +2185,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,
@@ -2194,10 +2206,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;
@@ -2217,6 +2230,7 @@ AddRelationNewConstraints(Relation rel,
 	{
 		Constraint *cdef = (Constraint *) lfirst(cell);
 		char	   *ccname;
+		Oid			constrOid;
 
 		if (cdef->contype != CONSTR_CHECK)
 			continue;
@@ -2319,13 +2333,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 ee10594..b424f73 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1096,7 +1096,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
@@ -1113,7 +1114,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,
@@ -1301,6 +1302,8 @@ index_constraint_create(Relation heapRelation,
 		heap_freetuple(indexTuple);
 		heap_close(pg_index, RowExclusiveLock);
 	}
+
+	return conOid;
 }
 
 /*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 041f5ad..f04de42 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -40,6 +40,8 @@
  * Subsidiary records (such as triggers or indexes to implement the
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
+ *
+ * The new constraint's OID is returned.
  */
 Oid
 CreateConstraintEntry(const char *constraintName,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0dee462..add3ab9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -46,6 +46,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -280,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);
@@ -322,26 +323,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);
@@ -349,20 +350,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,
@@ -373,9 +374,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);
@@ -388,7 +389,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);
@@ -621,6 +622,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 +1734,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;
@@ -3382,78 +3385,91 @@ static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
+	AttrNumber colno = InvalidAttrNumber;
+	Oid			newoid = InvalidOid;
+
 	switch (cmd->subtype)
 	{
 		case AT_AddColumn:		/* ADD COLUMN */
 		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
 										 * VIEW */
-			ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
-							false, false, false, lockmode);
+			colno = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+									false, false, false, lockmode);
 			break;
 		case AT_AddColumnRecurse:
-			ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
-							false, true, false, lockmode);
+			colno = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+									false, true, false, lockmode);
 			break;
 		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
-			ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
-			ATExecDropNotNull(rel, cmd->name, lockmode);
+			colno = ATExecDropNotNull(rel, cmd->name, lockmode);
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
-			ATExecSetNotNull(tab, rel, cmd->name, lockmode);
+			colno = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
 			break;
 		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
-			ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
-			ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
+			colno = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
 			break;
 		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
-			ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
+			colno = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
 			break;
 		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
-			ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
 			ATExecDropColumn(wqueue, rel, cmd->name,
-					 cmd->behavior, false, false, cmd->missing_ok, lockmode);
+							 cmd->behavior, false, false,
+							 cmd->missing_ok, lockmode);
 			break;
 		case AT_DropColumnRecurse:		/* DROP COLUMN with recursion */
 			ATExecDropColumn(wqueue, rel, cmd->name,
-					  cmd->behavior, true, false, cmd->missing_ok, lockmode);
+							 cmd->behavior, true, false,
+							 cmd->missing_ok, lockmode);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
-			ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false, lockmode);
+			newoid = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
+									lockmode);
 			break;
 		case AT_ReAddIndex:		/* ADD INDEX */
-			ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, lockmode);
+			newoid = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
+									lockmode);
 			break;
 		case AT_AddConstraint:	/* ADD CONSTRAINT */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								false, false, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									false, false, lockmode);
 			break;
 		case AT_AddConstraintRecurse:	/* ADD CONSTRAINT with recursion */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								true, false, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									true, false, lockmode);
 			break;
 		case AT_ReAddConstraint:		/* Re-add pre-existing check
 										 * constraint */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								false, true, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									false, true, lockmode);
 			break;
 		case AT_AddIndexConstraint:		/* ADD CONSTRAINT USING INDEX */
-			ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, lockmode);
+			newoid = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
+											  lockmode);
 			break;
 		case AT_AlterConstraint:		/* ALTER CONSTRAINT */
-			ATExecAlterConstraint(rel, cmd, false, false, lockmode);
+			newoid = ATExecAlterConstraint(rel, cmd, false, false, lockmode);
 			break;
 		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
-			ATExecValidateConstraint(rel, cmd->name, false, false, lockmode);
+			newoid = ATExecValidateConstraint(rel, cmd->name, false, false,
+											  lockmode);
 			break;
 		case AT_ValidateConstraintRecurse:		/* VALIDATE CONSTRAINT with
 												 * recursion */
-			ATExecValidateConstraint(rel, cmd->name, true, false, lockmode);
+			newoid = ATExecValidateConstraint(rel, cmd->name, true, false,
+											  lockmode);
 			break;
 		case AT_DropConstraint:	/* DROP CONSTRAINT */
 			ATExecDropConstraint(rel, cmd->name, cmd->behavior,
@@ -3466,10 +3482,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),
@@ -3477,7 +3495,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);
@@ -3578,7 +3596,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			ATExecDropOf(rel, lockmode);
 			break;
 		case AT_ReplicaIdentity:
-			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def,
+								  lockmode);
 			break;
 		case AT_EnableRowSecurity:
 			ATExecEnableRowSecurity(rel);
@@ -4573,7 +4592,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)
@@ -4658,7 +4677,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					  colDef->colname, RelationGetRelationName(rel))));
 
 			heap_close(attrdesc, RowExclusiveLock);
-			return;
+			return InvalidAttrNumber;
 		}
 	}
 
@@ -4904,6 +4923,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 		heap_close(childrel, NoLock);
 	}
+
+	return newattnum;
 }
 
 /*
@@ -5018,7 +5039,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;
@@ -5103,18 +5124,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)
 {
@@ -5158,18 +5183,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)
 {
@@ -5220,6 +5249,8 @@ ATExecColumnDefault(Relation rel, const char *colName,
 		AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
 								  false, true, false);
 	}
+
+	return attnum;
 }
 
 /*
@@ -5249,13 +5280,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);
@@ -5290,7 +5322,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\"",
@@ -5309,9 +5342,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)
 {
@@ -5319,6 +5354,7 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	HeapTuple	tuple,
 				newtuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 	Datum		datum,
 				newOptions;
 	bool		isnull;
@@ -5337,7 +5373,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\"",
@@ -5376,12 +5413,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;
@@ -5389,6 +5428,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);
@@ -5421,7 +5461,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\"",
@@ -5451,6 +5492,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return attnum;
 }
 
 
@@ -5676,7 +5719,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
  * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
  * us schedule creation of the index at the appropriate time during ALTER.
  */
-static void
+static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
@@ -5718,12 +5761,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)
 {
@@ -5733,6 +5778,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	IndexInfo  *indexInfo;
 	char	   *constraintName;
 	char		constraintType;
+	Oid			conOid;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(OidIsValid(index_oid));
@@ -5777,30 +5823,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));
 
 	/*
@@ -5811,9 +5861,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:
@@ -5844,17 +5895,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.
  *
@@ -5873,7 +5929,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)
@@ -5882,6 +5938,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)
@@ -5924,6 +5981,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 */
@@ -5939,7 +6003,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.
@@ -5947,7 +6011,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
@@ -5985,16 +6049,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)
 {
@@ -6393,6 +6460,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Close pk table, but keep lock until we've committed.
 	 */
 	heap_close(pkrel, NoLock);
+
+	return constrOid;
 }
 
 /*
@@ -6405,7 +6474,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)
 {
@@ -6416,6 +6485,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;
@@ -6475,6 +6545,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);
 
@@ -6522,6 +6594,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 /*
@@ -6532,7 +6606,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)
 {
@@ -6542,6 +6616,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);
 
@@ -6583,9 +6658,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;
 
 			/*
@@ -6600,7 +6676,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 conid);
+										 constrOid);
 			heap_close(refrel, NoLock);
 
 			/*
@@ -6681,6 +6757,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 
@@ -7767,7 +7845,7 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
 	}
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
@@ -8143,9 +8221,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 	/* Cleanup */
 	heap_freetuple(heapTup);
+
+	return attnum;
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnGenericOptions(Relation rel,
 								const char *colName,
 								List *options,
@@ -8164,9 +8244,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);
@@ -8193,7 +8274,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)));
@@ -8242,6 +8324,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	heap_close(attrel, RowExclusiveLock);
 
 	heap_freetuple(newtuple);
+
+	return attnum;
 }
 
 /*
@@ -8883,7 +8967,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;
@@ -8901,6 +8985,8 @@ ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 
 	/* And do the work */
 	mark_index_clustered(rel, indexOid, false);
+
+	return indexOid;
 }
 
 /*
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index e690e27..075c238 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -27,6 +27,7 @@ typedef struct RawColumnDefault
 typedef struct CookedConstraint
 {
 	ConstrType	contype;		/* CONSTR_DEFAULT or CONSTR_CHECK */
+	Oid			conoid;			/* OID of the new element */
 	char	   *name;			/* name, or NULL if none */
 	AttrNumber	attnum;			/* which attr (only for DEFAULT) */
 	Node	   *expr;			/* transformed default or check expr */
@@ -99,7 +100,7 @@ extern List *AddRelationNewConstraints(Relation rel,
 						  bool is_local,
 						  bool is_internal);
 
-extern void StoreAttrDefault(Relation rel, AttrNumber attnum,
+extern Oid StoreAttrDefault(Relation rel, AttrNumber attnum,
 				 Node *expr, bool is_internal);
 
 extern Node *cookDefault(ParseState *pstate,
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 006b180..acde583 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -62,7 +62,7 @@ extern Oid index_create(Relation heapRelation,
 			 bool concurrent,
 			 bool is_internal);
 
-extern void index_constraint_create(Relation heapRelation,
+extern Oid index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
 						const char *constraintName,
-- 
1.9.1

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

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

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 1b8c94b..c6770ea 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -251,6 +251,8 @@ check_ddl_tag(const char *tag)
 		pg_strcasecmp(tag, "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;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 24aa2b3..d4be750 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -547,11 +547,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);
@@ -1313,6 +1308,11 @@ ProcessUtilitySlow(Node *parsetree,
 				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_GrantStmt:
+				/* command is stashed in ExecuteGrantStmt_oids */
+				ExecuteGrantStmt((GrantStmt *) parsetree);
+				break;
+
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
 				break;
-- 
1.9.1

0004-deparse-infrastructure-needed-for-command-deparsing.patchtext/x-diff; charset=us-asciiDownload
>From 5d3459dcebd9c907e5c9d036196e87ea5c955d7a 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 04/30] deparse: infrastructure needed for command deparsing

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

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b69b75b..a2445f1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -723,6 +723,121 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 }
 
 /*
+ * Return the OID of the catalog corresponding to the given object type
+ */
+Oid
+get_objtype_catalog_oid(ObjectType objtype)
+{
+	Oid		catalog_id;
+
+	switch (objtype)
+	{
+		case OBJECT_INDEX:
+		case OBJECT_COMPOSITE:
+		case OBJECT_SEQUENCE:
+		case OBJECT_TABLE:
+		case OBJECT_VIEW:
+		case OBJECT_MATVIEW:
+		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_COLUMN:
+			catalog_id = RelationRelationId;
+			break;
+		case OBJECT_RULE:
+			catalog_id = RewriteRelationId;
+			break;
+		case OBJECT_TRIGGER:
+			catalog_id = TriggerRelationId;
+			break;
+		case OBJECT_CONSTRAINT:
+			catalog_id = ConstraintRelationId;
+			break;
+		case OBJECT_DATABASE:
+			catalog_id = DatabaseRelationId;
+			break;
+		case OBJECT_EXTENSION:
+			catalog_id = ExtensionRelationId;
+			break;
+		case OBJECT_TABLESPACE:
+			catalog_id = TableSpaceRelationId;
+			break;
+		case OBJECT_ROLE:
+			catalog_id = AuthIdRelationId;
+			break;
+		case OBJECT_SCHEMA:
+			catalog_id = NamespaceRelationId;
+			break;
+		case OBJECT_LANGUAGE:
+			catalog_id = LanguageRelationId;
+			break;
+		case OBJECT_FDW:
+			catalog_id = ForeignDataWrapperRelationId;
+			break;
+		case OBJECT_FOREIGN_SERVER:
+			catalog_id = ForeignServerRelationId;
+			break;
+		case OBJECT_USER_MAPPING:
+			catalog_id = UserMappingRelationId;
+			break;
+		case OBJECT_EVENT_TRIGGER:
+			catalog_id = EventTriggerRelationId;
+			break;
+		case OBJECT_TYPE:
+		case OBJECT_DOMAIN:
+			catalog_id = TypeRelationId;
+			break;
+		case OBJECT_ATTRIBUTE:
+			catalog_id = TypeRelationId;	/* XXX? */
+			break;
+		case OBJECT_AGGREGATE:
+			catalog_id = ProcedureRelationId;
+			break;
+		case OBJECT_FUNCTION:
+			catalog_id = ProcedureRelationId;
+			break;
+		case OBJECT_OPERATOR:
+			catalog_id = OperatorRelationId;
+			break;
+		case OBJECT_COLLATION:
+			catalog_id = CollationRelationId;
+			break;
+		case OBJECT_CONVERSION:
+			catalog_id = ConversionRelationId;
+			break;
+		case OBJECT_OPCLASS:
+			catalog_id = OperatorClassRelationId;
+			break;
+		case OBJECT_OPFAMILY:
+			catalog_id = OperatorFamilyRelationId;
+			break;
+		case OBJECT_LARGEOBJECT:
+			catalog_id = LargeObjectRelationId;
+			break;
+		case OBJECT_CAST:
+			catalog_id = CastRelationId;
+			break;
+		case OBJECT_TSPARSER:
+			catalog_id = TSParserRelationId;
+			break;
+		case OBJECT_TSDICTIONARY:
+			catalog_id = TSDictionaryRelationId;
+			break;
+		case OBJECT_TSTEMPLATE:
+			catalog_id = TSTemplateRelationId;
+			break;
+		case OBJECT_TSCONFIGURATION:
+			catalog_id = TSConfigRelationId;
+			break;
+		default:
+				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
+				/* placate compiler, in case it thinks elog might return */
+				catalog_id = InvalidOid;
+	}
+
+	/* Return the object address and the relation. */
+	return catalog_id;
+}
+
+/*
  * Find an ObjectAddress for a type of object that is identified by an
  * unqualified name.
  */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index c6770ea..3f278d9 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -25,16 +25,19 @@
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/trigger.h"
 #include "funcapi.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -48,6 +51,7 @@ typedef struct EventTriggerQueryState
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
 	MemoryContext cxt;
+	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -66,6 +70,7 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
 	{"AGGREGATE", true},
 	{"CAST", true},
@@ -923,6 +928,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
 		case OBJECT_COLUMN:
+		case OBJECT_COMPOSITE:
 		case OBJECT_CONSTRAINT:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
@@ -950,6 +956,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
+		case OBJECT_USER_MAPPING:
 		case OBJECT_VIEW:
 			return true;
 	}
@@ -1027,13 +1034,6 @@ EventTriggerBeginCompleteQuery(void)
 	EventTriggerQueryState *state;
 	MemoryContext cxt;
 
-	/*
-	 * Currently, sql_drop events are the only reason to have event trigger
-	 * state at all; so if there are none, don't install one.
-	 */
-	if (!trackDroppedObjectsNeeded())
-		return false;
-
 	cxt = AllocSetContextCreate(TopMemoryContext,
 								"event trigger state",
 								ALLOCSET_DEFAULT_MINSIZE,
@@ -1043,6 +1043,7 @@ EventTriggerBeginCompleteQuery(void)
 	state->cxt = cxt;
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
+	state->stash = NIL;
 
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
@@ -1299,3 +1300,929 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
+ * EventTriggerStashCommand
+ * 		Save data about a simple DDL command that was just executed
+ */
+void
+EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype,
+						 Node *parsetree)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_Basic;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.basic.objectId = objectId;
+	stashed->d.basic.objtype = objtype;
+	stashed->d.basic.objectSubId = objectSubId;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+Datum
+pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_get_creation_commands()")));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->stash)
+	{
+		StashedCommand *cmd = lfirst(lc);
+		char	   *command;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid; in those cases, return an empty
+		 * command instead of trying to soldier on.
+		 *
+		 * XXX an alternative would be to look up the Oid of the existing
+		 * object and run the deparse with that.  But since the parse tree
+		 * might be different from the one that created the object in the first
+		 * place, we might not end up in a consistent state anyway.
+		 */
+		if (cmd->type == SCT_Basic &&
+			!OidIsValid(cmd->d.basic.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_Basic)
+			{
+				Oid			classId;
+				Oid			objId;
+				uint32		objSubId;
+				const char *tag;
+				char	   *identity;
+				char	   *type;
+				char	   *schema = NULL;
+
+				if (cmd->type == SCT_Basic)
+				{
+					classId = get_objtype_catalog_oid(cmd->d.basic.objtype);
+					objId = cmd->d.basic.objectId;
+					objSubId = cmd->d.basic.objectSubId;
+				}
+
+				tag = CreateCommandTag(cmd->parsetree);
+				addr.classId = classId;
+				addr.objectId = objId;
+				addr.objectSubId = objSubId;
+
+				type = getObjectTypeDescription(&addr);
+				identity = getObjectIdentity(&addr);
+
+				/*
+				 * Obtain schema name, if any ("pg_temp" if a temp object)
+				 */
+				if (is_objectclass_supported(addr.classId))
+				{
+					AttrNumber	nspAttnum;
+
+					nspAttnum = get_object_attnum_namespace(addr.classId);
+					if (nspAttnum != InvalidAttrNumber)
+					{
+						Relation	catalog;
+						HeapTuple	objtup;
+						Oid			schema_oid;
+						bool		isnull;
+
+						catalog = heap_open(addr.classId, AccessShareLock);
+						objtup = get_catalog_object_by_oid(catalog,
+														   addr.objectId);
+						if (!HeapTupleIsValid(objtup))
+							elog(ERROR, "cache lookup failed for object %u/%u",
+								 addr.classId, addr.objectId);
+						schema_oid = heap_getattr(objtup, nspAttnum,
+												  RelationGetDescr(catalog), &isnull);
+						if (isnull)
+							elog(ERROR, "invalid null namespace in object %u/%u/%d",
+								 addr.classId, addr.objectId, addr.objectSubId);
+						if (isAnyTempNamespace(schema_oid))
+							schema = pstrdup("pg_temp");
+						else
+							schema = get_namespace_name(schema_oid);
+
+						heap_close(catalog, AccessShareLock);
+					}
+				}
+
+				/* classid */
+				values[i++] = ObjectIdGetDatum(addr.classId);
+				/* objid */
+				values[i++] = ObjectIdGetDatum(addr.objectId);
+				/* objsubid */
+				values[i++] = Int32GetDatum(addr.objectSubId);
+				/* command tag */
+				values[i++] = CStringGetTextDatum(tag);
+				/* object_type */
+				values[i++] = CStringGetTextDatum(type);
+				/* schema */
+				if (schema == NULL)
+					nulls[i++] = true;
+				else
+					values[i++] = CStringGetTextDatum(schema);
+				/* identity */
+				values[i++] = CStringGetTextDatum(identity);
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+			}
+
+			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+		}
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
+
+/* ************************* JSON STUFF FROM HERE ************************* *
+ *	Code below is used to decode blobs returned by deparse_utility_command	*
+ *																			*/
+
+/*
+ * Note we only support types that are valid in command representation from
+ * deparse_utility_command.
+ */
+typedef enum
+{
+	JsonTypeArray,
+	JsonTypeObject,
+	JsonTypeString
+} JsonType;
+
+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	SpecStringLiteral,
+	SpecIdentifier
+} convSpecifier;
+
+/*
+ * Extract the named json field, which must be of type string, from the given
+ * JSON datum, which must be of type object.  If the field doesn't exist,
+ * NULL is returned.  Otherwise the string value is returned.
+ */
+static char *
+expand_get_strval(Datum json, char *field_name)
+{
+	FunctionCallInfoData fcinfo;
+	Datum		result;
+	char	   *value_str;
+
+	InitFunctionCallInfoData(fcinfo, NULL, 2, InvalidOid, NULL, NULL);
+
+	fcinfo.arg[0] = json;
+	fcinfo.argnull[0] = false;
+	fcinfo.arg[1] = CStringGetTextDatum(field_name);
+	fcinfo.argnull[1] = false;
+
+	result = (*json_object_field_text) (&fcinfo);
+
+	if (fcinfo.isnull)
+		return NULL;
+
+	value_str = TextDatumGetCString(result);
+
+	pfree(DatumGetPointer(result));
+
+	return value_str;
+}
+
+/*
+ * Extract the named json field, which must be of type boolean, from the given
+ * JSON datum, which must be of type object.  If the field doesn't exist,
+ * isnull is set to TRUE and the return value should not be consulted.
+ * Otherwise the boolean value is returned.
+ */
+static bool
+expand_get_boolval(Datum json, char *field_name, bool *isnull)
+{
+	FunctionCallInfoData fcinfo;
+	Datum		result;
+	char	   *value_str;
+
+	*isnull = false;
+
+	InitFunctionCallInfoData(fcinfo, NULL, 2, InvalidOid, NULL, NULL);
+
+	fcinfo.arg[0] = json;
+	fcinfo.argnull[0] = false;
+	fcinfo.arg[1] = CStringGetTextDatum(field_name);
+	fcinfo.argnull[1] = false;
+
+	result = (*json_object_field_text) (&fcinfo);
+
+	if (fcinfo.isnull)
+	{
+		*isnull = true;
+		return false;
+	}
+
+	value_str = TextDatumGetCString(result);
+
+	if (strcmp(value_str, "true") == 0)
+		return true;
+
+	Assert(strcmp(value_str, "false") == 0);
+	return false;
+}
+
+/*
+ * Given a JSON value, return its type.
+ *
+ * We return both a JsonType (for easy control flow), and a string name (for
+ * error reporting).
+ */
+static JsonType
+jsonval_get_type(Datum jsonval, char **typename)
+{
+	JsonType	json_elt_type;
+	Datum		paramtype_datum;
+	char	   *paramtype;
+
+	paramtype_datum = DirectFunctionCall1(json_typeof, jsonval);
+	paramtype = TextDatumGetCString(paramtype_datum);
+
+	if (strcmp(paramtype, "array") == 0)
+		json_elt_type = JsonTypeArray;
+	else if (strcmp(paramtype, "object") == 0)
+		json_elt_type = JsonTypeObject;
+	else if (strcmp(paramtype, "string") == 0)
+		json_elt_type = JsonTypeString;
+	else
+		/* XXX improve this; need to specify array index or param name */
+		elog(ERROR, "unexpected JSON element type %s",
+			 paramtype);
+
+	if (typename)
+		*typename = pstrdup(paramtype);
+
+	return json_elt_type;
+}
+
+/*
+ * dequote_jsonval
+ *		Take a string value extracted from a JSON object, and return a copy of it
+ *		with the quoting removed.
+ *
+ * Another alternative to this would be to run the extraction routine again,
+ * using the "_text" variant which returns the value without quotes; but this
+ * is expensive, and moreover it complicates the logic a lot because not all
+ * values are extracted in the same way (some are extracted using
+ * json_object_field, others using json_array_element).  Dequoting the object
+ * already at hand is a lot easier.
+ */
+static char *
+dequote_jsonval(char *jsonval)
+{
+	char	   *result;
+	int			inputlen = strlen(jsonval);
+	int			i;
+	int			j = 0;
+
+	result = palloc(strlen(jsonval) + 1);
+
+	/* skip the start and end quotes right away */
+	for (i = 1; i < inputlen - 1; i++)
+	{
+		if (jsonval[i] == '\\')
+		{
+			i++;
+
+			/* This uses same logic as json.c */
+			switch (jsonval[i])
+			{
+				case 'b':
+					result[j++] = '\b';
+					continue;
+				case 'f':
+					result[j++] = '\f';
+					continue;
+				case 'n':
+					result[j++] = '\n';
+					continue;
+				case 'r':
+					result[j++] = '\r';
+					continue;
+				case 't':
+					result[j++] = '\t';
+					continue;
+				case '"':
+				case '\\':
+				case '/':
+					break;
+				default:
+					/* XXX: ERROR? */
+					break;
+			}
+		}
+
+		result[j++] = jsonval[i];
+	}
+	result[j] = '\0';
+
+	return result;
+}
+
+/*
+ * Expand a json value as an identifier.  The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, Datum jsonval)
+{
+	char	   *unquoted;
+
+	unquoted = dequote_jsonval(TextDatumGetCString(jsonval));
+	appendStringInfo(buf, "%s", quote_identifier(unquoted));
+
+	pfree(unquoted);
+}
+
+/*
+ * Expand a json value as a dotted-name.  The value must be of type object
+ * and must contain elements "schemaname" (optional), "objname" (mandatory),
+ * "attrname" (optional).
+ *
+ * XXX do we need a "catalogname" as well?
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, Datum jsonval)
+{
+	char	   *schema;
+	char	   *objname;
+	char	   *attrname;
+	const char *qschema;
+	const char *qname;
+
+	schema = expand_get_strval(jsonval, "schemaname");
+	objname = expand_get_strval(jsonval, "objname");
+	if (objname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL object name in %%D element")));
+	qname = quote_identifier(objname);
+	if (schema == NULL)
+	{
+		appendStringInfo(buf, "%s", qname);
+	}
+	else
+	{
+		qschema = quote_identifier(schema);
+		appendStringInfo(buf, "%s.%s",
+						 qschema, qname);
+		if (qschema != schema)
+			pfree((char *) qschema);
+		pfree(schema);
+	}
+
+	attrname = expand_get_strval(jsonval, "attrname");
+	if (attrname)
+	{
+		const char *qattr;
+
+		qattr = quote_identifier(attrname);
+		appendStringInfo(buf, ".%s", qattr);
+		if (qattr != attrname)
+			pfree((char *) qattr);
+		pfree(attrname);
+	}
+
+	if (qname != objname)
+		pfree((char *) qname);
+	pfree(objname);
+}
+
+/*
+ * expand a json value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, Datum jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		array_isnull;
+	bool		is_array;
+
+	typename = expand_get_strval(jsonval, "typename");
+	if (typename == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL type name in %%T element")));
+	typmodstr = expand_get_strval(jsonval, "typmod");	/* OK if null */
+	is_array = expand_get_boolval(jsonval, "is_array", &array_isnull);
+	schema = expand_get_strval(jsonval, "schemaname");
+
+	/*
+	 * If schema is NULL, then don't schema qualify, but do quote the type
+	 * name; if the schema is empty instead, then we don't quote the type name.
+	 * This is our (admittedly quite ugly) way of dealing with type names that
+	 * might require special treatment.
+	 */
+	if (schema == NULL)
+		appendStringInfo(buf, "%s%s%s",
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 is_array ? "[]" : "");
+	else if (schema[0] == '\0')
+		appendStringInfo(buf, "%s%s%s",
+						 typename,
+						 typmodstr ? typmodstr : "",
+						 is_array ? "[]" : "");
+	else
+		appendStringInfo(buf, "%s.%s%s%s",
+						 quote_identifier(schema),
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 is_array ? "[]" : "");
+}
+
+/*
+ * Expand a json value as an operator name
+ */
+static void
+expand_jsonval_operator(StringInfo buf, Datum jsonval)
+{
+	char	   *schema = NULL;
+	char	   *operator;
+
+	operator = expand_get_strval(jsonval, "objname");
+	if (operator == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL operator name in %%O element")));
+	schema = expand_get_strval(jsonval, "schemaname");
+
+	/* schema might be NULL or empty */
+	if (schema == NULL || schema[0] == '\0')
+		appendStringInfo(buf, "%s", operator);
+	else
+		appendStringInfo(buf, "%s.%s",
+						 quote_identifier(schema),
+						 operator);
+}
+
+/*
+ * Expand a json value as a string.  The value must be of type string or of
+ * type object, in which case it must contain a "fmt" element which will be
+ * recursively expanded; also, if the object contains an element "present"
+ * and it is set to false, the expansion is the empty string.
+ */
+static void
+expand_jsonval_string(StringInfo buf, Datum jsonval, JsonType json_elt_type)
+{
+	if (json_elt_type == JsonTypeString)
+	{
+		char	   *str;
+		char	   *unquoted;
+
+		str = TextDatumGetCString(jsonval);
+		unquoted = dequote_jsonval(str);
+		appendStringInfo(buf, "%s", unquoted);
+		pfree(str);
+		pfree(unquoted);
+	}
+	else if (json_elt_type == JsonTypeObject)
+	{
+		bool		present;
+		bool		isnull;
+
+		present = expand_get_boolval(jsonval, "present", &isnull);
+
+		if (isnull || present)
+		{
+			Datum		inner;
+			char	   *str;
+
+			inner = DirectFunctionCall1(pg_event_trigger_expand_command,
+										jsonval);
+			str = TextDatumGetCString(inner);
+
+			appendStringInfoString(buf, str);
+			pfree(DatumGetPointer(inner));
+			pfree(str);
+		}
+	}
+}
+
+/*
+ * Expand a json value as a string literal
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, Datum jsonval)
+{
+	char   *str;
+	char   *unquoted;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int         dqnextchar = 0;
+
+	/* obtain the string, and remove the JSON quotes and stuff */
+	str = TextDatumGetCString(jsonval);
+	unquoted = dequote_jsonval(str);
+
+	/* easy case: if there are no ' and no \, just use a single quote */
+	if (strchr(unquoted, '\'') == NULL &&
+		strchr(unquoted, '\\') == NULL)
+	{
+		appendStringInfo(buf, "'%s'", unquoted);
+		return;
+	}
+
+	/* Find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(unquoted, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar %= sizeof(dqsuffixes) - 1;
+	}
+	/* add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, unquoted, dqdelim.data);
+}
+
+/*
+ * Expand one json element according to rules.
+ */
+static void
+expand_one_element(StringInfo buf, char *param,
+				   Datum jsonval, char *valtype, JsonType json_elt_type,
+				   convSpecifier specifier)
+{
+	/*
+	 * Validate the parameter type.  If dotted-name was specified, then a JSON
+	 * object element is expected; if an identifier was specified, then a JSON
+	 * string is expected.	If a string was specified, then either a JSON
+	 * object or a string is expected.
+	 */
+	if (specifier == SpecDottedName && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%D element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecTypename && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%T element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecOperatorname && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%O element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecIdentifier && json_elt_type != JsonTypeString)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string for %%I element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecStringLiteral && json_elt_type != JsonTypeString)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string for %%L element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecString &&
+		json_elt_type != JsonTypeString && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string or object for %%s element \"%s\", got %s",
+						param, valtype)));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			expand_jsonval_identifier(buf, jsonval);
+			break;
+
+		case SpecDottedName:
+			expand_jsonval_dottedname(buf, jsonval);
+			break;
+
+		case SpecString:
+			expand_jsonval_string(buf, jsonval, json_elt_type);
+			break;
+
+		case SpecStringLiteral:
+			expand_jsonval_strlit(buf, jsonval);
+			break;
+
+		case SpecTypename:
+			expand_jsonval_typename(buf, jsonval);
+			break;
+
+		case SpecOperatorname:
+			expand_jsonval_operator(buf, jsonval);
+			break;
+	}
+}
+
+/*
+ * Expand one JSON array element according to rules.
+ */
+static void
+expand_one_array_element(StringInfo buf, Datum array, int idx, char *param,
+						 convSpecifier specifier)
+{
+	Datum		elemval;
+	JsonType	json_elt_type;
+	char	   *elemtype;
+
+	elemval = DirectFunctionCall2(json_array_element,
+								  PointerGetDatum(array),
+								  Int32GetDatum(idx));
+	json_elt_type = jsonval_get_type(elemval, &elemtype);
+
+	expand_one_element(buf, param,
+					   elemval, elemtype, json_elt_type,
+					   specifier);
+}
+
+#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \
+	do { \
+		if (++(ptr) >= (end_ptr)) \
+			ereport(ERROR, \
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
+					 errmsg("unterminated format specifier"))); \
+	} while (0)
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ *
+ * The element name may have an optional separator specification preceded
+ * by a colon.	Its presence indicates that the element is expected to be
+ * an array; the specified separator is used to join the array elements.
+ *
+ * XXX the current implementation works fine, but is likely to be slow: for
+ * each element found in the fmt string we parse the JSON object once.	It
+ * might be better to use jsonapi.h directly so that we build a hash or tree of
+ * elements and their values once before parsing the fmt string, and later scan
+ * fmt using the tree.
+ *------
+ */
+Datum
+pg_event_trigger_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	char	   *fmt_str;
+	int			fmt_len;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	StringInfoData str;
+
+	fmt_str = expand_get_strval(PointerGetDatum(json), "fmt");
+	if (fmt_str == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL format string")));
+	fmt_len = strlen(fmt_str);
+
+	start_ptr = fmt_str;
+	end_ptr = start_ptr + fmt_len;
+	initStringInfo(&str);
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{
+		convSpecifier specifier;
+		bool		is_array;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+		Datum		paramval;
+		char	   *paramtype;
+		JsonType	json_elt_type;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(&str, *cp);
+			continue;
+		}
+
+		is_array = false;
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(&str, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			for (; cp < end_ptr;)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing conversion name in conversion specifier")));
+
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'L':
+				specifier = SpecStringLiteral;
+				break;
+			case 'T':
+				specifier = SpecTypename;
+				break;
+			case 'O':
+				specifier = SpecOperatorname;
+				break;
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid conversion specifier \"%c\"", *cp)));
+		}
+
+		/*
+		 * Obtain the element to be expanded.  Note we cannot use
+		 * DirectFunctionCall here, because the element might not exist.
+		 */
+		{
+			FunctionCallInfoData fcinfo;
+
+			InitFunctionCallInfoData(fcinfo, NULL, 2, InvalidOid, NULL, NULL);
+
+			fcinfo.arg[0] = PointerGetDatum(json);
+			fcinfo.argnull[0] = false;
+			fcinfo.arg[1] = CStringGetTextDatum(param);
+			fcinfo.argnull[1] = false;
+
+			paramval = (*json_object_field) (&fcinfo);
+
+			if (fcinfo.isnull)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("non-existant element \"%s\" in JSON formatting object",
+								param)));
+			}
+		}
+
+		/* figure out its type */
+		json_elt_type = jsonval_get_type(paramval, &paramtype);
+
+		/* Validate that we got an array if the format string specified one. */
+		if (is_array && json_elt_type != JsonTypeArray)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("expected JSON array for element \"%s\", got %s",
+							param, paramtype)));
+
+		/* And finally print out the data */
+		if (is_array)
+		{
+			int			count;
+			bool		putsep = false;
+			int			i;
+
+			count = DatumGetInt32(DirectFunctionCall1(json_array_length,
+													  paramval));
+			for (i = 0; i < count; i++)
+			{
+				if (putsep)
+					appendStringInfoString(&str, arraysep);
+				putsep = true;
+
+				expand_one_array_element(&str, paramval, i, param, specifier);
+			}
+		}
+		else
+		{
+			expand_one_element(&str, param, paramval, paramtype, json_elt_type,
+							   specifier);
+		}
+	}
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(str.data));
+}
diff --git a/src/backend/tcop/Makefile b/src/backend/tcop/Makefile
index 674302f..34acdce 100644
--- a/src/backend/tcop/Makefile
+++ b/src/backend/tcop/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/tcop
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS= dest.o fastpath.o postgres.o pquery.o utility.o
+OBJS= dest.o deparse_utility.o fastpath.o postgres.o pquery.o utility.o
 
 ifneq (,$(filter $(PORTNAME),cygwin win32))
 override CPPFLAGS += -DWIN32_STACK_RLIMIT=$(WIN32_STACK_RLIMIT)
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
new file mode 100644
index 0000000..bea12ac
--- /dev/null
+++ b/src/backend/tcop/deparse_utility.c
@@ -0,0 +1,810 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Before they are turned into JSON representation, each command is represented
+ * as an object tree, using the structs below.
+ */
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjTree
+{
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+	bool		bool_value;
+	char	   *str_value;
+	ObjTree	   *obj_value;
+	List	   *array_value;
+	slist_node	node;
+} ObjElem;
+
+static ObjElem *new_null_object(char *name);
+static ObjElem *new_bool_object(char *name, bool value);
+static ObjElem *new_string_object(char *name, char *value);
+static ObjElem *new_object_object(char *name, ObjTree *value);
+static ObjElem *new_array_object(char *name, List *array);
+static void append_null_object(ObjTree *tree, char *name);
+static void append_bool_object(ObjTree *tree, char *name, bool value);
+static void append_string_object(ObjTree *tree, char *name, char *value);
+static void append_object_object(ObjTree *tree, char *name, ObjTree *value);
+static void append_array_object(ObjTree *tree, char *name, List *array);
+static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
+
+/*
+ * Allocate a new object tree to store parameter values.
+ */
+static ObjTree *
+new_objtree(void)
+{
+	ObjTree    *params;
+
+	params = palloc(sizeof(ObjTree));
+	params->numParams = 0;
+	slist_init(&params->params);
+
+	return params;
+}
+
+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one,
+ * a name, type and value must be supplied.  Note we don't have the luxury of
+ * sprintf-like compiler warnings for malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree();
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		ObjTree    *value;
+		ObjType		type;
+		ObjElem	   *elem;
+		char	   *name;
+		char	   *strval;
+		bool		boolval;
+		List	   *list;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/* Null params don't have a value (obviously) */
+		if (type == ObjTypeNull)
+		{
+			append_null_object(tree, name);
+			continue;
+		}
+
+		/*
+		 * For all other param types there must be a value in the varargs.
+		 * Fetch it and add the fully formed subobject into the main object.
+		 */
+		switch (type)
+		{
+			case ObjTypeBool:
+				boolval = va_arg(args, int);
+				elem = new_bool_object(name, boolval);
+				break;
+			case ObjTypeString:
+				strval = va_arg(args, char *);
+				elem = new_string_object(name, strval);
+				break;
+			case ObjTypeObject:
+				value = va_arg(args, ObjTree *);
+				elem = new_object_object(name, value);
+				break;
+			case ObjTypeArray:
+				list = va_arg(args, List *);
+				elem = new_array_object(name, list);
+				break;
+			default:
+				elog(ERROR, "invalid parameter type %d", type);
+		}
+
+		append_premade_object(tree, elem);
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/* Allocate a new parameter with a NULL value */
+static ObjElem *
+new_null_object(char *name)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeNull;
+
+	return param;
+}
+
+/* Append a NULL object to a tree */
+static void
+append_null_object(ObjTree *tree, char *name)
+{
+	ObjElem    *param;
+
+	param = new_null_object(name);
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new boolean parameter */
+static ObjElem *
+new_bool_object(char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeBool;
+	param->bool_value = value;
+
+	return param;
+}
+
+/* Append a boolean parameter to a tree */
+static void
+append_bool_object(ObjTree *tree, char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = new_bool_object(name, value);
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new string object */
+static ObjElem *
+new_string_object(char *name, char *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeString;
+	param->str_value = value;	/* XXX not duped */
+
+	return param;
+}
+
+/*
+ * Append a string parameter to a tree.
+ *
+ * Note: we don't pstrdup the source string.  Caller must ensure the
+ * source string lives long enough.
+ */
+static void
+append_string_object(ObjTree *tree, char *name, char *value)
+{
+	ObjElem	   *param;
+
+	param = new_string_object(name, value);
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new object parameter */
+static ObjElem *
+new_object_object(char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeObject;
+	param->obj_value = value;	/* XXX not duped */
+
+	return param;
+}
+
+/* Append an object parameter to a tree */
+static void
+append_object_object(ObjTree *tree, char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = new_object_object(name, value);
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new array parameter */
+static ObjElem *
+new_array_object(char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeArray;
+	param->array_value = array; /* XXX not duped */
+
+	return param;
+}
+
+/* Append an array parameter to a tree */
+static void
+append_array_object(ObjTree *tree, char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = new_array_object(name, array);
+	append_premade_object(tree, param);
+}
+
+/* Append a preallocated parameter to a tree */
+static inline void
+append_premade_object(ObjTree *tree, ObjElem *elem)
+{
+	slist_push_head(&tree->params, &elem->node);
+	tree->numParams++;
+}
+
+/*
+ * Create a JSON blob from our ad-hoc representation.
+ *
+ * Note this leaks some memory; caller is responsible for later clean up.
+ *
+ * XXX this implementation will fail if there are more JSON objects in the tree
+ * than the maximum number of columns in a heap tuple.  To fix we would first call
+ * construct_md_array and then json_object.
+ */
+static char *
+jsonize_objtree(ObjTree *tree)
+{
+	TupleDesc	tupdesc;
+	Datum	   *values;
+	bool	   *nulls;
+	slist_iter	iter;
+	int			i;
+	HeapTuple	htup;
+	Datum		json;
+	char	   *jsonstr;
+
+	tupdesc = CreateTemplateTupleDesc(tree->numParams, false);
+	values = palloc(sizeof(Datum) * tree->numParams);
+	nulls = palloc(sizeof(bool) * tree->numParams);
+
+	i = 1;
+	slist_foreach(iter, &tree->params)
+	{
+		ObjElem    *object = slist_container(ObjElem, node, iter.cur);
+		Oid			typeid;
+
+		switch (object->objtype)
+		{
+			case ObjTypeNull:
+			case ObjTypeString:
+				typeid = TEXTOID;
+				break;
+			case ObjTypeBool:
+				typeid = BOOLOID;
+				break;
+			case ObjTypeArray:
+			case ObjTypeObject:
+				typeid = JSONOID;
+				break;
+			default:
+				elog(ERROR, "unable to determine type id");
+				typeid = InvalidOid;
+		}
+
+		TupleDescInitEntry(tupdesc, i, object->name, typeid, -1, 0);
+
+		nulls[i - 1] = false;
+		switch (object->objtype)
+		{
+			case ObjTypeNull:
+				nulls[i - 1] = true;
+				break;
+			case ObjTypeBool:
+				values[i - 1] = BoolGetDatum(object->bool_value);
+				break;
+			case ObjTypeString:
+				values[i - 1] = CStringGetTextDatum(object->str_value);
+				break;
+			case ObjTypeArray:
+				{
+					ArrayType  *arrayt;
+					Datum	   *arrvals;
+					Datum		jsonary;
+					ListCell   *cell;
+					int			length = list_length(object->array_value);
+					int			j;
+
+					/*
+					 * Arrays are stored as Lists up to this point, with each
+					 * element being a ObjElem; we need to construct an
+					 * ArrayType with them to turn the whole thing into a JSON
+					 * array.
+					 */
+					j = 0;
+					arrvals = palloc(sizeof(Datum) * length);
+					foreach(cell, object->array_value)
+					{
+						ObjElem    *elem = lfirst(cell);
+
+						switch (elem->objtype)
+						{
+							case ObjTypeString:
+								arrvals[j] =
+									/*
+									 * XXX need quotes around the value.  This
+									 * needs to be handled differently because
+									 * it will fail for values of anything but
+									 * trivial complexity.
+									 */
+									CStringGetTextDatum(psprintf("\"%s\"",
+																 elem->str_value));
+								break;
+							case ObjTypeObject:
+								arrvals[j] =
+									CStringGetTextDatum(jsonize_objtree(elem->obj_value));
+								break;
+							default:
+								/* not worth supporting other cases */
+								elog(ERROR, "unsupported object type %d",
+									 elem->objtype);
+						}
+
+						j++;
+
+					}
+					arrayt = construct_array(arrvals, length,
+											 JSONOID, -1, false, 'i');
+
+					jsonary = DirectFunctionCall1(array_to_json,
+												  (PointerGetDatum(arrayt)));
+
+					values[i - 1] = jsonary;
+				}
+				break;
+			case ObjTypeObject:
+				values[i - 1] =
+					CStringGetTextDatum(jsonize_objtree(object->obj_value));
+				break;
+		}
+
+		i++;
+	}
+
+	BlessTupleDesc(tupdesc);
+	htup = heap_form_tuple(tupdesc, values, nulls);
+	json = DirectFunctionCall1(row_to_json, HeapTupleGetDatum(htup));
+
+	jsonstr = TextDatumGetCString(json);
+
+	return jsonstr;
+}
+
+/*
+ * Release all memory used by parameters and their expansion
+ */
+static void
+free_objtree(ObjTree *tree)
+{
+	/* XXX nothing here */
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{
+	ObjTree    *typeParam;
+	Oid			typnspid;
+	char	   *typnsp;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		is_array;
+
+	format_type_detailed(typeId, typmod,
+						 &typnspid, &typename, &typmodstr, &is_array);
+
+	if (!OidIsValid(typnspid))
+		typnsp = pstrdup("");
+	else if (isAnyTempNamespace(typnspid))
+		typnsp = pstrdup("pg_temp");
+	else
+		typnsp = get_namespace_name(typnspid);
+
+	/* We don't use new_objtree_VA here because types don't have a "fmt" */
+	typeParam = new_objtree();
+	append_string_object(typeParam, "schemaname", typnsp);
+	append_string_object(typeParam, "typename", typename);
+	append_string_object(typeParam, "typmod", typmodstr);
+	append_bool_object(typeParam, "is_array", is_array);
+
+	return typeParam;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements
+ *
+ * Elements "schemaname" and "objname" are set.  If the namespace OID
+ * corresponds to a temp schema, that's set to "pg_temp".
+ *
+ * The difference between those two element types is whether the objname will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static ObjTree *
+new_objtree_for_qualname(Oid nspid, char *name)
+{
+	ObjTree    *qualified;
+	char	   *namespace;
+
+	/*
+	 * We don't use new_objtree_VA here because these names don't have a "fmt"
+	 */
+	qualified = new_objtree();
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+	append_string_object(qualified, "schemaname", namespace);
+	append_string_object(qualified, "objname", pstrdup(name));
+
+	return qualified;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements, with the object specified
+ * by classId/objId
+ *
+ * Elements "schemaname" and "objname" are set.  If the object is a temporary
+ * object, the schema name is set to "pg_temp".
+ */
+static ObjTree *
+new_objtree_for_qualname_id(Oid classId, Oid objectId)
+{
+	ObjTree    *qualified;
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		objnsp;
+	Datum		objname;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	bool		isnull;
+
+	catalog = heap_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL namespace");
+	objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL name");
+
+	qualified = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+										 NameStr(*DatumGetName(objname)));
+
+	pfree(catobj);
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+static char *
+deparse_parsenode_cmd(StashedCommand *cmd)
+{
+	Oid			objectId;
+	uint32		objectSubId = 0;
+	Node	   *parsetree;
+	char	   *command;
+
+	parsetree = cmd->parsetree;
+
+	switch (cmd->type)
+	{
+		case SCT_Basic:
+			objectId = cmd->d.basic.objectId;
+			objectSubId = cmd->d.basic.objectSubId;
+			break;
+		default:
+			elog(ERROR, "unexpected deparse node type %d", cmd->type);
+	}
+
+	/* This switch needs to handle everything that ProcessUtility does */
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			command = NULL;
+			break;
+
+		case T_CreateStmt:
+			command = NULL;
+			break;
+
+			/* other local objects */
+		case T_DefineStmt:
+			command = NULL;
+			break;
+
+		case T_IndexStmt:
+			command = NULL;
+			break;
+
+		case T_CreateExtensionStmt:
+			command = NULL;
+			break;
+
+			/* FDW-related objects */
+		case T_CreateForeignTableStmt:
+			command = NULL;
+			break;
+
+		case T_CreateFdwStmt:
+			command = NULL;
+			break;
+
+		case T_CreateForeignServerStmt:
+			command = NULL;
+			break;
+
+		case T_CreateUserMappingStmt:
+			command = NULL;
+			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_ViewStmt:		/* CREATE VIEW */
+			command = NULL;
+			break;
+
+		case T_CreateFunctionStmt:
+			command = NULL;
+			break;
+
+		case T_RuleStmt:
+			command = NULL;
+			break;
+
+		case T_CreateSeqStmt:
+			command = NULL;
+			break;
+
+		case T_AlterSeqStmt:
+			command = NULL;
+			break;
+
+		case T_CreateTableAsStmt:
+			command = NULL;
+			break;
+
+		case T_RefreshMatViewStmt:
+			command = NULL;
+			break;
+
+		case T_CreateTrigStmt:
+			command = NULL;
+			break;
+
+		case T_CreatePLangStmt:
+			command = NULL;
+			break;
+
+		case T_CreateDomainStmt:
+			command = NULL;
+			break;
+
+		case T_CreateConversionStmt:
+			command = NULL;
+			break;
+
+		case T_CreateCastStmt:
+			command = NULL;
+			break;
+
+		case T_CreateOpClassStmt:
+			command = NULL;
+			break;
+
+		case T_CreateOpFamilyStmt:
+			command = NULL;
+			break;
+
+		case T_RenameStmt:
+			command = NULL;
+			break;
+
+		case T_AlterObjectSchemaStmt:
+			command = NULL;
+			break;
+
+		case T_AlterOwnerStmt:
+			command = NULL;
+			break;
+
+		case T_GrantStmt:
+			command = NULL;
+			break;
+
+		/*
+		 * MB: some command types are missing here, in particular some
+		 * ALTER cases, IMPORT FOREIGN SCHEMA, CREATE POLICY, all DROPs.
+		 */
+
+		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)
+{
+	MemoryContext	oldcxt;
+	MemoryContext	tmpcxt;
+	OverrideSearchPath *overridePath;
+	char	   *command;
+
+	/*
+	 * Allocate everything done by the deparsing routines into a temp context,
+	 * to avoid having to sprinkle them with memory handling code
+	 */
+	tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "deparse ctx",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * in order to obtain deparsed versions of expressions.  In such results,
+	 * we want all object names to be qualified, so that results are "portable"
+	 * to environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = false;
+	PushOverrideSearchPath(overridePath);
+
+	switch (cmd->type)
+	{
+		case SCT_Basic:
+			command = deparse_parsenode_cmd(cmd);
+			break;
+		default:
+			elog(ERROR, "unexpected deparse node type %d", cmd->type);
+	}
+
+	PopOverrideSearchPath();
+
+	/*
+	 * XXX to avoid the pstrdup we could have the routines return the
+	 * ObjTree and do the jsonize_objtree() here after changing cxt ...
+	 */
+	MemoryContextSwitchTo(oldcxt);
+	if (command != NULL)
+		command = pstrdup(command);
+	MemoryContextDelete(tmpcxt);
+
+	return command;
+}
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index e1763a3..d9958d6 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
 	return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
@@ -323,6 +326,106 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
+/*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - is_array indicates whether []s must be added
+ *
+ * Also, we don't try to decode type names to their standard-mandated names,
+ * except in the cases of unusual typmod rules, as specified above.
+ *
+ * XXX there is a lot of code duplication between this routine and
+ * format_type_internal.  (One thing that doesn't quite match is the whole
+ * allow_invalid business.)
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *is_array)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID)
+	{
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPOID:
+			case TIMESTAMPTZOID:
+				/* the TZ part is added by typmod */
+				*typname = pstrdup("TIMESTAMP");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		*is_array = false;
+
+		ReleaseSysCache(tuple);
+		return;
+	}
+
+	/*
+	 * Check if it's a regular (variable length) array type.  As above,
+	 * fixed-length array types such as "name" shouldn't get deconstructed.
+	 */
+	array_base_type = typeform->typelem;
+
+	if (array_base_type != InvalidOid &&
+		typeform->typstorage != 'p')
+	{
+		/* Switch our attention to the array element type */
+		ReleaseSysCache(tuple);
+		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+		typeform = (Form_pg_type) GETSTRUCT(tuple);
+		type_oid = array_base_type;
+		*is_array = true;
+	}
+	else
+		*is_array = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
 
 /*
  * Add typmod decoration to the basic type name
@@ -338,7 +441,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,7 +453,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 2a9431d..fd20291 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -32,6 +32,8 @@ extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
 				   List *objargs, Relation *relp,
 				   LOCKMODE lockmode, bool missing_ok);
 
+extern Oid get_objtype_catalog_oid(ObjectType objtype);
+
 extern void check_object_ownership(Oid roleid,
 					   ObjectType objtype, ObjectAddress address,
 					   List *objname, List *objargs, Relation relation);
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d30d21a..80a58fb 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4986,6 +4986,10 @@ DESCR("peek at binary changes from replication slot");
 /* event triggers */
 DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
 DESCR("list objects dropped by the current command");
+DATA(insert OID = 3590 (  pg_event_trigger_get_creation_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,114}" "{o,o,o,o,o,o,o,o,o}" "{classid,objid,objsubid,command_tag,object_type,schema,identity,in_extension,command}" _null_ pg_event_trigger_get_creation_commands _null_ _null_ _null_ ));
+DESCR("list JSON-formatted commands executed by the current command");
+DATA(insert OID = 3591 (  pg_event_trigger_expand_command PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 25 "114" _null_ _null_ _null_ _null_ pg_event_trigger_expand_command _null_ _null_ _null_ ));
+DESCR("format JSON command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 0233f4c..9c276f1 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -52,4 +52,7 @@ extern void EventTriggerEndCompleteQuery(void);
 extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
 
+extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
+						 ObjectType objtype, Node *parsetree);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 2cf784b..6e53320 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -23,7 +23,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3aa69e..18cfc40 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1207,6 +1207,7 @@ typedef enum ObjectType
 	OBJECT_ATTRIBUTE,			/* type's attribute, when distinct from column */
 	OBJECT_CAST,
 	OBJECT_COLUMN,
+	OBJECT_COMPOSITE,
 	OBJECT_CONSTRAINT,
 	OBJECT_COLLATION,
 	OBJECT_CONVERSION,
@@ -1238,6 +1239,7 @@ typedef enum ObjectType
 	OBJECT_TSPARSER,
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
+	OBJECT_USER_MAPPING,
 	OBJECT_VIEW
 } ObjectType;
 
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..d95c33d
--- /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_Basic,
+} 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 BasicCommand
+		{
+			Oid			objectId;
+			uint32		objectSubId;
+			ObjectType	objtype;
+		} basic;
+	} d;
+} StashedCommand;
+
+extern char *deparse_utility_command(StashedCommand *cmd);
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fb1b4a4..8e3e622 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1068,6 +1068,9 @@ extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 extern Datum oidvectortypes(PG_FUNCTION_ARGS);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
+extern void format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname,
+					 char **typemodstr, bool *is_array);
 
 /* quote.c */
 extern Datum quote_ident(PG_FUNCTION_ARGS);
@@ -1189,6 +1192,8 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 
 /* commands/event_trigger.c */
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_expand_command(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
-- 
1.9.1

0005-deparse-initial-set-of-supported-commands.patchtext/x-diff; charset=us-asciiDownload
>From 9848b3c6b27b5b7cf592643950804662cc89e3bb Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 24 Sep 2014 15:53:31 -0300
Subject: [PATCH 05/30] deparse: initial set of supported commands

CREATE SCHEMA
CREATE TABLE
CREATE SEQUENCE
ALTER SEQUENCE
CREATE INDEX
CREATE TRIGGER
CREATE TYPE AS
CREATE TYPE AS ENUM
---
 src/backend/commands/schemacmds.c   |    9 +
 src/backend/commands/sequence.c     |   34 +
 src/backend/commands/tablecmds.c    |    3 +-
 src/backend/tcop/deparse_utility.c  | 1262 ++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c          |  156 +++--
 src/backend/utils/adt/format_type.c |    4 +-
 src/backend/utils/adt/ruleutils.c   |  373 +++++++++--
 src/include/commands/sequence.h     |    1 +
 src/include/utils/ruleutils.h       |   14 +-
 9 files changed, 1738 insertions(+), 118 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 03f5514..4548dfd 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, (Node *) stmt);
+
+	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
 	 * reorganize them into a sequentially executable order with no forward
 	 * references.  Note that the result is still a list of raw parsetrees ---
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 3b89dd0..0c5d5d7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1492,6 +1492,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 add3ab9..9587217 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8010,7 +8010,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 bea12ac..11d7894 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -587,6 +587,1252 @@ new_objtree_for_qualname_id(Oid classId, Oid objectId)
 	return qualified;
 }
 
+/*
+ * Return the string representation of the given RELPERSISTENCE value
+ */
+static char *
+get_persistence_str(char persistence)
+{
+	switch (persistence)
+	{
+		case RELPERSISTENCE_TEMP:
+			return "TEMPORARY";
+		case RELPERSISTENCE_UNLOGGED:
+			return "UNLOGGED";
+		case RELPERSISTENCE_PERMANENT:
+			return "";
+		default:
+			return "???";
+	}
+}
+
+/*
+ * deparse_CreateTrigStmt
+ *		Deparse a CreateTrigStmt (CREATE TRIGGER)
+ *
+ * Given a trigger OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ */
+static char *
+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;
+	char	   *command;
+
+	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(NULL, "INSERT"));
+	if (node->events & TRIGGER_TYPE_DELETE)
+		events = lappend(events, new_string_object(NULL, "DELETE"));
+	if (node->events & TRIGGER_TYPE_TRUNCATE)
+		events = lappend(events, new_string_object(NULL, "TRUNCATE"));
+	if (node->events & TRIGGER_TYPE_UPDATE)
+	{
+		if (node->columns == NIL)
+		{
+			events = lappend(events, new_string_object(NULL, "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(NULL, colname));
+			}
+
+			append_array_object(update, "columns", cols);
+
+			events = lappend(events,
+							 new_object_object(NULL, 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(NULL, "DEFERRABLE"));
+	if (node->initdeferred)
+		list = lappend(list,
+					   new_string_object(NULL, "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(NULL, p));
+
+			p += tlen + 1;
+		}
+	}
+
+	append_array_object(tmp, "args", list);		/* might be NIL */
+
+	append_object_object(trigger, "function", tmp);
+
+	heap_close(pg_trigger, AccessShareLock);
+
+	command = jsonize_objtree(trigger);
+	free_objtree(trigger);
+
+	return command;
+}
+
+/*
+ * deparse_ColumnDef
+ *		Subroutine for CREATE TABLE deparsing
+ *
+ * Deparse a ColumnDef node within a regular (non typed) table creation.
+ *
+ * NOT NULL constraints in the column definition are emitted directly in the
+ * column definition by this routine; other constraints must be emitted
+ * elsewhere (the info in the parse node is incomplete anyway.)
+ */
+static ObjTree *
+deparse_ColumnDef(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(NULL, 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(NULL, 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 the JSON blob
+ * representing the creation command.
+ */
+static char *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	List	   *list;
+	ListCell   *cell;
+	char	   *command;
+	char	   *fmtstr;
+
+	/*
+	 * Typed tables use a slightly different format string: we must not put
+	 * table_elements with parents directly in the fmt string, because if
+	 * there are no options the parens must not be emitted; and also, typed
+	 * tables do not allow for inheritance.
+	 */
+	if (node->ofTypename)
+		fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D "
+			"OF %{of_type}T %{table_elements}s "
+			"%{on_commit}s WITH (%{with:, }s) %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) %{inherits}s "
+			"%{on_commit}s WITH (%{with:, }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(NULL, 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(NULL, tmp));
+	foreach(cell, node->options)
+	{
+		DefElem	*opt = (DefElem *) lfirst(cell);
+		char   *fmt;
+		char   *value;
+
+		/* already handled above */
+		if (strcmp(opt->defname, "oids") == 0)
+			continue;
+
+		if (opt->defnamespace)
+			fmt = psprintf("%s.%s=%%{value}s", opt->defnamespace, opt->defname);
+		else
+			fmt = psprintf("%s=%%{value}s", opt->defname);
+		value = opt->arg ? defGetString(opt) :
+			defGetBoolean(opt) ? "TRUE" : "FALSE";
+		tmp = new_objtree_VA(fmt, 2,
+							 "option", ObjTypeString, opt->defname,
+							 "value", ObjTypeString, value);
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+	append_array_object(createStmt, "with", list);
+
+	command = jsonize_objtree(createStmt);
+
+	free_objtree(createStmt);
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+static char *
+deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
+{
+	CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree;
+	ObjTree	   *composite;
+	Relation	typerel = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	List	   *tableelts = NIL;
+	char	   *command;
+
+	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);
+
+	command = jsonize_objtree(composite);
+	free_objtree(composite);
+	heap_close(typerel, AccessShareLock);
+
+	return command;
+}
+
+static char *
+deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
+{
+	CreateEnumStmt *node = (CreateEnumStmt *) parsetree;
+	ObjTree	   *enumtype;
+	char	   *command;
+	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(NULL, strVal(val)));
+	}
+	append_array_object(enumtype, "values", values);
+
+	command = jsonize_objtree(enumtype);
+	free_objtree(enumtype);
+
+	return command;
+}
+
+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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, ownedby);
+}
+
+/*
+ * deparse_CreateSeqStmt
+ *		deparse a CreateSeqStmt
+ *
+ * Given a sequence OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ */
+static char *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *createSeq;
+	ObjTree    *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	char	   *command;
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+
+	seqdata = get_sequence_values(objectId);
+
+	createSeq =
+		new_objtree_VA("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);
+
+	command = jsonize_objtree(createSeq);
+
+	free_objtree(createSeq);
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+static char *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *alterSeq;
+	ObjTree	   *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	char	   *command;
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+	ListCell   *cell;
+
+	seqdata = get_sequence_values(objectId);
+
+	alterSeq =
+		new_objtree_VA("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);
+
+	command = jsonize_objtree(alterSeq);
+
+	free_objtree(alterSeq);
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+/*
+ * deparse_IndexStmt
+ *		deparse an IndexStmt
+ *
+ * Given an index OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ */
+static char *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+	IndexStmt  *node = (IndexStmt *) parsetree;
+	ObjTree    *indexStmt;
+	ObjTree    *tmp;
+	Relation	idxrel;
+	Relation	heaprel;
+	char	   *command;
+	char	   *index_am;
+	char	   *definition;
+	char	   *reloptions;
+	char	   *tablespace;
+	char	   *whereClause;
+
+	if (node->primary || node->isconstraint)
+	{
+		/*
+		 * indexes for PRIMARY KEY and other constraints are output
+		 * separately; return empty here.
+		 */
+		return NULL;
+	}
+
+	idxrel = relation_open(objectId, AccessShareLock);
+	heaprel = relation_open(idxrel->rd_index->indrelid, AccessShareLock);
+
+	pg_get_indexdef_detailed(objectId,
+							 &index_am, &definition, &reloptions,
+							 &tablespace, &whereClause);
+
+	indexStmt =
+		new_objtree_VA("CREATE %{unique}s INDEX %{concurrently}s %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   5,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(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);
+
+	command = jsonize_objtree(indexStmt);
+	free_objtree(indexStmt);
+
+	heap_close(idxrel, AccessShareLock);
+	heap_close(heaprel, AccessShareLock);
+
+	return command;
+}
+
+/*
+ * deparse_CreateSchemaStmt
+ *		deparse a CreateSchemaStmt
+ *
+ * Given a schema OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ *
+ * Note we don't output the schema elements given in the creation command.
+ * They must be output separately.	 (In the current implementation,
+ * CreateSchemaCommand passes them back to ProcessUtility, which will lead to
+ * this file if appropriate.)
+ */
+static char *
+deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
+{
+	CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
+	ObjTree    *createSchema;
+	ObjTree    *auth;
+	char	   *command;
+
+	createSchema =
+		new_objtree_VA("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);
+
+	command = jsonize_objtree(createSchema);
+	free_objtree(createSchema);
+
+	return command;
+}
+
 static char *
 deparse_parsenode_cmd(StashedCommand *cmd)
 {
@@ -611,11 +1857,11 @@ deparse_parsenode_cmd(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;
 
 			/* other local objects */
@@ -624,7 +1870,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_IndexStmt:
-			command = NULL;
+			command = deparse_IndexStmt(objectId, parsetree);
 			break;
 
 		case T_CreateExtensionStmt:
@@ -649,11 +1895,11 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
-			command = NULL;
+			command = deparse_CompositeTypeStmt(objectId, parsetree);
 			break;
 
 		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
-			command = NULL;
+			command = deparse_CreateEnumStmt(objectId, parsetree);
 			break;
 
 		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
@@ -673,11 +1919,11 @@ deparse_parsenode_cmd(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:
@@ -689,7 +1935,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_CreateTrigStmt:
-			command = NULL;
+			command = deparse_CreateTrigStmt(objectId, parsetree);
 			break;
 
 		case T_CreatePLangStmt:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d4be750..c98c7dc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -853,6 +853,7 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	Oid			objectId;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -871,6 +872,10 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * CreateSchemaCommand calls EventTriggerStashCommand
+				 * internally, for reasons explained there.
+				 */
 				break;
 
 			case T_CreateStmt:
@@ -878,7 +883,6 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					List	   *stmts;
 					ListCell   *l;
-					Oid			relOid;
 
 					/* Run parse analysis ... */
 					stmts = transformCreateStmt((CreateStmt *) parsetree,
@@ -895,9 +899,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,
+													 stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -919,20 +925,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,
+													 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,
@@ -1064,46 +1077,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, parsetree);
 				}
 				break;
 
@@ -1138,18 +1158,22 @@ 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,
+											 parsetree);
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				objectId = CreateExtension((CreateExtensionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
@@ -1161,7 +1185,8 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FDW, parsetree);
 				break;
 
 			case T_AlterFdwStmt:
@@ -1169,7 +1194,9 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FOREIGN_SERVER,
+										 parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
@@ -1177,7 +1204,9 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_USER_MAPPING,
+										 parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
@@ -1196,16 +1225,21 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					objectId = DefineCompositeType(stmt->typevar,
+												   stmt->coldeflist);
+					EventTriggerStashCommand(objectId, 0, OBJECT_COMPOSITE,
+											 parsetree);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				objectId = DefineEnum((CreateEnumStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				objectId = DefineRange((CreateRangeStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
@@ -1213,67 +1247,81 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				objectId = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_VIEW, parsetree);
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, parsetree);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				objectId = AlterFunction((AlterFunctionStmt *) parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				objectId = DefineRule((RuleStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_RULE, parsetree);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				objectId = DefineSequence((CreateSeqStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				objectId = AlterSequence((AlterSeqStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TABLE, parsetree);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+				objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
 								   queryString, params, completionTag);
+				EventTriggerStashCommand(objectId, 0, OBJECT_MATVIEW, 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, parsetree);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_LANGUAGE, parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				objectId = DefineDomain((CreateDomainStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_DOMAIN, parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				objectId = CreateConversionCommand((CreateConversionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_CONVERSION, parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				objectId = CreateCast((CreateCastStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_CAST, parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				objectId = DefineOpClass((CreateOpClassStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPCLASS, parsetree);
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPFAMILY, parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index d9958d6..65d5887 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -288,7 +288,8 @@ format_type_internal(Oid type_oid, int32 typemod,
 
 		case VARCHAROID:
 			if (with_typemod)
-				buf = printTypmod("character varying", typemod, typeform->typmodout);
+				buf = printTypmod("character varying", typemod,
+								  typeform->typmodout);
 			else
 				buf = pstrdup("character varying");
 			break;
@@ -462,7 +463,6 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	return res;
 }
 
-
 /*
  * type_maximum_size --- determine maximum width of a variable-width column
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 18d9b63..56e74b7 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);
 }
 
 /*
@@ -9323,3 +9574,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 914d155..984631a 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -70,6 +70,7 @@ extern Datum setval3_oid(PG_FUNCTION_ARGS);
 extern Datum lastval(PG_FUNCTION_ARGS);
 
 extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS);
+extern Form_pg_sequence get_sequence_values(Oid sequenceId);
 
 extern Oid	DefineSequence(CreateSeqStmt *stmt);
 extern Oid	AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 520b066..0503816 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);
@@ -31,4 +40,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 */
-- 
1.9.1

0006-deparse-Support-CREATE-TYPE-AS-RANGE.patchtext/x-diff; charset=us-asciiDownload
>From c12403b8761085c29d352fcb0a0a62f80e00d116 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 06/30] deparse: Support CREATE TYPE AS RANGE

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 11d7894..0cf2c55 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"
@@ -1426,6 +1428,108 @@ deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
 	return command;
 }
 
+static char *
+deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *range;
+	ObjTree	   *tmp;
+	List	   *definition = NIL;
+	Relation	pg_range;
+	HeapTuple	rangeTup;
+	Form_pg_range rangeForm;
+	ScanKeyData key[1];
+	SysScanDesc scan;
+	char	   *command;
+
+	pg_range = heap_open(RangeRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_range_rngtypid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(objectId));
+
+	scan = systable_beginscan(pg_range, RangeTypidIndexId, true,
+							  NULL, 1, key);
+
+	rangeTup = systable_getnext(scan);
+	if (!HeapTupleIsValid(rangeTup))
+		elog(ERROR, "cache lookup failed for range with type oid %u",
+			 objectId);
+
+	rangeForm = (Form_pg_range) GETSTRUCT(rangeTup);
+
+	range = new_objtree_VA("CREATE TYPE %{identity}D AS RANGE (%{definition:, }s)", 0);
+	tmp = new_objtree_for_qualname_id(TypeRelationId, objectId);
+	append_object_object(range, "identity", tmp);
+
+	/* SUBTYPE */
+	tmp = new_objtree_for_qualname_id(TypeRelationId,
+									  rangeForm->rngsubtype);
+	tmp = new_objtree_VA("SUBTYPE = %{type}D",
+						 2,
+						 "clause", ObjTypeString, "subtype",
+						 "type", ObjTypeObject, tmp);
+	definition = lappend(definition, new_object_object(NULL, tmp));
+
+	/* SUBTYPE_OPCLASS */
+	if (OidIsValid(rangeForm->rngsubopc))
+	{
+		tmp = new_objtree_for_qualname_id(OperatorClassRelationId,
+										  rangeForm->rngsubopc);
+		tmp = new_objtree_VA("SUBTYPE_OPCLASS = %{opclass}D",
+							 2,
+							 "clause", ObjTypeString, "opclass",
+							 "opclass", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	/* COLLATION */
+	if (OidIsValid(rangeForm->rngcollation))
+	{
+		tmp = new_objtree_for_qualname_id(CollationRelationId,
+										  rangeForm->rngcollation);
+		tmp = new_objtree_VA("COLLATION = %{collation}D",
+							 2,
+							 "clause", ObjTypeString, "collation",
+							 "collation", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	/* CANONICAL */
+	if (OidIsValid(rangeForm->rngcanonical))
+	{
+		tmp = new_objtree_for_qualname_id(ProcedureRelationId,
+										  rangeForm->rngcanonical);
+		tmp = new_objtree_VA("CANONICAL = %{canonical}D",
+							 2,
+							 "clause", ObjTypeString, "canonical",
+							 "canonical", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	/* SUBTYPE_DIFF */
+	if (OidIsValid(rangeForm->rngsubdiff))
+	{
+		tmp = new_objtree_for_qualname_id(ProcedureRelationId,
+										  rangeForm->rngsubdiff);
+		tmp = new_objtree_VA("SUBTYPE_DIFF = %{subtype_diff}D",
+							 2,
+							 "clause", ObjTypeString, "subtype_diff",
+							 "subtype_diff", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	append_array_object(range, "definition", definition);
+
+	systable_endscan(scan);
+	heap_close(pg_range, RowExclusiveLock);
+
+	command = jsonize_objtree(range);
+	free_objtree(range);
+
+	return command;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -1903,7 +2007,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
-			command = NULL;
+			command = deparse_CreateRangeStmt(objectId, parsetree);
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-- 
1.9.1

0007-deparse-Support-CREATE-EXTENSION.patchtext/x-diff; charset=us-asciiDownload
>From 394725858868f281e3649b01a32f75e37bb6da80 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/30] deparse: Support CREATE EXTENSION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 0cf2c55..bb81c50 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"
@@ -609,6 +610,85 @@ get_persistence_str(char persistence)
 }
 
 /*
+ * deparse_CreateExtensionStmt
+ *		deparse a CreateExtensionStmt
+ *
+ * Given an extension OID and the parsetree that created it, return the JSON
+ * blob representing the creation command.
+ *
+ * XXX the current representation makes the output command dependant on the
+ * installed versions of the extension.  Is this a problem?
+ */
+static char *
+deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
+{
+	CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *extStmt;
+	ObjTree	   *tmp;
+	char	   *command;
+	List	   *list;
+	ListCell   *cell;
+
+	pg_extension = heap_open(ExtensionRelationId, AccessShareLock);
+	extTup = get_catalog_object_by_oid(pg_extension, objectId);
+	if (!HeapTupleIsValid(extTup))
+		elog(ERROR, "cache lookup failed for extension with OID %u",
+			 objectId);
+	extForm = (Form_pg_extension) GETSTRUCT(extTup);
+
+	extStmt = new_objtree_VA("CREATE EXTENSION %{if_not_exists}s %{identity}I "
+							 "%{options: }s",
+							 1, "identity", ObjTypeString, node->extname);
+	append_string_object(extStmt, "if_not_exists",
+						 node->if_not_exists ? "IF NOT EXISTS" : "");
+	list = NIL;
+	foreach(cell, node->options)
+	{
+		DefElem *opt = (DefElem *) lfirst(cell);
+
+		if (strcmp(opt->defname, "schema") == 0)
+		{
+			/* skip this one; we add one unconditionally below */
+			continue;
+		}
+		else if (strcmp(opt->defname, "new_version") == 0)
+		{
+			tmp = new_objtree_VA("VERSION %{version}L", 2,
+								 "type", ObjTypeString, "version",
+								 "version", ObjTypeString, defGetString(opt));
+			list = lappend(list, new_object_object(NULL, tmp));
+		}
+		else if (strcmp(opt->defname, "old_version") == 0)
+		{
+			tmp = new_objtree_VA("FROM %{version}L", 2,
+								 "type", ObjTypeString, "from",
+								 "version", ObjTypeString, defGetString(opt));
+			list = lappend(list, new_object_object(NULL, tmp));
+		}
+		else
+			elog(ERROR, "unsupported option %s", opt->defname);
+	}
+
+	tmp = new_objtree_VA("SCHEMA %{schema}I",
+						 2, "type", ObjTypeString, "schema",
+						 "schema", ObjTypeString,
+						 get_namespace_name(extForm->extnamespace));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	append_array_object(extStmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	command = jsonize_objtree(extStmt);
+	free_objtree(extStmt);
+
+	return command;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -1978,7 +2058,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_CreateExtensionStmt:
-			command = NULL;
+			command = deparse_CreateExtensionStmt(objectId, parsetree);
 			break;
 
 			/* FDW-related objects */
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c98c7dc..5a1661a 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1177,7 +1177,8 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				objectId = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, parsetree);
 				break;
 
 			case T_AlterExtensionContentsStmt:
-- 
1.9.1

0008-deparse-Support-CREATE-RULE.patchtext/x-diff; charset=us-asciiDownload
>From b73531f049177c897c6b3560429f6afb2449206c 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/30] deparse: Support CREATE RULE

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index bb81c50..e8cfec2 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"
@@ -1974,6 +1976,99 @@ deparse_IndexStmt(Oid objectId, Node *parsetree)
 	return command;
 }
 
+static char *
+deparse_RuleStmt(Oid objectId, Node *parsetree)
+{
+	RuleStmt *node = (RuleStmt *) parsetree;
+	ObjTree	   *ruleStmt;
+	ObjTree	   *tmp;
+	char	   *command;
+	Relation	pg_rewrite;
+	Form_pg_rewrite rewrForm;
+	HeapTuple	rewrTup;
+	SysScanDesc	scan;
+	ScanKeyData	key;
+	Datum		ev_qual;
+	Datum		ev_actions;
+	bool		isnull;
+	char	   *qual;
+	List	   *actions;
+	List	   *list;
+	ListCell   *cell;
+
+	pg_rewrite = heap_open(RewriteRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(objectId));
+
+	scan = systable_beginscan(pg_rewrite, RewriteOidIndexId, true,
+							  NULL, 1, &key);
+	rewrTup = systable_getnext(scan);
+	if (!HeapTupleIsValid(rewrTup))
+		elog(ERROR, "cache lookup failed for rewrite rule with oid %u",
+			 objectId);
+
+	rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup);
+
+	ruleStmt =
+		new_objtree_VA("CREATE %{or_replace}s RULE %{identity}I "
+					   "AS ON %{event}s TO %{table}D %{where_clause}s "
+					   "DO %{instead}s (%{actions:; }s)", 2,
+					   "identity", ObjTypeString, node->rulename,
+					   "or_replace", ObjTypeString,
+					   node->replace ? "OR REPLACE" : "");
+	append_string_object(ruleStmt, "event",
+						 node->event == CMD_SELECT ? "SELECT" :
+						 node->event == CMD_UPDATE ? "UPDATE" :
+						 node->event == CMD_DELETE ? "DELETE" :
+						 node->event == CMD_INSERT ? "INSERT" : "XXX");
+	append_object_object(ruleStmt, "table",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 rewrForm->ev_class));
+
+	append_string_object(ruleStmt, "instead",
+						 node->instead ? "INSTEAD" : "ALSO");
+
+	ev_qual = heap_getattr(rewrTup, Anum_pg_rewrite_ev_qual,
+						   RelationGetDescr(pg_rewrite), &isnull);
+	ev_actions = heap_getattr(rewrTup, Anum_pg_rewrite_ev_action,
+							  RelationGetDescr(pg_rewrite), &isnull);
+
+	pg_get_ruledef_details(ev_qual, ev_actions, &qual, &actions);
+
+	tmp = new_objtree_VA("WHERE %{clause}s", 0);
+
+	if (qual)
+		append_string_object(tmp, "clause", qual);
+	else
+	{
+		append_null_object(tmp, "clause");
+		append_bool_object(tmp, "present", false);
+	}
+
+	append_object_object(ruleStmt, "where_clause", tmp);
+
+	list = NIL;
+	foreach(cell, actions)
+	{
+		char *action = lfirst(cell);
+
+		list = lappend(list, new_string_object(NULL, action));
+	}
+	append_array_object(ruleStmt, "actions", list);
+
+	systable_endscan(scan);
+	heap_close(pg_rewrite, AccessShareLock);
+
+	command = jsonize_objtree(ruleStmt);
+	free_objtree(ruleStmt);
+
+	return command;
+}
+
+
+
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -2099,7 +2194,7 @@ deparse_parsenode_cmd(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 56e74b7..6840c7a 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 0503816..46b4d98 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);
-- 
1.9.1

0009-deparse-Support-ALTER-TYPE-ADD-VALUE-enums.patchtext/x-diff; charset=us-asciiDownload
>From 57f9997c7aa10ce4cc90aa8b3ac2af1abe586d3b 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/30] deparse: Support ALTER TYPE / ADD VALUE (enums)

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index e8cfec2..6df0ec1 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2113,6 +2113,43 @@ deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
+{
+	AlterEnumStmt *node = (AlterEnumStmt *) parsetree;
+	ObjTree	   *alterEnum;
+	ObjTree	   *tmp;
+	char	   *command;
+
+	alterEnum =
+		new_objtree_VA("ALTER TYPE %{identity}D ADD VALUE %{if_not_exists}s %{value}L %{position}s",
+					   0);
+
+	append_string_object(alterEnum, "if_not_exists",
+						 node->skipIfExists ? "IF NOT EXISTS" : "");
+	append_object_object(alterEnum, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	append_string_object(alterEnum, "value", node->newVal);
+	tmp = new_objtree_VA("%{after_or_before}s %{neighbour}L", 0);
+	if (node->newValNeighbor)
+	{
+		append_string_object(tmp, "after_or_before",
+							 node->newValIsAfter ? "AFTER" : "BEFORE");
+		append_string_object(tmp, "neighbour", node->newValNeighbor);
+	}
+	else
+	{
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(alterEnum, "position", tmp);
+
+	command = jsonize_objtree(alterEnum);
+	free_objtree(alterEnum);
+
+	return command;
+}
+
+static char *
 deparse_parsenode_cmd(StashedCommand *cmd)
 {
 	Oid			objectId;
@@ -2185,6 +2222,10 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			command = deparse_CreateRangeStmt(objectId, parsetree);
 			break;
 
+		case T_AlterEnumStmt:
+			command = deparse_AlterEnumStmt(objectId, parsetree);
+			break;
+
 		case T_ViewStmt:		/* CREATE VIEW */
 			command = NULL;
 			break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5a1661a..e09728e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1244,7 +1244,8 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				objectId = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-- 
1.9.1

0010-deparse-Support-for-ALTER-OBJECT-RENAME.patchtext/x-diff; charset=us-asciiDownload
>From 4d3b0efb5a9aa7c41abe0c4c879f415474ebc10f 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/30] deparse: Support for ALTER <OBJECT> RENAME

It supports everything but functions, aggregates, operator classes and
operator families.
---
 src/backend/commands/alter.c       |   7 +-
 src/backend/commands/tablecmds.c   |  26 ++--
 src/backend/tcop/deparse_utility.c | 281 ++++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c         |  11 +-
 src/include/commands/alter.h       |   2 +-
 src/include/commands/tablecmds.h   |   2 +-
 6 files changed, 313 insertions(+), 16 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c9a9baf..298edad 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)
 	{
@@ -330,7 +333,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 9587217..b49bd30 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 column number of the attribute in the 'myrelid' relation.
  */
-static void
+static int
 renameatt_internal(Oid myrelid,
 				   const char *oldattname,
 				   const char *newattname,
@@ -2300,6 +2302,8 @@ renameatt_internal(Oid myrelid,
 	heap_close(attrelation, RowExclusiveLock);
 
 	relation_close(targetrelation, NoLock);		/* close rel but keep lock */
+
+	return attnum;
 }
 
 /*
@@ -2324,9 +2328,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, int32 *objsubid)
 {
 	Oid			relid;
+	int			attnum;
 
 	/* lock level taken here should match renameatt_internal */
 	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
@@ -2342,13 +2347,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/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 6df0ec1..f568df3 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1612,6 +1612,285 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return command;
 }
 
+/*
+ * Return the given object type as a string.
+ */
+static const char *
+stringify_objtype(ObjectType objtype)
+{
+	switch (objtype)
+	{
+		case OBJECT_AGGREGATE:
+			return "AGGREGATE";
+		case OBJECT_COLUMN:
+			return "COLUMN";
+		case OBJECT_DOMAIN:
+			return "DOMAIN";
+		case OBJECT_COLLATION:
+			return "COLLATION";
+		case OBJECT_CONVERSION:
+			return "CONVERSION";
+		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_OPERATOR:
+			return "OPERATOR";
+		case OBJECT_OPCLASS:
+			return "OPERATOR CLASS";
+		case OBJECT_OPFAMILY:
+			return "OPERATOR FAMILY";
+		case OBJECT_SCHEMA:
+			return "SCHEMA";
+		case OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case OBJECT_TABLE:
+			return "TABLE";
+		case OBJECT_TSCONFIGURATION:
+			return "TEXT SEARCH CONFIGURATION";
+		case OBJECT_TSDICTIONARY:
+			return "TEXT SEARCH DICTIONARY";
+		case OBJECT_TSPARSER:
+			return "TEXT SEARCH PARSER";
+		case OBJECT_TSTEMPLATE:
+			return "TEXT SEARCH TEMPLATE";
+		case OBJECT_TYPE:
+			return "TYPE";
+		case OBJECT_VIEW:
+			return "VIEW";
+
+		default:
+			elog(ERROR, "unsupported objtype %d", objtype);
+	}
+}
+
+static char *
+deparse_RenameStmt(Oid objectId, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree	   *renameStmt;
+	char	   *command;
+	char	   *fmtstr;
+	Relation	relation;
+	Oid			schemaId;
+	const char *subthing;
+
+	/*
+	 * FIXME --- this code is missing support for inheritance behavioral flags,
+	 * i.e. the "*" and ONLY elements.
+	 */
+
+	/*
+	 * In a ALTER .. RENAME command, we don't have the original name of the
+	 * object in system catalogs: since we inspect them after the command has
+	 * executed, the old name is already gone.  Therefore, we extract it from
+	 * the parse node.  Note we still extract the schema name from the catalog
+	 * (it might not be present in the parse node); it cannot possibly have
+	 * changed anyway.
+	 *
+	 * XXX what if there's another event trigger running concurrently that
+	 * renames the schema or moves the object to another schema?  Seems
+	 * pretty far-fetched, but possible nonetheless.
+	 */
+	switch (node->renameType)
+	{
+		case OBJECT_TABLE:
+		case OBJECT_SEQUENCE:
+		case OBJECT_VIEW:
+		case OBJECT_MATVIEW:
+		case OBJECT_INDEX:
+		case OBJECT_FOREIGN_TABLE:
+			fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME TO %%{newname}I",
+							  stringify_objtype(node->renameType));
+			relation = relation_open(objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_object_object(renameStmt, "identity",
+								 new_objtree_for_qualname(schemaId,
+														  node->relation->relname));
+			append_string_object(renameStmt, "if_exists",
+								 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+			break;
+
+		case OBJECT_COLUMN:
+		case OBJECT_ATTRIBUTE:
+			relation = relation_open(objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			if (node->renameType == OBJECT_COLUMN)
+				subthing = "COLUMN";
+			else
+				subthing = "ATTRIBUTE";
+
+			fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME %s %%{colname}I TO %%{newname}I",
+							  stringify_objtype(node->relationType),
+							  subthing);
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_object_object(renameStmt, "identity",
+								 new_objtree_for_qualname(schemaId,
+														  node->relation->relname));
+			append_string_object(renameStmt, "colname", node->subname);
+			append_string_object(renameStmt, "if_exists",
+								 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+			break;
+
+		case OBJECT_SCHEMA:
+		case OBJECT_FDW:
+		case OBJECT_LANGUAGE:
+		case OBJECT_FOREIGN_SERVER:
+			fmtstr = psprintf("ALTER %s %%{identity}I RENAME TO %%{newname}I",
+							  stringify_objtype(node->renameType));
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_string_object(renameStmt, "identity",
+								 node->subname);
+			break;
+
+		case OBJECT_COLLATION:
+		case OBJECT_CONVERSION:
+		case OBJECT_DOMAIN:
+		case OBJECT_TSDICTIONARY:
+		case OBJECT_TSPARSER:
+		case OBJECT_TSTEMPLATE:
+		case OBJECT_TSCONFIGURATION:
+		case OBJECT_TYPE:
+			{
+				ObjTree    *ident;
+				HeapTuple	objTup;
+				Oid			catalogId;
+				Relation	catalog;
+				bool		isnull;
+				AttrNumber	nspnum;
+
+				catalogId = get_objtype_catalog_oid(node->renameType);
+				catalog = heap_open(catalogId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, objectId);
+				nspnum = get_object_attnum_namespace(catalogId);
+
+				schemaId = DatumGetObjectId(heap_getattr(objTup,
+														 nspnum,
+														 RelationGetDescr(catalog),
+														 &isnull));
+
+				fmtstr = psprintf("ALTER %s %%{identity}D RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				ident = new_objtree_for_qualname(schemaId,
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+				relation_close(catalog, AccessShareLock);
+
+			}
+			break;
+
+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			elog(ERROR, "renaming of functions and aggregates is not supported yet");
+
+		case OBJECT_CONSTRAINT:
+			{
+				HeapTuple		conTup;
+				Form_pg_constraint	constrForm;
+				ObjTree		   *ident;
+
+				conTup = SearchSysCache1(CONSTROID, objectId);
+				constrForm = (Form_pg_constraint) GETSTRUCT(conTup);
+				fmtstr = psprintf("ALTER %s %%{identity}D RENAME CONSTRAINT %%{conname}I TO %%{newname}I",
+								  stringify_objtype(node->relationType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+
+				switch (node->relationType)
+				{
+					case OBJECT_DOMAIN:
+						ident = new_objtree_for_qualname_id(TypeRelationId,
+															constrForm->contypid);
+						break;
+					case OBJECT_TABLE:
+						ident = new_objtree_for_qualname_id(RelationRelationId,
+															constrForm->conrelid);
+						break;
+					default:
+						elog(ERROR, "invalid relation type %d", node->relationType);
+				}
+
+				append_string_object(renameStmt, "conname", node->subname);
+				append_object_object(renameStmt, "identity", ident);
+				ReleaseSysCache(conTup);
+			}
+			break;
+
+		case OBJECT_OPCLASS:
+		case OBJECT_OPFAMILY:
+			ereport(ERROR,
+					(errmsg("renaming of operator classes and families is not supported")));
+			break;
+
+		case OBJECT_RULE:
+			{
+				HeapTuple	rewrTup;
+				Form_pg_rewrite rewrForm;
+				Relation	pg_rewrite;
+
+				pg_rewrite = relation_open(RewriteRelationId, AccessShareLock);
+				rewrTup = get_catalog_object_by_oid(pg_rewrite, objectId);
+				rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup);
+
+				renameStmt = new_objtree_VA("ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "rulename", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 rewrForm->ev_class));
+				relation_close(pg_rewrite, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_TRIGGER:
+			{
+				HeapTuple	trigTup;
+				Form_pg_trigger trigForm;
+				Relation	pg_trigger;
+
+				pg_trigger = relation_open(TriggerRelationId, AccessShareLock);
+				trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+				trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+				renameStmt = new_objtree_VA("ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "triggername", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 trigForm->tgrelid));
+				relation_close(pg_trigger, AccessShareLock);
+			}
+			break;
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	append_string_object(renameStmt, "newname", node->newname);
+
+	command = jsonize_objtree(renameStmt);
+	free_objtree(renameStmt);
+
+	return command;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -2283,7 +2562,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_RenameStmt:
-			command = NULL;
+			command = deparse_RenameStmt(objectId, parsetree);
 			break;
 
 		case T_AlterObjectSchemaStmt:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index e09728e..063c9f4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -798,7 +798,7 @@ standard_ProcessUtility(Node *parsetree,
 									   context, params,
 									   dest, completionTag);
 				else
-					ExecRenameStmt(stmt);
+					ExecRenameStmt(stmt, NULL);
 			}
 			break;
 
@@ -1347,7 +1347,14 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree);
+				{
+					RenameStmt *stmt = (RenameStmt *) parsetree;
+					int			objsubid;
+
+					objectId = ExecRenameStmt(stmt, &objsubid);
+					EventTriggerStashCommand(objectId, objsubid,
+											 stmt->renameType, parsetree);
+				}
 				break;
 
 			case T_AlterObjectSchemaStmt:
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index 5907184..b8615ab 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 932322f..213bb29 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);
 
-- 
1.9.1

0011-deparse-Support-CREATE-DOMAIN.patchtext/x-diff; charset=us-asciiDownload
>From cb63bb8c0bc83c6d5bbd5c54ac97b4b680ade36b 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/30] deparse: Support CREATE DOMAIN

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

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

0012-deparse-Support-CREATE-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From 3d6e4017bb33b06bf897df6fe416c651cf6628fc 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/30] deparse: Support CREATE FUNCTION

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

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

0013-deparse-Support-ALTER-TABLE.patchtext/x-diff; charset=us-asciiDownload
>From 244b451e00c8a31ef7b6f70354f9977ed8bbac42 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/30] deparse: Support ALTER TABLE

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

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 3f278d9..437e3f1 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -51,6 +51,7 @@ typedef struct EventTriggerQueryState
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
 	MemoryContext cxt;
+	StashedCommand *curcmd;
 	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
@@ -1043,6 +1044,7 @@ EventTriggerBeginCompleteQuery(void)
 	state->cxt = cxt;
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
+	state->curcmd = NULL;
 	state->stash = NIL;
 
 	state->previous = currentEventTriggerState;
@@ -1330,6 +1332,113 @@ 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;
+	/* XXX is it necessary to have the whole parsetree? probably not ... */
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->curcmd = stashed;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+void
+EventTriggerComplexCmdSetOid(Oid objectId)
+{
+	currentEventTriggerState->curcmd->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerRecordSubcmd
+ * 		Save data about a single part of a complex DDL command
+ *
+ * Right now we only support ALTER TABLE; there are no other DDL commands that
+ * require this.  (ALTER TYPE can also generate multiple subcommands, but it's
+ * actually parsed as ALTER TABLE, so there is no difference at this level.)
+ */
+void
+EventTriggerRecordSubcmd(Node *subcmd, Oid relid, AttrNumber attnum,
+						 Oid newoid)
+{
+	MemoryContext	oldcxt;
+	StashedATSubcmd *newsub;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(OidIsValid(currentEventTriggerState->curcmd->d.alterTable.objectId));
+
+	/*
+	 * If we receive a subcommand intended for a relation other than the one
+	 * we've started the complex command for, ignore it.  This is chiefly
+	 * concerned with inheritance situations: in such cases, alter table
+	 * would dispatch multiple copies of the same command for various things,
+	 * but we're only concerned with the one for the main table.
+	 */
+	if (relid != currentEventTriggerState->curcmd->d.alterTable.objectId)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(StashedATSubcmd));
+	newsub->attnum = attnum;
+	newsub->oid = newoid;
+	newsub->parsetree = copyObject(subcmd);
+
+	currentEventTriggerState->curcmd->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->curcmd->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerEndRecordingSubcmds
+ * 		Finish up saving a complex DDL command
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerComplexCmdEnd(void)
+{
+	/* If no subcommands, don't stash anything */
+	if (list_length(currentEventTriggerState->curcmd->d.alterTable.subcmds) != 0)
+	{
+		currentEventTriggerState->stash =
+			lappend(currentEventTriggerState->stash,
+					currentEventTriggerState->curcmd);
+	}
+	else
+		pfree(currentEventTriggerState->curcmd);
+
+	currentEventTriggerState->curcmd = NULL;
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1408,7 +1517,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
 			MemSet(nulls, 0, sizeof(nulls));
 
-			if (cmd->type == SCT_Basic)
+			if (cmd->type == SCT_Basic ||
+				cmd->type == SCT_AlterTable)
 			{
 				Oid			classId;
 				Oid			objId;
@@ -1424,6 +1534,12 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 					objId = cmd->d.basic.objectId;
 					objSubId = cmd->d.basic.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 b49bd30..056f0c6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2757,6 +2757,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerComplexCmdSetOid(relid);
+
 	ATController(rel, cmds, recurse, lockmode);
 }
 
@@ -3622,6 +3624,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
@@ -5726,6 +5731,8 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
  * There is no such command in the grammar, but parse_utilcmd.c converts
  * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
  * us schedule creation of the index at the appropriate time during ALTER.
+ *
+ * Return value is the OID of the new index.
  */
 static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index a7ca33c..d591eff 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2802,6 +2802,396 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_AlterTableStmt(StashedCommand *cmd)
+{
+	ObjTree	   *alterTableStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	List	   *dpcontext;
+	Relation	rel;
+	List	   *subcmds = NIL;
+	ListCell   *cell;
+	char	   *command;
+
+	rel = heap_open(cmd->d.alterTable.objectId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									cmd->d.alterTable.objectId);
+
+	alterTableStmt =
+		new_objtree_VA("ALTER TABLE %{identity}D %{subcmds:, }s", 0);
+	tmp = new_objtree_for_qualname(rel->rd_rel->relnamespace,
+								   RelationGetRelationName(rel));
+	append_object_object(alterTableStmt, "identity", tmp);
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		StashedATSubcmd	*substashed = (StashedATSubcmd *) lfirst(cell);
+		AlterTableCmd	*subcmd = (AlterTableCmd *) substashed->parsetree;
+		ObjTree	   *tree;
+
+		Assert(IsA(subcmd, AlterTableCmd));
+
+		switch (subcmd->subtype)
+		{
+			case AT_AddColumn:
+			case AT_AddColumnRecurse:
+				/* XXX need to set the "recurse" bit somewhere? */
+				Assert(IsA(subcmd->def, ColumnDef));
+				tree = deparse_ColumnDef(rel, dpcontext, false,
+										 (ColumnDef *) subcmd->def);
+				tmp = new_objtree_VA("ADD COLUMN %{definition}s",
+									 2, "type", ObjTypeString, "add column",
+									 "definition", ObjTypeObject, tree);
+				subcmds = lappend(subcmds,
+								  new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropColumnRecurse:
+			case AT_ValidateConstraintRecurse:
+			case AT_DropConstraintRecurse:
+			case AT_AddOidsRecurse:
+			case AT_AddIndexConstraint:
+			case AT_ReAddIndex:
+			case AT_ReAddConstraint:
+			case AT_ProcessedConstraint:
+			case AT_ReplaceRelOptions:
+				/* Subtypes used for internal operations; nothing to do here */
+				break;
+
+			case AT_AddColumnToView:
+				/* CREATE OR REPLACE VIEW -- nothing to do here */
+				break;
+
+			case AT_ColumnDefault:
+				if (subcmd->def == NULL)
+				{
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP DEFAULT",
+										 1, "type", ObjTypeString, "drop default");
+				}
+				else
+				{
+					List	   *dpcontext;
+					HeapTuple	attrtup;
+					AttrNumber	attno;
+
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DEFAULT %{definition}s",
+										 1, "type", ObjTypeString, "set default");
+
+					dpcontext = deparse_context_for(RelationGetRelationName(rel),
+													RelationGetRelid(rel));
+					attrtup = SearchSysCacheAttName(RelationGetRelid(rel), subcmd->name);
+					attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum;
+					append_string_object(tmp, "definition",
+										 RelationGetColumnDefault(rel, attno, dpcontext));
+					ReleaseSysCache(attrtup);
+				}
+				append_string_object(tmp, "column", subcmd->name);
+
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropNotNull:
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP NOT NULL",
+									 1, "type", ObjTypeString, "drop not null");
+				append_string_object(tmp, "column", subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetNotNull:
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET NOT NULL",
+									 1, "type", ObjTypeString, "set not null");
+				append_string_object(tmp, "column", subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetStatistics:
+				/* not yet */
+				break;
+
+			case AT_SetOptions:
+				/* not yet */
+				break;
+
+			case AT_ResetOptions:
+				/* not yet */
+				break;
+
+			case AT_SetStorage:
+				Assert(IsA(subcmd->def, String));
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STORAGE %{storage}s",
+									 3, "type", ObjTypeString, "set storage",
+									 "column", ObjTypeString, subcmd->name,
+									 "storage", ObjTypeString,
+									 strVal((Value *) subcmd->def));
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropColumn:
+				tmp = new_objtree_VA("DROP COLUMN %{column}I",
+									 2, "type", ObjTypeString, "drop column",
+								 "column", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AddIndex:
+				{
+					Oid			idxOid = substashed->oid;
+					IndexStmt  *istmt;
+					Relation	idx;
+					const char *idxname;
+					Oid			constrOid;
+
+					Assert(IsA(subcmd->def, IndexStmt));
+					istmt = (IndexStmt *) subcmd->def;
+
+					if (!istmt->isconstraint)
+						break;
+
+					idx = relation_open(idxOid, AccessShareLock);
+					idxname = RelationGetRelationName(idx);
+
+					constrOid = get_relation_constraint_oid(
+						cmd->d.alterTable.objectId, idxname, false);
+
+					tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s",
+										 3, "type", ObjTypeString, "add constraint",
+										 "name", ObjTypeString, idxname,
+										 "definition", ObjTypeString,
+										 pg_get_constraintdef_string(constrOid, false));
+					subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;
+
+			case AT_AddConstraint:
+			case AT_AddConstraintRecurse:
+				{
+					/* XXX need to set the "recurse" bit somewhere? */
+					Oid			constrOid = substashed->oid;
+
+					tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s",
+										 3, "type", ObjTypeString, "add constraint",
+										 "name", ObjTypeString, get_constraint_name(constrOid),
+										 "definition", ObjTypeString,
+										 pg_get_constraintdef_string(constrOid, false));
+					subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				}
+				break;
+
+			case AT_AlterConstraint:
+				break;
+
+			case AT_ValidateConstraint:
+				tmp = new_objtree_VA("VALIDATE CONSTRAINT %{constraint}I", 2,
+									 "type", ObjTypeString, "validate constraint",
+									 "constraint", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropConstraint:
+				tmp = new_objtree_VA("DROP CONSTRAINT %{constraint}I", 2,
+									 "type", ObjTypeString, "drop constraint",
+									 "constraint", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AlterColumnType:
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DATA TYPE %{datatype}T collate_clause using_clause",
+									 2, "type", ObjTypeString, "alter column type",
+									 "column", ObjTypeString, subcmd->name);
+				/* FIXME figure out correct typid/typmod , collate clause, using_clause */
+				append_object_object(tmp, "datatype",
+									 new_objtree_for_type(INT4OID, -1));
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				break;
+
+			case AT_ChangeOwner:
+				tmp = new_objtree_VA("OWNER TO %{owner}I",
+									 2, "type", ObjTypeString, "change owner",
+									 "owner",  ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_ClusterOn:
+				tmp = new_objtree_VA("CLUSTER ON %{index}I", 2,
+									 "type", ObjTypeString, "cluster on",
+									 "index", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropCluster:
+				tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+									 "type", ObjTypeString, "set without cluster");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AddOids:
+				tmp = new_objtree_VA("SET WITH OIDS", 1,
+									 "type", ObjTypeString, "set with oids");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropOids:
+				tmp = new_objtree_VA("SET WITHOUT OIDS", 1,
+									 "type", ObjTypeString, "set without oids");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetTableSpace:
+				tmp = new_objtree_VA("SET TABLESPACE %{tablespace}I", 2,
+									 "type", ObjTypeString, "set tablespace",
+									 "tablespace", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetRelOptions:
+				break;
+
+			case AT_ResetRelOptions:
+				break;
+
+				/*
+				 * FIXME --- should we unify representation of all these
+				 * ENABLE/DISABLE TRIGGER commands??
+				 */
+			case AT_EnableTrig:
+				tmp = new_objtree_VA("ENABLE TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "enable trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableAlwaysTrig:
+				tmp = new_objtree_VA("ENABLE ALWAYS TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "enable always trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableReplicaTrig:
+				tmp = new_objtree_VA("ENABLE REPLICA TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "enable replica trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DisableTrig:
+				tmp = new_objtree_VA("DISABLE TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "disable trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableTrigAll:
+				tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "enable trigger all");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DisableTrigAll:
+				tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "disable trigger all");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableTrigUser:
+				tmp = new_objtree_VA("ENABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "enable trigger user");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DisableTrigUser:
+				tmp = new_objtree_VA("DISABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "disable trigger user");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableRule:
+				break;
+
+			case AT_EnableAlwaysRule:
+				break;
+
+			case AT_EnableReplicaRule:
+				break;
+
+			case AT_DisableRule:
+				break;
+
+			case AT_AddInherit:
+				/*
+				 * XXX this case is interesting: we cannot rely on parse node
+				 * because parent name might be unqualified; but there's no way
+				 * to extract it from catalog either, since we don't know which
+				 * of the parents is the new one.
+				 */
+				break;
+
+			case AT_DropInherit:
+				/* XXX ditto ... */
+				break;
+
+			case AT_AddOf:
+				break;
+
+			case AT_DropOf:
+				break;
+
+			case AT_ReplicaIdentity:
+				tmp = new_objtree_VA("REPLICA IDENTITY %{ident}s", 1,
+									 "type", ObjTypeString, "replica identity");
+				switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type)
+				{
+					case REPLICA_IDENTITY_DEFAULT:
+						append_string_object(tmp, "ident", "DEFAULT");
+						break;
+					case REPLICA_IDENTITY_FULL:
+						append_string_object(tmp, "ident", "FULL");
+						break;
+					case REPLICA_IDENTITY_NOTHING:
+						append_string_object(tmp, "ident", "NOTHING");
+						break;
+					case REPLICA_IDENTITY_INDEX:
+						tmp2 = new_objtree_VA("USING INDEX %{index}I", 1,
+											  "index", ObjTypeString,
+											  ((ReplicaIdentityStmt *) subcmd->def)->name);
+						append_object_object(tmp, "ident", tmp2);
+						break;
+				}
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_GenericOptions:
+				break;
+
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+	}
+
+	if (list_length(subcmds) == 0)
+	{
+		command = NULL;
+	}
+	else
+	{
+		append_array_object(alterTableStmt, "subcmds", subcmds);
+		command = jsonize_objtree(alterTableStmt);
+	}
+
+	free_objtree(alterTableStmt);
+	heap_close(rel, AccessShareLock);
+
+	return command;
+}
+
+static char *
 deparse_parsenode_cmd(StashedCommand *cmd)
 {
 	Oid			objectId;
@@ -2817,6 +3207,11 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			objectId = cmd->d.basic.objectId;
 			objectSubId = cmd->d.basic.objectSubId;
 			break;
+		case SCT_AlterTable:
+			/* XXX needed? */
+			objectId = cmd->d.alterTable.objectId;
+			objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
@@ -2832,6 +3227,10 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			command = deparse_CreateStmt(objectId, parsetree);
 			break;
 
+		case T_AlterTableStmt:
+			command = deparse_AlterTableStmt(cmd);
+			break;
+
 			/* other local objects */
 		case T_DefineStmt:
 			command = NULL;
@@ -3007,6 +3406,7 @@ deparse_utility_command(StashedCommand *cmd)
 	switch (cmd->type)
 	{
 		case SCT_Basic:
+		case SCT_AlterTable:
 			command = deparse_parsenode_cmd(cmd);
 			break;
 		default:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 063c9f4..a91e3c5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -984,6 +984,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)
 						{
@@ -997,19 +1001,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,
@@ -1158,6 +1175,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,
@@ -1168,6 +1186,7 @@ ProcessUtilitySlow(Node *parsetree,
 									false); /* quiet */
 					EventTriggerStashCommand(objectId, 0, OBJECT_INDEX,
 											 parsetree);
+					EventTriggerComplexCmdEnd();
 				}
 				break;
 
@@ -1249,8 +1268,10 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
+				EventTriggerComplexCmdStart(parsetree, OBJECT_VIEW);	/* XXX relkind? */
 				objectId = DefineView((ViewStmt *) parsetree, queryString);
 				EventTriggerStashCommand(objectId, 0, OBJECT_VIEW, 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 9c276f1..07eb515 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -54,5 +54,10 @@ extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
 
 extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
 						 ObjectType objtype, Node *parsetree);
+extern void EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype);
+extern void EventTriggerComplexCmdSetOid(Oid objectId);
+extern void EventTriggerRecordSubcmd(Node *subcmd, Oid relid,
+						 AttrNumber attnum, Oid newoid);
+extern void EventTriggerComplexCmdEnd(void);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index d95c33d..2a077f7 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -26,6 +26,7 @@
 typedef enum StashedCommandType
 {
 	SCT_Basic,
+	SCT_AlterTable
 } StashedCommandType;
 
 /*
@@ -52,6 +53,13 @@ typedef struct StashedCommand
 			uint32		objectSubId;
 			ObjectType	objtype;
 		} basic;
+
+		struct AlterTableCommand
+		{
+			Oid		objectId;
+			ObjectType objtype;
+			List   *subcmds;
+		} alterTable;
 	} d;
 } StashedCommand;
 
-- 
1.9.1

0014-deparse-Support-CREATE-VIEW.patchtext/x-diff; charset=us-asciiDownload
>From bd7464efd8de502de04b0ebabc3e52ea6365f250 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 24 Sep 2014 16:53:47 -0300
Subject: [PATCH 14/30] 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 d591eff..8e79aee 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -692,6 +692,42 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
 }
 
 /*
+ * deparse_ViewStmt
+ *		deparse a ViewStmt
+ *
+ * Given a view OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ */
+static char *
+deparse_ViewStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	char	   *command;
+	Relation	relation;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA("CREATE %{persistence}s VIEW %{identity}D AS %{query}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(viewStmt, "identity", tmp);
+
+	append_string_object(viewStmt, "query",
+						 pg_get_viewdef_internal(objectId));
+
+	command = jsonize_objtree(viewStmt);
+	free_objtree(viewStmt);
+
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -3278,7 +3314,7 @@ deparse_parsenode_cmd(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 771a83c..d0d0872 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 5f1f1e8..9c67b9a 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);
-- 
1.9.1

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

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 8e79aee..44fda78 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"
@@ -2838,6 +2839,45 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_CreateOpFamily(Oid objectId, Node *parsetree)
+{
+	HeapTuple   opfTup;
+	HeapTuple   amTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_am  amForm;
+	ObjTree	   *copfStmt;
+	ObjTree	   *tmp;
+	char	   *command;
+
+	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(opfTup))
+		elog(ERROR, "cache lookup failed for operator family with OID %u", objectId);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
+	if (!HeapTupleIsValid(amTup))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 opfForm->opfmethod);
+	amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+	copfStmt = new_objtree_VA("CREATE OPERATOR FAMILY %{identity}D USING %{amname}s",
+							  0);
+
+	tmp = new_objtree_for_qualname(opfForm->opfnamespace,
+								   NameStr(opfForm->opfname));
+	append_object_object(copfStmt, "identity", tmp);
+	append_string_object(copfStmt, "amname", NameStr(amForm->amname));
+
+	command = jsonize_objtree(copfStmt);
+	free_objtree(copfStmt);
+
+	ReleaseSysCache(amTup);
+	ReleaseSysCache(opfTup);
+
+	return command;
+}
+
+static char *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -3366,7 +3406,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_CreateOpFamilyStmt:
-			command = NULL;
+			command = deparse_CreateOpFamily(objectId, parsetree);
 			break;
 
 		case T_RenameStmt:
-- 
1.9.1

0016-deparse-Support-CREATE-CONVERSION.patchtext/x-diff; charset=us-asciiDownload
>From 6183f96c14f63270fd1e724405dd046b8ef8c92a 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/30] deparse: Support CREATE CONVERSION

---
 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 44fda78..f4c3e09 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"
@@ -2839,6 +2841,43 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+	HeapTuple   conTup;
+	Form_pg_conversion conForm;
+	ObjTree	   *ccStmt;
+	char	   *command;
+
+	conTup = SearchSysCache1(CONDEFAULT, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(conTup))
+		elog(ERROR, "cache lookup failed for conversion with OID %u", objectId);
+	conForm = (Form_pg_conversion) GETSTRUCT(conTup);
+
+	ccStmt = new_objtree_VA("CREATE %{default}s CONVERSION %{identity}D FOR "
+							"%{source}L TO %{dest}L FROM %{function}D", 0);
+
+	append_string_object(ccStmt, "default",
+						 conForm->condefault ? "DEFAULT" : "");
+	append_object_object(ccStmt, "identity",
+						 new_objtree_for_qualname(conForm->connamespace,
+												  NameStr(conForm->conname)));
+	append_string_object(ccStmt, "source", (char *)
+						 pg_encoding_to_char(conForm->conforencoding));
+	append_string_object(ccStmt, "dest", (char *)
+						 pg_encoding_to_char(conForm->contoencoding));
+	append_object_object(ccStmt, "function",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 conForm->conproc));
+
+	command = jsonize_objtree(ccStmt);
+	free_objtree(ccStmt);
+
+	ReleaseSysCache(conTup);
+
+	return command;
+}
+
+static char *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -3394,7 +3433,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_CreateConversionStmt:
-			command = NULL;
+			command = deparse_CreateConversion(objectId, parsetree);
 			break;
 
 		case T_CreateCastStmt:
-- 
1.9.1

0017-deparse-Support-CREATE-OPERATOR-via-DefineStmt.patchtext/x-diff; charset=us-asciiDownload
>From 351f3c51096b82854161b7e09f35222707380c2f 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/30] deparse: Support CREATE OPERATOR via DefineStmt

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

0023-deparse-Support-CREATE-TEXT-SEARCH-CONFIGURATION.patchtext/x-diff; charset=utf-8Download
>From 56529bafaf55661f7819f87d1b2c72a20e618469 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Tue, 6 May 2014 11:53:45 +0530
Subject: [PATCH 23/30] deparse: Support CREATE TEXT SEARCH CONFIGURATION

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

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

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

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

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

0025-deparse-support-ALTER-THING-OWNER-TO.patchtext/x-diff; charset=us-asciiDownload
>From 79fc1ea525cc9a7ff713e80238d302b9c4bcb00d 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 25/30] deparse: support ALTER THING OWNER TO

---
 src/backend/tcop/deparse_utility.c | 29 ++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c         |  5 ++++-
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index f45c894..93637c4 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3698,6 +3698,33 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+	ObjTree	   *ownerStmt;
+	ObjectAddress addr;
+	char	   *fmt;
+	char	   *command;
+
+	fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newname}I",
+				   stringify_objtype(node->objectType));
+	ownerStmt = new_objtree_VA(fmt, 0);
+	append_string_object(ownerStmt, "newname", node->newowner);
+
+	addr.classId = get_objtype_catalog_oid(node->objectType);
+	addr.objectId = objectId;
+	addr.objectSubId = 0;
+
+	append_string_object(ownerStmt, "identity",
+						 getObjectIdentity(&addr));
+
+	command = jsonize_objtree(ownerStmt);
+	free_objtree(ownerStmt);
+
+	return command;
+}
+
+static char *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
 	HeapTuple   conTup;
@@ -4314,7 +4341,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_AlterOwnerStmt:
-			command = NULL;
+			command = deparse_AlterOwnerStmt(objectId, parsetree);
 			break;
 
 		case T_GrantStmt:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a91e3c5..92fb46e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1383,7 +1383,10 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterOwnerStmt:
-				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				objectId = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0,
+										 ((AlterOwnerStmt *) parsetree)->objectType,
+										 parsetree);
 				break;
 
 			case T_GrantStmt:
-- 
1.9.1

0026-deparse-Support-ALTER-EXTENSION-UPDATE-TO.patchtext/x-diff; charset=us-asciiDownload
>From d861bcf26c69523c948736172b390a38da9d78d3 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 26/30] deparse: Support ALTER EXTENSION / UPDATE TO

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

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

0027-deparse-Support-GRANT-REVOKE.patchtext/x-diff; charset=us-asciiDownload
>From cb4ea7d3fa782df8096d3e56279289f1b010742a 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 27/30] deparse: Support GRANT/REVOKE

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

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

0030-deparse-Support-ALTER-TABLE-ALL-IN-TABLESPACE.patchtext/x-diff; charset=us-asciiDownload
>From 067dfc7640a48ab3ba18eaa858eda75d102598b6 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 8 Sep 2014 17:55:07 -0300
Subject: [PATCH 30/30] deparse: Support ALTER TABLE ALL IN TABLESPACE

This version emits one ALTER TABLE command for each table affected by
the ALL IN TABLESPACE variant.  We might revisit this later.
---
 src/backend/commands/tablecmds.c   | 2 ++
 src/backend/tcop/deparse_utility.c | 1 +
 src/backend/tcop/utility.c         | 1 +
 3 files changed, 4 insertions(+)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 056f0c6..9624a59 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9572,7 +9572,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 9c02626..80d0bbf 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4627,6 +4627,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_AlterTableStmt:
+		case T_AlterTableMoveAllStmt:
 			command = deparse_AlterTableStmt(cmd);
 			break;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 388157f..43a42ac 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1370,6 +1370,7 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterTableMoveAllStmt:
+				/* commands are stashed in AlterTableMoveAll */
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
 				break;
 
-- 
1.9.1

#2Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#1)
27 attachment(s)

Actually here's a different split of these patches, which I hope makes
more sense. My intention here is that patches 0001 to 0004 are simple
changes that can be pushed right away; they are not directly related to
the return-creation-command feature. Patches 0005 to 0027 implement
that feature incrementally. You can see in patch 0005 the DDL commands
that are still not implemented in deparse (they are the ones that have
an elog(ERROR) rather than a "command = NULL"). Patch 0006 adds calls
in ProcessUtilitySlow() to each command, so that the object(s) being
touched are added to the event trigger command stash.

Patches from 0007 to 0027 (excepting patch 0017) implement one or a
small number of commands in deparse. Patch 0017 is necessary
infrastructure in ALTER TABLE to support deparsing that one.

My intention with the later patches is that they would all be pushed as
a single commit, i.e. the deparse support would be implemented for all
commands in a fell swoop rather than piecemeal -- except possibly patch
0017 (the ALTER TABLE infrastructure). I split them up only for ease of
review. Of course, before pushing we (I) need to implement deparsing
for all the remaining commands.

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

Attachments:

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

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

diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 850008b..8849c08 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -28,6 +28,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/tqual.h"
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 55c1e79..c0eade0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -69,6 +69,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..49963ff 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -28,6 +28,7 @@
 #include "utils/json.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/tuplesort.h"
 #include "utils/xml.h"
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb16c53..d9263e3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -85,6 +85,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/relcache.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index ad364ef..55a6881 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -72,6 +72,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 4eeb631..67539ec 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -35,6 +35,7 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/timestamp.h"
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9ed7b71..18d9b63 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -55,6 +55,7 @@
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index d88e7a3..fb1b4a4 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -676,13 +676,10 @@ extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
-extern char *pg_get_indexdef_string(Oid indexrelid);
-extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS);
-extern char *pg_get_constraintdef_string(Oid constraintId);
 extern Datum pg_get_expr(PG_FUNCTION_ARGS);
 extern Datum pg_get_expr_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_userbyid(PG_FUNCTION_ARGS);
@@ -692,17 +689,9 @@ extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_arg_default(PG_FUNCTION_ARGS);
-extern char *deparse_expression(Node *expr, List *dpcontext,
-				   bool forceprefix, bool showimplicit);
-extern List *deparse_context_for(const char *aliasname, Oid relid);
-extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
-							  List *rtable, List *rtable_names);
-extern List *select_rtable_names_for_explain(List *rtable,
-								Bitmapset *rels_used);
 extern const char *quote_identifier(const char *ident);
 extern char *quote_qualified_identifier(const char *qualifier,
 						   const char *ident);
-extern char *generate_collation_name(Oid collid);
 
 
 /* tid.c */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
new file mode 100644
index 0000000..520b066
--- /dev/null
+++ b/src/include/utils/ruleutils.h
@@ -0,0 +1,34 @@
+/*-------------------------------------------------------------------------
+ *
+ * ruleutils.h
+ *		Declarations for ruleutils.c
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/ruleutils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef RULEUTILS_H
+#define RULEUTILS_H
+
+#include "nodes/nodes.h"
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+
+extern char *pg_get_indexdef_string(Oid indexrelid);
+extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
+
+extern char *pg_get_constraintdef_string(Oid constraintId);
+extern char *deparse_expression(Node *expr, List *dpcontext,
+				   bool forceprefix, bool showimplicit);
+extern List *deparse_context_for(const char *aliasname, Oid relid);
+extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
+							  List *rtable, List *rtable_names);
+extern List *select_rtable_names_for_explain(List *rtable,
+								Bitmapset *rels_used);
+extern char *generate_collation_name(Oid collid);
+
+#endif	/* RULEUTILS_H */
-- 
1.9.1

0002-deparse-core-have-RENAME-return-attribute-number.patchtext/x-diff; charset=us-asciiDownload
>From 5e3555058a1bbb93e25a3a104c26e6b96dce994d 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/27] deparse/core: have RENAME return attribute number

---
 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 c9a9baf..298edad 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)
 	{
@@ -330,7 +333,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 d9263e3..0281c6a 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, int32 *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 4a2a339..ab223ad 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -803,7 +803,7 @@ standard_ProcessUtility(Node *parsetree,
 									   context, params,
 									   dest, completionTag);
 				else
-					ExecRenameStmt(stmt);
+					ExecRenameStmt(stmt, NULL);
 			}
 			break;
 
@@ -1302,7 +1302,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 5907184..b8615ab 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 932322f..213bb29 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);
 
-- 
1.9.1

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

---
 doc/src/sgml/event-trigger.sgml      | 15 ++++++++++++++-
 src/backend/commands/event_trigger.c |  2 ++
 src/backend/tcop/utility.c           |  9 ++++-----
 3 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 6f71a27..49b8384 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
@@ -622,12 +623,24 @@
         <entry align="center"><literal>X</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>
+       </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>
         <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>
+       </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 1b8c94b..c6770ea 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -251,6 +251,8 @@ check_ddl_tag(const char *tag)
 		pg_strcasecmp(tag, "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;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ab223ad..b27c4d7 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -547,11 +547,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);
@@ -1313,6 +1308,10 @@ ProcessUtilitySlow(Node *parsetree,
 				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_GrantStmt:
+				ExecuteGrantStmt((GrantStmt *) parsetree);
+				break;
+
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
 				break;
-- 
1.9.1

0004-deparse-core-event-triggers-support-COMMENT.patchtext/x-diff; charset=us-asciiDownload
>From 5bb35d2e51fe6c6e219fe5cf7ddbab5423e6be1b 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/27] deparse/core: event triggers support COMMENT

---
 doc/src/sgml/event-trigger.sgml      |  8 +++++++-
 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, 30 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 49b8384..39ecd94 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
@@ -281,6 +281,12 @@
         <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>
+       </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 34f6cae..59781e5 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 c6770ea..7b55d8f 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -251,6 +251,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 b27c4d7..63a9485 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -512,10 +512,6 @@ standard_ProcessUtility(Node *parsetree,
 			ExecuteTruncate((TruncateStmt *) parsetree);
 			break;
 
-		case T_CommentStmt:
-			CommentObject((CommentStmt *) parsetree);
-			break;
-
 		case T_SecLabelStmt:
 			ExecSecLabelStmt((SecLabelStmt *) parsetree);
 			break;
@@ -828,6 +824,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,
@@ -1308,6 +1317,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 05fe0c6..46a6fa9 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);
 
-- 
1.9.1

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

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

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

0006-deparse-sprinkle-EventTriggerStashCommand-calls.patchtext/x-diff; charset=us-asciiDownload
>From ea8997de828931e954e1a52821ffc834370859d2 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/27] deparse: sprinkle EventTriggerStashCommand() calls

---
 src/backend/commands/schemacmds.c |   9 ++
 src/backend/tcop/utility.c        | 259 ++++++++++++++++++++++++++------------
 2 files changed, 185 insertions(+), 83 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 03f5514..4548dfd 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, (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 c61367a..277b39c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -864,6 +864,7 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	Oid			objectId;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -882,6 +883,10 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * CreateSchemaCommand calls EventTriggerStashCommand
+				 * internally, for reasons explained there.
+				 */
 				break;
 
 			case T_CreateStmt:
@@ -889,7 +894,6 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					List	   *stmts;
 					ListCell   *l;
-					Oid			relOid;
 
 					/* Run parse analysis ... */
 					stmts = transformCreateStmt((CreateStmt *) parsetree,
@@ -906,9 +910,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,
+													 stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -930,20 +936,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,
+													 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,
@@ -1032,36 +1045,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,
+											 parsetree);
 				}
 				break;
 
@@ -1075,46 +1096,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, parsetree);
 				}
 				break;
 
@@ -1149,50 +1177,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,
+											 parsetree);
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				objectId = CreateExtension((CreateExtensionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				objectId = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, parsetree);
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
+				objectId = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, parsetree);
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FDW, parsetree);
 				break;
 
 			case T_AlterFdwStmt:
-				AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				objectId = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FDW, parsetree);
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FOREIGN_SERVER,
+										 parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
-				AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				objectId = AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FOREIGN_SERVER,
+										 parsetree);
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_USER_MAPPING,
+										 parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
-				AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				objectId = AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_USER_MAPPING,
+										 parsetree);
 				break;
 
 			case T_DropUserMappingStmt:
@@ -1201,105 +1245,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,
+											 parsetree);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				objectId = DefineEnum((CreateEnumStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				objectId = DefineRange((CreateRangeStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				objectId = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				objectId = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_VIEW, parsetree);
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, parsetree);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				objectId = AlterFunction((AlterFunctionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				objectId = DefineRule((RuleStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_RULE, parsetree);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				objectId = DefineSequence((CreateSeqStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				objectId = AlterSequence((AlterSeqStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TABLE, parsetree);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+				objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
 								   queryString, params, completionTag);
+				EventTriggerStashCommand(objectId, 0, OBJECT_MATVIEW, 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, parsetree);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_LANGUAGE, parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				objectId = DefineDomain((CreateDomainStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_DOMAIN, parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				objectId = CreateConversionCommand((CreateConversionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_CONVERSION, parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				objectId = CreateCast((CreateCastStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_CAST, parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				objectId = DefineOpClass((CreateOpClassStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPCLASS, parsetree);
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPFAMILY, parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
-				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				objectId = AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPFAMILY, parsetree);
 				break;
 
 			case T_AlterTSDictionaryStmt:
-				AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				objectId = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TSDICTIONARY, parsetree);
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				objectId = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TSCONFIGURATION, parsetree);
 				break;
 
 			case T_AlterTableMoveAllStmt:
+				/* commands are stashed in AlterTableMoveAll */
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
 				break;
 
@@ -1308,23 +1378,44 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree, NULL);
+				{
+					RenameStmt *stmt = (RenameStmt *) parsetree;
+					int			objsubid;
+
+					objectId = ExecRenameStmt(stmt, &objsubid);
+					EventTriggerStashCommand(objectId, objsubid,
+											 stmt->renameType, 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,
+										 parsetree);
 				break;
 
 			case T_CommentStmt:
-				CommentObject((CommentStmt *) parsetree, NULL);
+				{
+					uint32		objsubid;
+
+					objectId = CommentObject((CommentStmt *) parsetree, &objsubid);
+					EventTriggerStashCommand(objectId, objsubid,
+											 ((CommentStmt *) parsetree)->objtype,
+											 parsetree);
+				}
 				break;
 
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
+				/* commands are stashed in ExecGrantStmt_oids */
 				break;
 
 			case T_DropOwnedStmt:
@@ -1336,11 +1427,13 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
-				CreatePolicy((CreatePolicyStmt *) parsetree);
+				objectId = CreatePolicy((CreatePolicyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_POLICY, parsetree);
 				break;
 
 			case T_AlterPolicyStmt:		/* ALTER POLICY */
-				AlterPolicy((AlterPolicyStmt *) parsetree);
+				objectId = AlterPolicy((AlterPolicyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_POLICY, parsetree);
 				break;
 
 			default:
-- 
1.9.1

0007-deparse-Support-CREATE-TYPE-AS.patchtext/x-diff; charset=us-asciiDownload
>From 54eae50ba3d5ed1dafd558af294ebbfdbd67776d 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/27] deparse: Support CREATE TYPE AS

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 65b7e7d..16aa0d5 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -580,6 +580,269 @@ 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(NULL, 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
@@ -677,7 +940,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 */
-- 
1.9.1

0008-deparse-Support-CREATE-TYPE-AS-ENUM.patchtext/x-diff; charset=us-asciiDownload
>From 70d1d1d3305b40ba442195b2419e26144da5892f 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/27] deparse: Support CREATE TYPE AS ENUM

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 16aa0d5..5ef1fd3 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -843,6 +843,37 @@ 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(NULL, 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
@@ -944,7 +975,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 */
-- 
1.9.1

0009-deparse-Support-CREATE-SCHEMA-TABLE-SEQUENCE-INDEX-T.patchtext/x-diff; charset=us-asciiDownload
>From 72ce83295f36cd1f71a10b85760e7fbf1aa7f16b 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 09/27] 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 | 954 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  | 373 +++++++++++++--
 src/include/commands/sequence.h    |   1 +
 src/include/utils/ruleutils.h      |  14 +-
 6 files changed, 1319 insertions(+), 60 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 3b89dd0..0c5d5d7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1492,6 +1492,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 0281c6a..3d7e1b3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -7940,7 +7940,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 5ef1fd3..f9658bb 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -580,6 +580,221 @@ 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(NULL, "INSERT"));
+	if (node->events & TRIGGER_TYPE_DELETE)
+		events = lappend(events, new_string_object(NULL, "DELETE"));
+	if (node->events & TRIGGER_TYPE_TRUNCATE)
+		events = lappend(events, new_string_object(NULL, "TRUNCATE"));
+	if (node->events & TRIGGER_TYPE_UPDATE)
+	{
+		if (node->columns == NIL)
+		{
+			events = lappend(events, new_string_object(NULL, "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(NULL, colname));
+			}
+
+			append_array_object(update, "columns", cols);
+
+			events = lappend(events,
+							 new_object_object(NULL, 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(NULL, "DEFERRABLE"));
+	if (node->initdeferred)
+		list = lappend(list,
+					   new_string_object(NULL, "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(NULL, 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
@@ -808,6 +1023,333 @@ 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(NULL, 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 "
+			"%{on_commit}s WITH (%{with:, }s) %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) %{inherits}s "
+			"%{on_commit}s WITH (%{with:, }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(NULL, 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(NULL, tmp));
+	foreach(cell, node->options)
+	{
+		DefElem	*opt = (DefElem *) lfirst(cell);
+		char   *fmt;
+		char   *value;
+
+		/* already handled above */
+		if (strcmp(opt->defname, "oids") == 0)
+			continue;
+
+		if (opt->defnamespace)
+			fmt = psprintf("%s.%s=%%{value}s", opt->defnamespace, opt->defname);
+		else
+			fmt = psprintf("%s=%%{value}s", opt->defname);
+		value = opt->arg ? defGetString(opt) :
+			defGetBoolean(opt) ? "TRUE" : "FALSE";
+		tmp = new_objtree_VA(fmt, 2,
+							 "option", ObjTypeString, opt->defname,
+							 "value", ObjTypeString, value);
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+	append_array_object(createStmt, "with", list);
+
+	relation_close(relation, AccessShareLock);
+
+	return createStmt;
+}
+
+/*
  * deparse_CompositeTypeStmt
  *		Deparse a CompositeTypeStmt (CREATE TYPE AS)
  *
@@ -874,6 +1416,406 @@ 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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 %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   5,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(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.
  *
  * This function contains a large switch that mirrors that in
@@ -895,11 +1837,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:
@@ -922,7 +1864,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_IndexStmt:
-			command = NULL;
+			command = deparse_IndexStmt(objectId, parsetree);
 			break;
 
 		case T_CreateExtensionStmt:
@@ -1003,11 +1945,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:
@@ -1019,7 +1961,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 18d9b63..56e74b7 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);
 }
 
 /*
@@ -9323,3 +9574,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 914d155..984631a 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -70,6 +70,7 @@ extern Datum setval3_oid(PG_FUNCTION_ARGS);
 extern Datum lastval(PG_FUNCTION_ARGS);
 
 extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS);
+extern Form_pg_sequence get_sequence_values(Oid sequenceId);
 
 extern Oid	DefineSequence(CreateSeqStmt *stmt);
 extern Oid	AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 520b066..0503816 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);
@@ -31,4 +40,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 */
-- 
1.9.1

0010-deparse-Support-CREATE-TYPE-AS-RANGE.patchtext/x-diff; charset=us-asciiDownload
>From 5697cf42e542c875a9e866d9c94ce566e0113a4a 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 10/27] 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 f9658bb..dfd7ccf 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"
@@ -1417,6 +1419,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(NULL, tmp));
+
+	/* SUBTYPE_OPCLASS */
+	if (OidIsValid(rangeForm->rngsubopc))
+	{
+		tmp = new_objtree_for_qualname_id(OperatorClassRelationId,
+										  rangeForm->rngsubopc);
+		tmp = new_objtree_VA("SUBTYPE_OPCLASS = %{opclass}D",
+							 2,
+							 "clause", ObjTypeString, "opclass",
+							 "opclass", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	/* COLLATION */
+	if (OidIsValid(rangeForm->rngcollation))
+	{
+		tmp = new_objtree_for_qualname_id(CollationRelationId,
+										  rangeForm->rngcollation);
+		tmp = new_objtree_VA("COLLATION = %{collation}D",
+							 2,
+							 "clause", ObjTypeString, "collation",
+							 "collation", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	/* CANONICAL */
+	if (OidIsValid(rangeForm->rngcanonical))
+	{
+		tmp = new_objtree_for_qualname_id(ProcedureRelationId,
+										  rangeForm->rngcanonical);
+		tmp = new_objtree_VA("CANONICAL = %{canonical}D",
+							 2,
+							 "clause", ObjTypeString, "canonical",
+							 "canonical", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	/* SUBTYPE_DIFF */
+	if (OidIsValid(rangeForm->rngsubdiff))
+	{
+		tmp = new_objtree_for_qualname_id(ProcedureRelationId,
+										  rangeForm->rngsubdiff);
+		tmp = new_objtree_VA("SUBTYPE_DIFF = %{subtype_diff}D",
+							 2,
+							 "clause", ObjTypeString, "subtype_diff",
+							 "subtype_diff", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	append_array_object(range, "definition", definition);
+
+	systable_endscan(scan);
+	heap_close(pg_range, RowExclusiveLock);
+
+	return range;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -1921,7 +2021,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:
-- 
1.9.1

0011-deparse-Support-CREATE-EXTENSION.patchtext/x-diff; charset=us-asciiDownload
>From cb4342792be2b3c6617934881d12bd26733546f6 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 11/27] 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 dfd7ccf..d60c37e 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"
@@ -601,6 +602,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(NULL, tmp));
+		}
+		else if (strcmp(opt->defname, "old_version") == 0)
+		{
+			tmp = new_objtree_VA("FROM %{version}L", 2,
+								 "type", ObjTypeString, "from",
+								 "version", ObjTypeString, defGetString(opt));
+			list = lappend(list, new_object_object(NULL, tmp));
+		}
+		else
+			elog(ERROR, "unsupported option %s", opt->defname);
+	}
+
+	tmp = new_objtree_VA("SCHEMA %{schema}I",
+						 2, "type", ObjTypeString, "schema",
+						 "schema", ObjTypeString,
+						 get_namespace_name(extForm->extnamespace));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	append_array_object(extStmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	return extStmt;
+}
+
+/*
  * deparse_CreateTrigStmt
  *		Deparse a CreateTrigStmt (CREATE TRIGGER)
  *
@@ -1968,7 +2044,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateExtensionStmt:
-			command = NULL;
+			command = deparse_CreateExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionStmt:
-- 
1.9.1

0012-deparse-Support-CREATE-RULE.patchtext/x-diff; charset=us-asciiDownload
>From d1fe0d317e96e68f071fb90ae9cc7070d1a00aea 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 12/27] 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 d60c37e..3580d84 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"
@@ -1952,6 +1954,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(NULL, action));
+	}
+	append_array_object(ruleStmt, "actions", list);
+
+	systable_endscan(scan);
+	heap_close(pg_rewrite, AccessShareLock);
+
+	return ruleStmt;
+}
+
+
+
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -2117,7 +2208,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 56e74b7..6840c7a 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 0503816..46b4d98 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);
-- 
1.9.1

0013-deparse-Support-ALTER-TYPE-ADD-VALUE-enums.patchtext/x-diff; charset=us-asciiDownload
>From 067fc2e8cc44cdcf5cba79163c93d7431cd02085 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 13/27] 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 3580d84..1e3635c 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2082,6 +2082,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.
  *
@@ -2192,7 +2225,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterEnumStmt:
-			command = NULL;
+			command = deparse_AlterEnumStmt(objectId, parsetree);
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-- 
1.9.1

0014-deparse-Support-for-ALTER-OBJECT-RENAME.patchtext/x-diff; charset=us-asciiDownload
>From 58b9e1aa2792672615ece8a467414ed06013eaf3 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 14/27] deparse: Support for ALTER <OBJECT> RENAME

It supports everything but functions, aggregates, operator classes and
operator families.
---
 src/backend/tcop/deparse_utility.c | 277 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 276 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 1e3635c..7d7dbe7 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1595,6 +1595,281 @@ 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_COLUMN:
+			return "COLUMN";
+		case OBJECT_DOMAIN:
+			return "DOMAIN";
+		case OBJECT_COLLATION:
+			return "COLLATION";
+		case OBJECT_CONVERSION:
+			return "CONVERSION";
+		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_OPERATOR:
+			return "OPERATOR";
+		case OBJECT_OPCLASS:
+			return "OPERATOR CLASS";
+		case OBJECT_OPFAMILY:
+			return "OPERATOR FAMILY";
+		case OBJECT_SCHEMA:
+			return "SCHEMA";
+		case OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case OBJECT_TABLE:
+			return "TABLE";
+		case OBJECT_TSCONFIGURATION:
+			return "TEXT SEARCH CONFIGURATION";
+		case OBJECT_TSDICTIONARY:
+			return "TEXT SEARCH DICTIONARY";
+		case OBJECT_TSPARSER:
+			return "TEXT SEARCH PARSER";
+		case OBJECT_TSTEMPLATE:
+			return "TEXT SEARCH TEMPLATE";
+		case OBJECT_TYPE:
+			return "TYPE";
+		case OBJECT_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;
+	const char *subthing;
+
+	/*
+	 * FIXME --- this code is missing support for inheritance behavioral flags,
+	 * i.e. the "*" and ONLY elements.
+	 */
+
+	/*
+	 * In a ALTER .. RENAME command, we don't have the original name of the
+	 * object in system catalogs: since we inspect them after the command has
+	 * executed, the old name is already gone.  Therefore, we extract it from
+	 * the parse node.  Note we still extract the schema name from the catalog
+	 * (it might not be present in the parse node); it cannot possibly have
+	 * changed anyway.
+	 *
+	 * XXX what if there's another event trigger running concurrently that
+	 * renames the schema or moves the object to another schema?  Seems
+	 * pretty far-fetched, but possible nonetheless.
+	 */
+	switch (node->renameType)
+	{
+		case OBJECT_TABLE:
+		case OBJECT_SEQUENCE:
+		case OBJECT_VIEW:
+		case OBJECT_MATVIEW:
+		case OBJECT_INDEX:
+		case OBJECT_FOREIGN_TABLE:
+			fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME TO %%{newname}I",
+							  stringify_objtype(node->renameType));
+			relation = relation_open(objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_object_object(renameStmt, "identity",
+								 new_objtree_for_qualname(schemaId,
+														  node->relation->relname));
+			append_string_object(renameStmt, "if_exists",
+								 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+			break;
+
+		case OBJECT_COLUMN:
+		case OBJECT_ATTRIBUTE:
+			relation = relation_open(objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			if (node->renameType == OBJECT_COLUMN)
+				subthing = "COLUMN";
+			else
+				subthing = "ATTRIBUTE";
+
+			fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME %s %%{colname}I TO %%{newname}I",
+							  stringify_objtype(node->relationType),
+							  subthing);
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_object_object(renameStmt, "identity",
+								 new_objtree_for_qualname(schemaId,
+														  node->relation->relname));
+			append_string_object(renameStmt, "colname", node->subname);
+			append_string_object(renameStmt, "if_exists",
+								 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+			break;
+
+		case OBJECT_SCHEMA:
+		case OBJECT_FDW:
+		case OBJECT_LANGUAGE:
+		case OBJECT_FOREIGN_SERVER:
+			fmtstr = psprintf("ALTER %s %%{identity}I RENAME TO %%{newname}I",
+							  stringify_objtype(node->renameType));
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_string_object(renameStmt, "identity",
+								 node->subname);
+			break;
+
+		case OBJECT_COLLATION:
+		case OBJECT_CONVERSION:
+		case OBJECT_DOMAIN:
+		case OBJECT_TSDICTIONARY:
+		case OBJECT_TSPARSER:
+		case OBJECT_TSTEMPLATE:
+		case OBJECT_TSCONFIGURATION:
+		case OBJECT_TYPE:
+			{
+				ObjTree    *ident;
+				HeapTuple	objTup;
+				Oid			catalogId;
+				Relation	catalog;
+				bool		isnull;
+				AttrNumber	nspnum;
+
+				catalogId = get_objtype_catalog_oid(node->renameType);
+				catalog = heap_open(catalogId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, objectId);
+				nspnum = get_object_attnum_namespace(catalogId);
+
+				schemaId = DatumGetObjectId(heap_getattr(objTup,
+														 nspnum,
+														 RelationGetDescr(catalog),
+														 &isnull));
+
+				fmtstr = psprintf("ALTER %s %%{identity}D RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				ident = new_objtree_for_qualname(schemaId,
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+				relation_close(catalog, AccessShareLock);
+
+			}
+			break;
+
+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			elog(ERROR, "renaming of functions and aggregates is not supported yet");
+
+		case OBJECT_CONSTRAINT:
+			{
+				HeapTuple		conTup;
+				Form_pg_constraint	constrForm;
+				ObjTree		   *ident;
+
+				conTup = SearchSysCache1(CONSTROID, objectId);
+				constrForm = (Form_pg_constraint) GETSTRUCT(conTup);
+				fmtstr = psprintf("ALTER %s %%{identity}D RENAME CONSTRAINT %%{conname}I TO %%{newname}I",
+								  stringify_objtype(node->relationType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+
+				switch (node->relationType)
+				{
+					case OBJECT_DOMAIN:
+						ident = new_objtree_for_qualname_id(TypeRelationId,
+															constrForm->contypid);
+						break;
+					case OBJECT_TABLE:
+						ident = new_objtree_for_qualname_id(RelationRelationId,
+															constrForm->conrelid);
+						break;
+					default:
+						elog(ERROR, "invalid relation type %d", node->relationType);
+				}
+
+				append_string_object(renameStmt, "conname", node->subname);
+				append_object_object(renameStmt, "identity", ident);
+				ReleaseSysCache(conTup);
+			}
+			break;
+
+		case OBJECT_OPCLASS:
+		case OBJECT_OPFAMILY:
+			ereport(ERROR,
+					(errmsg("renaming of operator classes and families is not supported")));
+			break;
+
+		case OBJECT_RULE:
+			{
+				HeapTuple	rewrTup;
+				Form_pg_rewrite rewrForm;
+				Relation	pg_rewrite;
+
+				pg_rewrite = relation_open(RewriteRelationId, AccessShareLock);
+				rewrTup = get_catalog_object_by_oid(pg_rewrite, objectId);
+				rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup);
+
+				renameStmt = new_objtree_VA("ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "rulename", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 rewrForm->ev_class));
+				relation_close(pg_rewrite, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_TRIGGER:
+			{
+				HeapTuple	trigTup;
+				Form_pg_trigger trigForm;
+				Relation	pg_trigger;
+
+				pg_trigger = relation_open(TriggerRelationId, AccessShareLock);
+				trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+				trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+				renameStmt = new_objtree_VA("ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "triggername", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 trigForm->tgrelid));
+				relation_close(pg_trigger, AccessShareLock);
+			}
+			break;
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	append_string_object(renameStmt, "newname", node->newname);
+
+	return renameStmt;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -2306,7 +2581,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RenameStmt:
-			command = NULL;
+			command = deparse_RenameStmt(objectId, parsetree);
 			break;
 
 		case T_AlterObjectSchemaStmt:
-- 
1.9.1

0015-deparse-Support-CREATE-DOMAIN.patchtext/x-diff; charset=us-asciiDownload
>From 35a93630afb29b7af78ab21bd0f3d7ea58323981 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 15/27] deparse: Support CREATE DOMAIN

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 7d7dbe7..add1bbe 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1595,6 +1595,60 @@ 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}D %{not_null}s %{constraints}s %{collation}s",
+								  0);
+
+	append_object_object(createDomain,
+						 "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	append_object_object(createDomain,
+						 "type",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 typForm->typbasetype));
+
+	if (typForm->typnotnull)
+		append_string_object(createDomain, "not_null", "NOT NULL");
+	else
+		append_string_object(createDomain, "not_null", "");
+
+	constraints = obtainConstraints(NIL, InvalidOid, objectId);
+	tmp = new_objtree_VA("%{elements: }s", 0);
+	if (constraints == NIL)
+		append_bool_object(tmp, "present", false);
+	else
+		append_array_object(tmp, "elements", constraints);
+	append_object_object(createDomain, "constraints", tmp);
+
+	tmp = new_objtree_VA("COLLATE %{collation}D", 0);
+	if (OidIsValid(typForm->typcollation))
+		append_object_object(tmp, "collation",
+							 new_objtree_for_qualname_id(CollationRelationId,
+														 typForm->typcollation));
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createDomain, "collation", tmp);
+
+	ReleaseSysCache(typTup);
+
+	return createDomain;
+}
+
 /*
  * Return the given object type as a string.
  */
@@ -2544,7 +2598,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateDomainStmt:
-			command = NULL;
+			command = deparse_CreateDomain(objectId, parsetree);
 			break;
 
 		case T_CreateConversionStmt:
-- 
1.9.1

0016-deparse-Support-CREATE-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From e57865621aebaad9882be3d2dde263fa5a1046a4 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 16/27] deparse: Support CREATE FUNCTION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index add1bbe..bf7e2f8 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"
@@ -1650,6 +1651,318 @@ 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)
+		{
+			pfree(probin);
+			probin = NULL;
+		}
+	}
+
+	if (probin == NULL)
+		definition = "%{definition}L";
+	else
+		definition = "%{objfile}L, %{symbol}L";
+
+	fmt = psprintf("CREATE %%{or_replace}s FUNCTION %%{signature}s "
+				   "RETURNS %%{return_type}s LANGUAGE %%{language}I "
+				   "%%{window}s %%{volatility}s %%{leakproof}s "
+				   "%%{strict}s %%{security_definer}s %%{cost}s %%{rows}s "
+				   "%%{set_options: }s "
+				   "AS %s", definition);
+
+	createFunc = new_objtree_VA(fmt, 1,
+								"or_replace", ObjTypeString,
+								node->replace ? "OR REPLACE" : "");
+
+	sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0);
+
+	/*
+	 * To construct the arguments array, extract the type OIDs from the
+	 * function's pg_proc entry.  If pronargs equals the parameter list length,
+	 * there are no OUT parameters and thus we can extract the type OID from
+	 * proargtypes; otherwise we need to decode proallargtypes, which is
+	 * a bit more involved.
+	 */
+	typarray = palloc(list_length(node->parameters) * sizeof(Oid));
+	if (list_length(node->parameters) > procForm->pronargs)
+	{
+		bool	isnull;
+		Datum	alltypes;
+		Datum  *values;
+		bool   *nulls;
+		int		nelems;
+
+		alltypes = SysCacheGetAttr(PROCOID, procTup,
+								   Anum_pg_proc_proallargtypes, &isnull);
+		if (isnull)
+			elog(ERROR, "NULL proallargtypes, but more parameters than args");
+		deconstruct_array(DatumGetArrayTypeP(alltypes),
+						  OIDOID, 4, 't', 'i',
+						  &values, &nulls, &nelems);
+		if (nelems != list_length(node->parameters))
+			elog(ERROR, "mismatched proallargatypes");
+		for (i = 0; i < list_length(node->parameters); i++)
+			typarray[i] = values[i];
+	}
+	else
+	{
+		for (i = 0; i < list_length(node->parameters); i++)
+			 typarray[i] = procForm->proargtypes.values[i];
+	}
+
+	/*
+	 * If there are any default expressions, we read the deparsed expression as
+	 * a list so that we can attach them to each argument.
+	 */
+	tmpdatum = SysCacheGetAttr(PROCOID, procTup,
+							   Anum_pg_proc_proargdefaults, &isnull);
+	if (!isnull)
+	{
+		defaults = FunctionGetDefaults(DatumGetTextP(tmpdatum));
+		curdef = list_head(defaults);
+	}
+	else
+	{
+		defaults = NIL;
+		curdef = NULL;
+	}
+
+	/*
+	 * Now iterate over each parameter in the parsetree to create the
+	 * parameters array.
+	 */
+	params = NIL;
+	typnum = 0;
+	foreach(cell, node->parameters)
+	{
+		FunctionParameter *param = (FunctionParameter *) lfirst(cell);
+		ObjTree	   *tmp2;
+		ObjTree	   *tmp3;
+
+		/*
+		 * A PARAM_TABLE parameter indicates end of input arguments; the
+		 * following parameters are part of the return type.  We ignore them
+		 * here, but keep track of the current position in the list so that
+		 * we can easily produce the return type below.
+		 */
+		if (param->mode == FUNC_PARAM_TABLE)
+		{
+			table_params = cell;
+			break;
+		}
+
+		/*
+		 * Note that %{name}s is a string here, not an identifier; the reason
+		 * for this is that an absent parameter name must produce an empty
+		 * string, not "", which is what would happen if we were to use
+		 * %{name}I here.  So we add another level of indirection to allow us
+		 * to inject a "present" parameter.
+		 */
+		tmp2 = new_objtree_VA("%{mode}s %{name}s %{type}T %{default}s", 0);
+		append_string_object(tmp2, "mode",
+							 param->mode == FUNC_PARAM_IN ? "IN" :
+							 param->mode == FUNC_PARAM_OUT ? "OUT" :
+							 param->mode == FUNC_PARAM_INOUT ? "INOUT" :
+							 param->mode == FUNC_PARAM_VARIADIC ? "VARIADIC" :
+							 "INVALID MODE");
+
+		/* optional wholesale suppression of "name" occurs here */
+		append_object_object(tmp2, "name",
+							 new_objtree_VA("%{name}I", 2,
+											"name", ObjTypeString,
+											param->name ? param->name : "NULL",
+											"present", ObjTypeBool,
+											param->name ? true : false));
+
+		tmp3 = new_objtree_VA("DEFAULT %{value}s", 0);
+		if (PointerIsValid(param->defexpr))
+		{
+			char *expr;
+
+			if (curdef == NULL)
+				elog(ERROR, "proargdefaults list too short");
+			expr = lfirst(curdef);
+
+			append_string_object(tmp3, "value", expr);
+			curdef = lnext(curdef);
+		}
+		else
+			append_bool_object(tmp3, "present", false);
+		append_object_object(tmp2, "default", tmp3);
+
+		append_object_object(tmp2, "type",
+							 new_objtree_for_type(typarray[typnum++], -1));
+
+		params = lappend(params,
+						 new_object_object(NULL, tmp2));
+	}
+	append_array_object(sign, "arguments", params);
+	append_object_object(sign, "identity",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 objectId));
+	append_object_object(createFunc, "signature", sign);
+
+	/*
+	 * A return type can adopt one of two forms: either a [SETOF] some_type, or
+	 * a TABLE(list-of-types).  We can tell the second form because we saw a
+	 * table param above while scanning the argument list.
+	 */
+	if (table_params == NULL)
+	{
+		tmp = new_objtree_VA("%{setof}s %{rettype}T", 0);
+		append_string_object(tmp, "setof",
+							 procForm->proretset ? "SETOF" : "");
+		append_object_object(tmp, "rettype",
+							 new_objtree_for_type(procForm->prorettype, -1));
+		append_string_object(tmp, "return_form", "plain");
+	}
+	else
+	{
+		List	   *rettypes = NIL;
+		ObjTree	   *tmp2;
+
+		tmp = new_objtree_VA("TABLE (%{rettypes:, }s)", 0);
+		for (; table_params != NULL; table_params = lnext(table_params))
+		{
+			FunctionParameter *param = lfirst(table_params);
+
+			tmp2 = new_objtree_VA("%{name}I %{type}T", 0);
+			append_string_object(tmp2, "name", param->name);
+			append_object_object(tmp2, "type",
+								 new_objtree_for_type(typarray[typnum++], -1));
+			rettypes = lappend(rettypes,
+							   new_object_object(NULL, tmp2));
+		}
+
+		append_array_object(tmp, "rettypes", rettypes);
+		append_string_object(tmp, "return_form", "table");
+	}
+
+	append_object_object(createFunc, "return_type", tmp);
+
+	append_string_object(createFunc, "language",
+						 NameStr(langForm->lanname));
+
+	append_string_object(createFunc, "window",
+						 procForm->proiswindow ? "WINDOW" : "");
+	append_string_object(createFunc, "volatility",
+						 procForm->provolatile == PROVOLATILE_VOLATILE ?
+						 "VOLATILE" :
+						 procForm->provolatile == PROVOLATILE_STABLE ?
+						 "STABLE" :
+						 procForm->provolatile == PROVOLATILE_IMMUTABLE ?
+						 "IMMUTABLE" : "INVALID VOLATILITY");
+
+	append_string_object(createFunc, "leakproof",
+						 procForm->proleakproof ? "LEAKPROOF" : "");
+	append_string_object(createFunc, "strict",
+						 procForm->proisstrict ?
+						 "RETURNS NULL ON NULL INPUT" :
+						 "CALLED ON NULL INPUT");
+
+	append_string_object(createFunc, "security_definer",
+						 procForm->prosecdef ?
+						 "SECURITY DEFINER" : "SECURITY INVOKER");
+
+	append_object_object(createFunc, "cost",
+						 new_objtree_VA("COST %{cost}s", 1,
+										"cost", ObjTypeString,
+										psprintf("%f", procForm->procost)));
+
+	tmp = new_objtree_VA("ROWS %{rows}s", 0);
+	if (procForm->prorows == 0)
+		append_bool_object(tmp, "present", false);
+	else
+		append_string_object(tmp, "rows",
+							 psprintf("%f", procForm->prorows));
+	append_object_object(createFunc, "rows", tmp);
+
+	append_array_object(createFunc, "set_options", NIL);
+
+	if (probin == NULL)
+	{
+		append_string_object(createFunc, "definition",
+							 source);
+	}
+	else
+	{
+		append_string_object(createFunc, "objfile", probin);
+		append_string_object(createFunc, "symbol", source);
+	}
+
+	ReleaseSysCache(langTup);
+	ReleaseSysCache(procTup);
+
+	return createFunc;
+}
+
+/*
  * Return the given object type as a string.
  */
 static const char *
@@ -2562,7 +2875,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 6840c7a..771a83c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9663,3 +9663,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 46b4d98..5f1f1e8 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -41,6 +41,7 @@ extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
 extern List *select_rtable_names_for_explain(List *rtable,
 								Bitmapset *rels_used);
 extern char *generate_collation_name(Oid collid);
+extern List *FunctionGetDefaults(text *proargdefaults);
 
 extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
 						 List *dpcontext);
-- 
1.9.1

0017-deparse-core-have-ALTER-TABLE-return-OIDs-and-col-of.patchtext/x-diff; charset=us-asciiDownload
>From 339f68fbcea121a47b70b7fc43952f0838fca1fc 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 17/27] deparse/core: have ALTER TABLE return OIDs and col# of
 affected objs

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

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c0eade0..0bdc000 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -96,7 +96,7 @@ static Oid AddNewRelationType(const char *typeName,
 				   Oid new_row_type,
 				   Oid new_array_type);
 static void RelationRemoveInheritance(Oid relid);
-static void StoreRelCheck(Relation rel, char *ccname, Node *expr,
+static Oid StoreRelCheck(Relation rel, char *ccname, Node *expr,
 			  bool is_validated, bool is_local, int inhcount,
 			  bool is_no_inherit, bool is_internal);
 static void StoreConstraints(Relation rel, List *cooked_constraints,
@@ -1843,7 +1843,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)
 {
@@ -1948,6 +1948,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	 */
 	InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
 								  RelationGetRelid(rel), attnum, is_internal);
+
+	return attrdefOid;
 }
 
 /*
@@ -1955,8 +1957,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)
@@ -1966,6 +1970,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
+	Oid			constrOid;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2017,7 +2022,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 */
@@ -2048,11 +2053,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
@@ -2064,7 +2073,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 */
 
 	/*
@@ -2081,12 +2090,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:
@@ -2174,6 +2185,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,
@@ -2194,10 +2206,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;
@@ -2217,6 +2230,7 @@ AddRelationNewConstraints(Relation rel,
 	{
 		Constraint *cdef = (Constraint *) lfirst(cell);
 		char	   *ccname;
+		Oid			constrOid;
 
 		if (cdef->contype != CONSTR_CHECK)
 			continue;
@@ -2319,13 +2333,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 ee10594..b424f73 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1096,7 +1096,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
@@ -1113,7 +1114,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,
@@ -1301,6 +1302,8 @@ index_constraint_create(Relation heapRelation,
 		heap_freetuple(indexTuple);
 		heap_close(pg_index, RowExclusiveLock);
 	}
+
+	return conOid;
 }
 
 /*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 041f5ad..f04de42 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -40,6 +40,8 @@
  * Subsidiary records (such as triggers or indexes to implement the
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
+ *
+ * The new constraint's OID is returned.
  */
 Oid
 CreateConstraintEntry(const char *constraintName,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3d7e1b3..1a44c91 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -46,6 +46,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -280,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);
@@ -322,26 +323,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);
@@ -349,20 +350,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,
@@ -373,9 +374,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);
@@ -388,7 +389,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);
@@ -621,6 +622,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 +1734,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;
@@ -3390,78 +3393,91 @@ static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
+	AttrNumber colno = InvalidAttrNumber;
+	Oid			newoid = InvalidOid;
+
 	switch (cmd->subtype)
 	{
 		case AT_AddColumn:		/* ADD COLUMN */
 		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
 										 * VIEW */
-			ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
-							false, false, false, lockmode);
+			colno = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+									false, false, false, lockmode);
 			break;
 		case AT_AddColumnRecurse:
-			ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
-							false, true, false, lockmode);
+			colno = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+									false, true, false, lockmode);
 			break;
 		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
-			ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
-			ATExecDropNotNull(rel, cmd->name, lockmode);
+			colno = ATExecDropNotNull(rel, cmd->name, lockmode);
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
-			ATExecSetNotNull(tab, rel, cmd->name, lockmode);
+			colno = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
 			break;
 		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
-			ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
-			ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
+			colno = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
 			break;
 		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
-			ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
+			colno = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
 			break;
 		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
-			ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
 			ATExecDropColumn(wqueue, rel, cmd->name,
-					 cmd->behavior, false, false, cmd->missing_ok, lockmode);
+							 cmd->behavior, false, false,
+							 cmd->missing_ok, lockmode);
 			break;
 		case AT_DropColumnRecurse:		/* DROP COLUMN with recursion */
 			ATExecDropColumn(wqueue, rel, cmd->name,
-					  cmd->behavior, true, false, cmd->missing_ok, lockmode);
+							 cmd->behavior, true, false,
+							 cmd->missing_ok, lockmode);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
-			ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false, lockmode);
+			newoid = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
+									lockmode);
 			break;
 		case AT_ReAddIndex:		/* ADD INDEX */
-			ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, lockmode);
+			newoid = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
+									lockmode);
 			break;
 		case AT_AddConstraint:	/* ADD CONSTRAINT */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								false, false, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									false, false, lockmode);
 			break;
 		case AT_AddConstraintRecurse:	/* ADD CONSTRAINT with recursion */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								true, false, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									true, false, lockmode);
 			break;
 		case AT_ReAddConstraint:		/* Re-add pre-existing check
 										 * constraint */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								false, true, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									false, true, lockmode);
 			break;
 		case AT_AddIndexConstraint:		/* ADD CONSTRAINT USING INDEX */
-			ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, lockmode);
+			newoid = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
+											  lockmode);
 			break;
 		case AT_AlterConstraint:		/* ALTER CONSTRAINT */
-			ATExecAlterConstraint(rel, cmd, false, false, lockmode);
+			newoid = ATExecAlterConstraint(rel, cmd, false, false, lockmode);
 			break;
 		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
-			ATExecValidateConstraint(rel, cmd->name, false, false, lockmode);
+			newoid = ATExecValidateConstraint(rel, cmd->name, false, false,
+											  lockmode);
 			break;
 		case AT_ValidateConstraintRecurse:		/* VALIDATE CONSTRAINT with
 												 * recursion */
-			ATExecValidateConstraint(rel, cmd->name, true, false, lockmode);
+			newoid = ATExecValidateConstraint(rel, cmd->name, true, false,
+											  lockmode);
 			break;
 		case AT_DropConstraint:	/* DROP CONSTRAINT */
 			ATExecDropConstraint(rel, cmd->name, cmd->behavior,
@@ -3474,10 +3490,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),
@@ -3485,7 +3503,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);
@@ -3586,7 +3604,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			ATExecDropOf(rel, lockmode);
 			break;
 		case AT_ReplicaIdentity:
-			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def,
+								  lockmode);
 			break;
 		case AT_EnableRowSecurity:
 			ATExecEnableRowSecurity(rel);
@@ -4581,7 +4600,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)
@@ -4666,7 +4685,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					  colDef->colname, RelationGetRelationName(rel))));
 
 			heap_close(attrdesc, RowExclusiveLock);
-			return;
+			return InvalidAttrNumber;
 		}
 	}
 
@@ -4912,6 +4931,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 		heap_close(childrel, NoLock);
 	}
+
+	return newattnum;
 }
 
 /*
@@ -5026,7 +5047,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;
@@ -5111,18 +5132,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)
 {
@@ -5166,18 +5191,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)
 {
@@ -5228,6 +5257,8 @@ ATExecColumnDefault(Relation rel, const char *colName,
 		AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
 								  false, true, false);
 	}
+
+	return attnum;
 }
 
 /*
@@ -5257,13 +5288,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);
@@ -5298,7 +5330,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\"",
@@ -5317,9 +5350,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)
 {
@@ -5327,6 +5362,7 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	HeapTuple	tuple,
 				newtuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 	Datum		datum,
 				newOptions;
 	bool		isnull;
@@ -5345,7 +5381,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\"",
@@ -5384,12 +5421,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;
@@ -5397,6 +5436,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);
@@ -5429,7 +5469,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\"",
@@ -5459,6 +5500,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return attnum;
 }
 
 
@@ -5684,7 +5727,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
  * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
  * us schedule creation of the index at the appropriate time during ALTER.
  */
-static void
+static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
@@ -5726,12 +5769,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)
 {
@@ -5741,6 +5786,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	IndexInfo  *indexInfo;
 	char	   *constraintName;
 	char		constraintType;
+	Oid			conOid;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(OidIsValid(index_oid));
@@ -5785,30 +5831,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));
 
 	/*
@@ -5819,9 +5869,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:
@@ -5852,17 +5903,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.
  *
@@ -5881,7 +5937,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)
@@ -5890,6 +5946,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)
@@ -5932,6 +5989,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 */
@@ -5947,7 +6011,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.
@@ -5955,7 +6019,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
@@ -5993,16 +6057,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)
 {
@@ -6401,6 +6468,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Close pk table, but keep lock until we've committed.
 	 */
 	heap_close(pkrel, NoLock);
+
+	return constrOid;
 }
 
 /*
@@ -6413,7 +6482,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)
 {
@@ -6424,6 +6493,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;
@@ -6483,6 +6553,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);
 
@@ -6530,6 +6602,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 /*
@@ -6540,7 +6614,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)
 {
@@ -6550,6 +6624,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);
 
@@ -6591,9 +6666,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;
 
 			/*
@@ -6608,7 +6684,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 conid);
+										 constrOid);
 			heap_close(refrel, NoLock);
 
 			/*
@@ -6689,6 +6765,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 
@@ -7775,7 +7853,7 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
 	}
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
@@ -8152,9 +8230,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 	/* Cleanup */
 	heap_freetuple(heapTup);
+
+	return attnum;
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnGenericOptions(Relation rel,
 								const char *colName,
 								List *options,
@@ -8173,9 +8253,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);
@@ -8202,7 +8283,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)));
@@ -8251,6 +8333,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	heap_close(attrel, RowExclusiveLock);
 
 	heap_freetuple(newtuple);
+
+	return attnum;
 }
 
 /*
@@ -8892,7 +8976,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;
@@ -8910,6 +8994,8 @@ ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 
 	/* And do the work */
 	mark_index_clustered(rel, indexOid, false);
+
+	return indexOid;
 }
 
 /*
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index e690e27..075c238 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -27,6 +27,7 @@ typedef struct RawColumnDefault
 typedef struct CookedConstraint
 {
 	ConstrType	contype;		/* CONSTR_DEFAULT or CONSTR_CHECK */
+	Oid			conoid;			/* OID of the new element */
 	char	   *name;			/* name, or NULL if none */
 	AttrNumber	attnum;			/* which attr (only for DEFAULT) */
 	Node	   *expr;			/* transformed default or check expr */
@@ -99,7 +100,7 @@ extern List *AddRelationNewConstraints(Relation rel,
 						  bool is_local,
 						  bool is_internal);
 
-extern void StoreAttrDefault(Relation rel, AttrNumber attnum,
+extern Oid StoreAttrDefault(Relation rel, AttrNumber attnum,
 				 Node *expr, bool is_internal);
 
 extern Node *cookDefault(ParseState *pstate,
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 006b180..acde583 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -62,7 +62,7 @@ extern Oid index_create(Relation heapRelation,
 			 bool concurrent,
 			 bool is_internal);
 
-extern void index_constraint_create(Relation heapRelation,
+extern Oid index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
 						const char *constraintName,
-- 
1.9.1

0018-deparse-Support-ALTER-TABLE.patchtext/x-diff; charset=us-asciiDownload
>From 90d1abef584b90c0826d55368bf4a8daffc8727e 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 18/27] deparse: Support ALTER TABLE

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

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 2dd7fff..136c86a 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -51,6 +51,7 @@ typedef struct EventTriggerQueryState
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
 	MemoryContext cxt;
+	StashedCommand *curcmd;
 	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
@@ -1044,6 +1045,7 @@ EventTriggerBeginCompleteQuery(void)
 	state->cxt = cxt;
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
+	state->curcmd = NULL;
 	state->stash = NIL;
 
 	state->previous = currentEventTriggerState;
@@ -1331,6 +1333,113 @@ 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;
+	/* XXX is it necessary to have the whole parsetree? probably not ... */
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->curcmd = stashed;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+void
+EventTriggerComplexCmdSetOid(Oid objectId)
+{
+	currentEventTriggerState->curcmd->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerRecordSubcmd
+ * 		Save data about a single part of a complex DDL command
+ *
+ * Right now we only support ALTER TABLE; there are no other DDL commands that
+ * require this.  (ALTER TYPE can also generate multiple subcommands, but it's
+ * actually parsed as ALTER TABLE, so there is no difference at this level.)
+ */
+void
+EventTriggerRecordSubcmd(Node *subcmd, Oid relid, AttrNumber attnum,
+						 Oid newoid)
+{
+	MemoryContext	oldcxt;
+	StashedATSubcmd *newsub;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(OidIsValid(currentEventTriggerState->curcmd->d.alterTable.objectId));
+
+	/*
+	 * If we receive a subcommand intended for a relation other than the one
+	 * we've started the complex command for, ignore it.  This is chiefly
+	 * concerned with inheritance situations: in such cases, alter table
+	 * would dispatch multiple copies of the same command for various things,
+	 * but we're only concerned with the one for the main table.
+	 */
+	if (relid != currentEventTriggerState->curcmd->d.alterTable.objectId)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(StashedATSubcmd));
+	newsub->attnum = attnum;
+	newsub->oid = newoid;
+	newsub->parsetree = copyObject(subcmd);
+
+	currentEventTriggerState->curcmd->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->curcmd->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerEndRecordingSubcmds
+ * 		Finish up saving a complex DDL command
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerComplexCmdEnd(void)
+{
+	/* If no subcommands, don't stash anything */
+	if (list_length(currentEventTriggerState->curcmd->d.alterTable.subcmds) != 0)
+	{
+		currentEventTriggerState->stash =
+			lappend(currentEventTriggerState->stash,
+					currentEventTriggerState->curcmd);
+	}
+	else
+		pfree(currentEventTriggerState->curcmd);
+
+	currentEventTriggerState->curcmd = NULL;
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1409,7 +1518,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;
@@ -1425,6 +1535,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 1a44c91..531d7f3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2757,6 +2757,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerComplexCmdSetOid(relid);
+
 	ATController(rel, cmds, recurse, lockmode);
 }
 
@@ -3622,6 +3624,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
@@ -5726,6 +5731,8 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
  * There is no such command in the grammar, but parse_utilcmd.c converts
  * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
  * us schedule creation of the index at the appropriate time during ALTER.
+ *
+ * Return value is the OID of the new index.
  */
 static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
@@ -9565,7 +9572,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 bf7e2f8..418f584 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2757,6 +2757,390 @@ 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;
+
+	Assert(cmd->type == SCT_AlterTable);
+
+	rel = heap_open(cmd->d.alterTable.objectId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									cmd->d.alterTable.objectId);
+
+	alterTableStmt =
+		new_objtree_VA("ALTER TABLE %{identity}D %{subcmds:, }s", 0);
+	tmp = new_objtree_for_qualname(rel->rd_rel->relnamespace,
+								   RelationGetRelationName(rel));
+	append_object_object(alterTableStmt, "identity", tmp);
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		StashedATSubcmd	*substashed = (StashedATSubcmd *) lfirst(cell);
+		AlterTableCmd	*subcmd = (AlterTableCmd *) substashed->parsetree;
+		ObjTree	   *tree;
+
+		Assert(IsA(subcmd, AlterTableCmd));
+
+		switch (subcmd->subtype)
+		{
+			case AT_AddColumn:
+			case AT_AddColumnRecurse:
+				/* XXX need to set the "recurse" bit somewhere? */
+				Assert(IsA(subcmd->def, ColumnDef));
+				tree = deparse_ColumnDef(rel, dpcontext, false,
+										 (ColumnDef *) subcmd->def);
+				tmp = new_objtree_VA("ADD COLUMN %{definition}s",
+									 2, "type", ObjTypeString, "add column",
+									 "definition", ObjTypeObject, tree);
+				subcmds = lappend(subcmds,
+								  new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropColumnRecurse:
+			case AT_ValidateConstraintRecurse:
+			case AT_DropConstraintRecurse:
+			case AT_AddOidsRecurse:
+			case AT_AddIndexConstraint:
+			case AT_ReAddIndex:
+			case AT_ReAddConstraint:
+			case AT_ProcessedConstraint:
+			case AT_ReplaceRelOptions:
+				/* Subtypes used for internal operations; nothing to do here */
+				break;
+
+			case AT_AddColumnToView:
+				/* CREATE OR REPLACE VIEW -- nothing to do here */
+				break;
+
+			case AT_ColumnDefault:
+				if (subcmd->def == NULL)
+				{
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP DEFAULT",
+										 1, "type", ObjTypeString, "drop default");
+				}
+				else
+				{
+					List	   *dpcontext;
+					HeapTuple	attrtup;
+					AttrNumber	attno;
+
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DEFAULT %{definition}s",
+										 1, "type", ObjTypeString, "set default");
+
+					dpcontext = deparse_context_for(RelationGetRelationName(rel),
+													RelationGetRelid(rel));
+					attrtup = SearchSysCacheAttName(RelationGetRelid(rel), subcmd->name);
+					attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum;
+					append_string_object(tmp, "definition",
+										 RelationGetColumnDefault(rel, attno, dpcontext));
+					ReleaseSysCache(attrtup);
+				}
+				append_string_object(tmp, "column", subcmd->name);
+
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropNotNull:
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP NOT NULL",
+									 1, "type", ObjTypeString, "drop not null");
+				append_string_object(tmp, "column", subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetNotNull:
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET NOT NULL",
+									 1, "type", ObjTypeString, "set not null");
+				append_string_object(tmp, "column", subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetStatistics:
+				/* not yet */
+				break;
+
+			case AT_SetOptions:
+				/* not yet */
+				break;
+
+			case AT_ResetOptions:
+				/* not yet */
+				break;
+
+			case AT_SetStorage:
+				Assert(IsA(subcmd->def, String));
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STORAGE %{storage}s",
+									 3, "type", ObjTypeString, "set storage",
+									 "column", ObjTypeString, subcmd->name,
+									 "storage", ObjTypeString,
+									 strVal((Value *) subcmd->def));
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropColumn:
+				tmp = new_objtree_VA("DROP COLUMN %{column}I",
+									 2, "type", ObjTypeString, "drop column",
+								 "column", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AddIndex:
+				{
+					Oid			idxOid = substashed->oid;
+					IndexStmt  *istmt;
+					Relation	idx;
+					const char *idxname;
+					Oid			constrOid;
+
+					Assert(IsA(subcmd->def, IndexStmt));
+					istmt = (IndexStmt *) subcmd->def;
+
+					if (!istmt->isconstraint)
+						break;
+
+					idx = relation_open(idxOid, AccessShareLock);
+					idxname = RelationGetRelationName(idx);
+
+					constrOid = get_relation_constraint_oid(
+						cmd->d.alterTable.objectId, idxname, false);
+
+					tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s",
+										 3, "type", ObjTypeString, "add constraint",
+										 "name", ObjTypeString, idxname,
+										 "definition", ObjTypeString,
+										 pg_get_constraintdef_string(constrOid, false));
+					subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;
+
+			case AT_AddConstraint:
+			case AT_AddConstraintRecurse:
+				{
+					/* XXX need to set the "recurse" bit somewhere? */
+					Oid			constrOid = substashed->oid;
+
+					tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s",
+										 3, "type", ObjTypeString, "add constraint",
+										 "name", ObjTypeString, get_constraint_name(constrOid),
+										 "definition", ObjTypeString,
+										 pg_get_constraintdef_string(constrOid, false));
+					subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				}
+				break;
+
+			case AT_AlterConstraint:
+				break;
+
+			case AT_ValidateConstraint:
+				tmp = new_objtree_VA("VALIDATE CONSTRAINT %{constraint}I", 2,
+									 "type", ObjTypeString, "validate constraint",
+									 "constraint", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropConstraint:
+				tmp = new_objtree_VA("DROP CONSTRAINT %{constraint}I", 2,
+									 "type", ObjTypeString, "drop constraint",
+									 "constraint", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AlterColumnType:
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DATA TYPE %{datatype}T collate_clause using_clause",
+									 2, "type", ObjTypeString, "alter column type",
+									 "column", ObjTypeString, subcmd->name);
+				/* FIXME figure out correct typid/typmod , collate clause, using_clause */
+				append_object_object(tmp, "datatype",
+									 new_objtree_for_type(INT4OID, -1));
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				break;
+
+			case AT_ChangeOwner:
+				tmp = new_objtree_VA("OWNER TO %{owner}I",
+									 2, "type", ObjTypeString, "change owner",
+									 "owner",  ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_ClusterOn:
+				tmp = new_objtree_VA("CLUSTER ON %{index}I", 2,
+									 "type", ObjTypeString, "cluster on",
+									 "index", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropCluster:
+				tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+									 "type", ObjTypeString, "set without cluster");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AddOids:
+				tmp = new_objtree_VA("SET WITH OIDS", 1,
+									 "type", ObjTypeString, "set with oids");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropOids:
+				tmp = new_objtree_VA("SET WITHOUT OIDS", 1,
+									 "type", ObjTypeString, "set without oids");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetTableSpace:
+				tmp = new_objtree_VA("SET TABLESPACE %{tablespace}I", 2,
+									 "type", ObjTypeString, "set tablespace",
+									 "tablespace", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetRelOptions:
+				break;
+
+			case AT_ResetRelOptions:
+				break;
+
+				/*
+				 * FIXME --- should we unify representation of all these
+				 * ENABLE/DISABLE TRIGGER commands??
+				 */
+			case AT_EnableTrig:
+				tmp = new_objtree_VA("ENABLE TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "enable trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableAlwaysTrig:
+				tmp = new_objtree_VA("ENABLE ALWAYS TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "enable always trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableReplicaTrig:
+				tmp = new_objtree_VA("ENABLE REPLICA TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "enable replica trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DisableTrig:
+				tmp = new_objtree_VA("DISABLE TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "disable trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableTrigAll:
+				tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "enable trigger all");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DisableTrigAll:
+				tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "disable trigger all");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableTrigUser:
+				tmp = new_objtree_VA("ENABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "enable trigger user");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DisableTrigUser:
+				tmp = new_objtree_VA("DISABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "disable trigger user");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableRule:
+				break;
+
+			case AT_EnableAlwaysRule:
+				break;
+
+			case AT_EnableReplicaRule:
+				break;
+
+			case AT_DisableRule:
+				break;
+
+			case AT_AddInherit:
+				/*
+				 * XXX this case is interesting: we cannot rely on parse node
+				 * because parent name might be unqualified; but there's no way
+				 * to extract it from catalog either, since we don't know which
+				 * of the parents is the new one.
+				 */
+				break;
+
+			case AT_DropInherit:
+				/* XXX ditto ... */
+				break;
+
+			case AT_AddOf:
+				break;
+
+			case AT_DropOf:
+				break;
+
+			case AT_ReplicaIdentity:
+				tmp = new_objtree_VA("REPLICA IDENTITY %{ident}s", 1,
+									 "type", ObjTypeString, "replica identity");
+				switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type)
+				{
+					case REPLICA_IDENTITY_DEFAULT:
+						append_string_object(tmp, "ident", "DEFAULT");
+						break;
+					case REPLICA_IDENTITY_FULL:
+						append_string_object(tmp, "ident", "FULL");
+						break;
+					case REPLICA_IDENTITY_NOTHING:
+						append_string_object(tmp, "ident", "NOTHING");
+						break;
+					case REPLICA_IDENTITY_INDEX:
+						tmp2 = new_objtree_VA("USING INDEX %{index}I", 1,
+											  "index", ObjTypeString,
+											  ((ReplicaIdentityStmt *) subcmd->def)->name);
+						append_object_object(tmp, "ident", tmp2);
+						break;
+				}
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_GenericOptions:
+				break;
+
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+	}
+
+	heap_close(rel, AccessShareLock);
+
+	if (list_length(subcmds) == 0)
+		return NULL;
+
+	append_array_object(alterTableStmt, "subcmds", subcmds);
+	return alterTableStmt;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -3040,6 +3424,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 277b39c..e0e16e6 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -995,6 +995,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)
 						{
@@ -1008,19 +1012,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,
@@ -1177,6 +1194,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,
@@ -1187,6 +1205,7 @@ ProcessUtilitySlow(Node *parsetree,
 									false); /* quiet */
 					EventTriggerStashCommand(objectId, 0, OBJECT_INDEX,
 											 parsetree);
+					EventTriggerComplexCmdEnd();
 				}
 				break;
 
@@ -1275,8 +1294,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, 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 9c276f1..07eb515 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -54,5 +54,10 @@ extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
 
 extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
 						 ObjectType objtype, Node *parsetree);
+extern void EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype);
+extern void EventTriggerComplexCmdSetOid(Oid objectId);
+extern void EventTriggerRecordSubcmd(Node *subcmd, Oid relid,
+						 AttrNumber attnum, Oid newoid);
+extern void EventTriggerComplexCmdEnd(void);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 1278df9..910c716 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;
 
 /*
@@ -52,6 +53,13 @@ typedef struct StashedCommand
 			uint32		objectSubId;
 			ObjectType	objtype;
 		} simple;
+
+		struct AlterTableCommand
+		{
+			Oid		objectId;
+			ObjectType objtype;
+			List   *subcmds;
+		} alterTable;
 	} d;
 } StashedCommand;
 
-- 
1.9.1

0019-deparse-Support-CREATE-VIEW.patchtext/x-diff; charset=us-asciiDownload
>From b28d780effd8e194f7db38b364288b5848fbe5cc 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 19/27] deparse: Support CREATE VIEW

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 418f584..2e617cd 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -680,6 +680,38 @@ 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)
+{
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	Relation	relation;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA("CREATE %{persistence}s VIEW %{identity}D AS %{query}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(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)
  *
@@ -3255,7 +3287,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 771a83c..d0d0872 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 5f1f1e8..9c67b9a 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);
-- 
1.9.1

0020-deparse-Support-CREATE-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From d9f792c5c7df5921b6b69129f0cafbf6e2dc42ae 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 20/27] 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 2e617cd..39feb7f 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"
@@ -2790,6 +2791,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;
@@ -3343,7 +3379,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateOpFamilyStmt:
-			command = NULL;
+			command = deparse_CreateOpFamily(objectId, parsetree);
 			break;
 
 		case T_AlterOpFamilyStmt:
-- 
1.9.1

0021-deparse-Support-CREATE-CONVERSION.patchtext/x-diff; charset=us-asciiDownload
>From fabcb2ad3537dc009d003afd9c1d4c8e15b81c9e 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 21/27] 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 39feb7f..5856a49 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"
@@ -2791,6 +2793,39 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+	HeapTuple   conTup;
+	Form_pg_conversion conForm;
+	ObjTree	   *ccStmt;
+
+	conTup = SearchSysCache1(CONDEFAULT, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(conTup))
+		elog(ERROR, "cache lookup failed for conversion with OID %u", objectId);
+	conForm = (Form_pg_conversion) GETSTRUCT(conTup);
+
+	ccStmt = new_objtree_VA("CREATE %{default}s CONVERSION %{identity}D FOR "
+							"%{source}L TO %{dest}L FROM %{function}D", 0);
+
+	append_string_object(ccStmt, "default",
+						 conForm->condefault ? "DEFAULT" : "");
+	append_object_object(ccStmt, "identity",
+						 new_objtree_for_qualname(conForm->connamespace,
+												  NameStr(conForm->conname)));
+	append_string_object(ccStmt, "source", (char *)
+						 pg_encoding_to_char(conForm->conforencoding));
+	append_string_object(ccStmt, "dest", (char *)
+						 pg_encoding_to_char(conForm->contoencoding));
+	append_object_object(ccStmt, "function",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 conForm->conproc));
+
+	ReleaseSysCache(conTup);
+
+	return ccStmt;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -3367,7 +3402,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateConversionStmt:
-			command = NULL;
+			command = deparse_CreateConversion(objectId, parsetree);
 			break;
 
 		case T_CreateCastStmt:
-- 
1.9.1

0022-deparse-Support-DefineStmt-commands.patchtext/x-diff; charset=utf-8Download
>From 5f1b1f2bfbb012fac86f126e29cceea60d199191 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 22/27] 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 | 855 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 854 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 5856a49..330f33f 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"
@@ -607,6 +612,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(NULL, new_objtree_VA("*", 0)));
+	else
+	{
+		int			i;
+		int			nargs;
+		Oid		   *types;
+		char	   *modes;
+		char	  **names;
+		int			insertorderbyat = -1;
+
+		nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+		if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
+			insertorderbyat = agg->aggnumdirectargs;
+
+		for (i = 0; i < nargs; i++)
+		{
+			tmp = new_objtree_VA("%{order}s%{mode}s%{name}s%{type}T", 0);
+
+			if (i == insertorderbyat)
+				append_string_object(tmp, "order", "ORDER BY ");
+			else
+				append_string_object(tmp, "order", "");
+
+			if (modes)
+				append_string_object(tmp, "mode",
+									 modes[i] == 'v' ? "VARIADIC " : "");
+			else
+				append_string_object(tmp, "mode", "");
+
+			if (names)
+				append_string_object(tmp, "name", names[i]);
+			else
+				append_string_object(tmp, "name", " ");
+
+			append_object_object(tmp, "type",
+								 new_objtree_for_type(types[i], -1));
+
+			list = lappend(list, new_object_object(NULL, tmp));
+
+			/*
+			 * For variadic ordered-set aggregates, we have to repeat
+			 * the last argument. This nasty hack is copied from
+			 * print_function_arguments in ruleutils.c
+			 */
+			if (i == insertorderbyat && i == nargs-1)
+				list = lappend(list, new_object_object(NULL, tmp));
+		}
+	}
+
+	append_array_object(stmt, "types", list);
+
+	list = NIL;
+
+	tmp = new_objtree_VA("SFUNC=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 agg->aggtransfn));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	tmp = new_objtree_VA("STYPE=%{type}T", 0);
+	append_object_object(tmp, "type",
+						 new_objtree_for_type(agg->aggtranstype, -1));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	if (agg->aggtransspace != 0)
+	{
+		tmp = new_objtree_VA("SSPACE=%{space}s", 1,
+							 "space", ObjTypeString,
+							 psprintf("%d", agg->aggtransspace));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggfinalfn))
+	{
+		tmp = new_objtree_VA("FINALFUNC=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggfinalfn));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (agg->aggfinalextra)
+	{
+		tmp = new_objtree_VA("FINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	initval = SysCacheGetAttr(AGGFNOID, aggTup,
+							  Anum_pg_aggregate_agginitval,
+							  &isnull);
+	if (!isnull)
+	{
+		tmp = new_objtree_VA("INITCOND=%{initval}L",
+							 1, "initval", ObjTypeString,
+							 TextDatumGetCString(initval));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggmtransfn))
+	{
+		tmp = new_objtree_VA("MSFUNC=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggmtransfn));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggmtranstype))
+	{
+		tmp = new_objtree_VA("MSTYPE=%{type}T", 0);
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(agg->aggmtranstype, -1));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (agg->aggmtransspace != 0)
+	{
+		tmp = new_objtree_VA("SSPACE=%{space}s", 1,
+							 "space", ObjTypeString,
+							 psprintf("%d", agg->aggmtransspace));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggminvtransfn))
+	{
+		tmp = new_objtree_VA("MINVFUNC=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggminvtransfn));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggmfinalfn))
+	{
+		tmp = new_objtree_VA("MFINALFUNC=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggmfinalfn));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (agg->aggmfinalextra)
+	{
+		tmp = new_objtree_VA("MFINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	initval = SysCacheGetAttr(AGGFNOID, aggTup,
+							  Anum_pg_aggregate_aggminitval,
+							  &isnull);
+	if (!isnull)
+	{
+		tmp = new_objtree_VA("MINITCOND=%{initval}L",
+							 1, "initval", ObjTypeString,
+							 TextDatumGetCString(initval));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (agg->aggkind == AGGKIND_HYPOTHETICAL)
+	{
+		tmp = new_objtree_VA("HYPOTHETICAL=true", 0);
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggsortop))
+	{
+		Oid sortop = agg->aggsortop;
+		Form_pg_operator op;
+		HeapTuple tup;
+
+		tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(sortop));
+		if (!HeapTupleIsValid(tup))
+			elog(ERROR, "cache lookup failed for operator with OID %u", sortop);
+		op = (Form_pg_operator) GETSTRUCT(tup);
+
+		tmp = new_objtree_VA("SORTOP=%{operator}O", 0);
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname(op->oprnamespace,
+													  NameStr(op->oprname)));
+		list = lappend(list, new_object_object(NULL, tmp));
+
+		ReleaseSysCache(tup);
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(procTup);
+	ReleaseSysCache(aggTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt_Collation(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   colTup;
+	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(NULL, tmp));
+
+	if (OidIsValid(oprForm->oprleft))
+	{
+		tmp = new_objtree_VA("LEFTARG=%{type}T", 0);
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(oprForm->oprleft, -1));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(oprForm->oprright))
+	{
+		tmp = new_objtree_VA("RIGHTARG=%{type}T", 0);
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(oprForm->oprright, -1));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(oprForm->oprcom))
+	{
+		tmp = new_objtree_VA("COMMUTATOR=%{oper}O", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprcom));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(oprForm->oprnegate))
+	{
+		tmp = new_objtree_VA("NEGATOR=%{oper}O", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprnegate));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(oprForm->oprrest))
+	{
+		tmp = new_objtree_VA("RESTRICT=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 oprForm->oprrest));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(oprForm->oprjoin))
+	{
+		tmp = new_objtree_VA("JOIN=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 oprForm->oprjoin));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (oprForm->oprcanmerge)
+		list = lappend(list, new_object_object(NULL,
+											   new_objtree_VA("MERGES", 0)));
+	if (oprForm->oprcanhash)
+		list = lappend(list, new_object_object(NULL,
+											   new_objtree_VA("HASHES", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(oprTup);
+
+	return stmt;
+}
+
+static 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(NULL, tmp));
+
+	tmp = new_objtree_VA("GETTOKEN=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prstoken));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	tmp = new_objtree_VA("END=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prsend));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	tmp = new_objtree_VA("LEXTYPES=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prslextype));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	if (OidIsValid(tspForm->prsheadline))
+	{
+		tmp = new_objtree_VA("HEADLINE=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 tspForm->prsheadline));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt_TSDictionary(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   tsdTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	Datum		options;
+	bool		isnull;
+	Form_pg_ts_dict tsdForm;
+
+	tsdTup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tsdTup))
+		elog(ERROR, "cache lookup failed for text search dictionary "
+			 "with OID %u", objectId);
+	tsdForm = (Form_pg_ts_dict) GETSTRUCT(tsdTup);
+
+	stmt = new_objtree_VA("CREATE TEXT SEARCH DICTIONARY %{identity}D "
+						  "(%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(tsdForm->dictnamespace,
+												  NameStr(tsdForm->dictname)));
+
+	list = NIL;
+
+	tmp = new_objtree_VA("TEMPLATE=%{template}D", 0);
+	append_object_object(tmp, "template",
+						 new_objtree_for_qualname_id(TSTemplateRelationId,
+													 tsdForm->dicttemplate));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	options = SysCacheGetAttr(TSDICTOID, tsdTup,
+							  Anum_pg_ts_dict_dictinitoption,
+							  &isnull);
+	if (!isnull)
+	{
+		tmp = new_objtree_VA("%{options}s", 0);
+		append_string_object(tmp, "options", TextDatumGetCString(options));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tsdTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   tstTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	Form_pg_ts_template tstForm;
+
+	tstTup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tstTup))
+		elog(ERROR, "cache lookup failed for text search template with OID %u",
+			 objectId);
+	tstForm = (Form_pg_ts_template) GETSTRUCT(tstTup);
+
+	stmt = new_objtree_VA("CREATE TEXT SEARCH TEMPLATE %{identity}D "
+						  "(%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(tstForm->tmplnamespace,
+												  NameStr(tstForm->tmplname)));
+
+	list = NIL;
+
+	if (OidIsValid(tstForm->tmplinit))
+	{
+		tmp = new_objtree_VA("INIT=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 tstForm->tmplinit));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	tmp = new_objtree_VA("LEXIZE=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tstForm->tmpllexize));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tstTup);
+
+	return stmt;
+}
+
+static ObjTree *
+deparse_DefineStmt_Type(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   typTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	char	   *str;
+	Datum		dflt;
+	bool		isnull;
+	Form_pg_type typForm;
+
+	typTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(typTup))
+		elog(ERROR, "cache lookup failed for type with OID %u", objectId);
+	typForm = (Form_pg_type) GETSTRUCT(typTup);
+
+	/* Shortcut processing for shell types. */
+	if (!typForm->typisdefined)
+	{
+		stmt = new_objtree_VA("CREATE TYPE %{identity}D", 0);
+		append_object_object(stmt, "identity",
+							 new_objtree_for_qualname(typForm->typnamespace,
+													  NameStr(typForm->typname)));
+		ReleaseSysCache(typTup);
+		return stmt;
+	}
+
+	stmt = new_objtree_VA("CREATE TYPE %{identity}D (%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(typForm->typnamespace,
+												  NameStr(typForm->typname)));
+
+	list = NIL;
+
+	/* INPUT */
+	tmp = new_objtree_VA("INPUT=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 typForm->typinput));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* OUTPUT */
+	tmp = new_objtree_VA("OUTPUT=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 typForm->typoutput));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* RECEIVE */
+	if (OidIsValid(typForm->typreceive))
+	{
+		tmp = new_objtree_VA("RECEIVE=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typreceive));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* SEND */
+	if (OidIsValid(typForm->typsend))
+	{
+		tmp = new_objtree_VA("SEND=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typsend));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* TYPMOD_IN */
+	if (OidIsValid(typForm->typmodin))
+	{
+		tmp = new_objtree_VA("TYPMOD_IN=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typmodin));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* TYPMOD_OUT */
+	if (OidIsValid(typForm->typmodout))
+	{
+		tmp = new_objtree_VA("TYPMOD_OUT=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typmodout));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* ANALYZE */
+	if (OidIsValid(typForm->typanalyze))
+	{
+		tmp = new_objtree_VA("ANALYZE=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typanalyze));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* INTERNALLENGTH */
+	tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}s", 0);
+	if (typForm->typlen == -1)
+		append_string_object(tmp, "typlen", "VARIABLE");
+	else
+		append_string_object(tmp, "typlen",
+							 psprintf("%d", typForm->typlen));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* PASSEDBYVALUE */
+	if (typForm->typbyval)
+		list = lappend(list, new_object_object(NULL, new_objtree_VA("PASSEDBYVALUE", 0)));
+
+	/* ALIGNMENT */
+	tmp = new_objtree_VA("ALIGNMENT=%{align}s", 0);
+	switch (typForm->typalign)
+	{
+		case 'd':
+			str = "pg_catalog.float8";
+			break;
+		case 'i':
+			str = "pg_catalog.int4";
+			break;
+		case 's':
+			str = "pg_catalog.int2";
+			break;
+		case 'c':
+			str = "pg_catalog.bpchar";
+			break;
+		default:
+			elog(ERROR, "invalid alignment %c", typForm->typalign);
+	}
+	append_string_object(tmp, "align", str);
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	tmp = new_objtree_VA("STORAGE=%{storage}s", 0);
+	switch (typForm->typstorage)
+	{
+		case 'p':
+			str = "plain";
+			break;
+		case 'e':
+			str = "external";
+			break;
+		case 'x':
+			str = "extended";
+			break;
+		case 'm':
+			str = "main";
+			break;
+		default:
+			elog(ERROR, "invalid storage specifier %c", typForm->typstorage);
+	}
+	append_string_object(tmp, "storage", str);
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* CATEGORY */
+	tmp = new_objtree_VA("CATEGORY=%{category}L", 0);
+	append_string_object(tmp, "category",
+						 psprintf("%c", typForm->typcategory));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* PREFERRED */
+	if (typForm->typispreferred)
+		list = lappend(list, new_object_object(NULL, new_objtree_VA("PREFERRED=true", 0)));
+
+	/* DEFAULT */
+	dflt = SysCacheGetAttr(TYPEOID, typTup,
+						   Anum_pg_type_typdefault,
+						   &isnull);
+	if (!isnull)
+	{
+		tmp = new_objtree_VA("DEFAULT=%{default}L", 0);
+		append_string_object(tmp, "default", TextDatumGetCString(dflt));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* ELEMENT */
+	if (OidIsValid(typForm->typelem))
+	{
+		tmp = new_objtree_VA("ELEMENT=%{elem}T", 0);
+		append_object_object(tmp, "elem",
+							 new_objtree_for_type(typForm->typelem, -1));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* DELIMITER */
+	tmp = new_objtree_VA("DELIMITER=%{delim}L", 0);
+	append_string_object(tmp, "delim",
+						 psprintf("%c", typForm->typdelim));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* COLLATABLE */
+	if (OidIsValid(typForm->typcollation))
+		list = lappend(list,
+					   new_object_object(NULL,
+										 new_objtree_VA("COLLATABLE=true", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(typTup);
+
+	return stmt;
+}
+
+static 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
@@ -3289,7 +4142,7 @@ deparse_simple_command(StashedCommand *cmd)
 
 			/* other local objects */
 		case T_DefineStmt:
-			command = NULL;
+			command = deparse_DefineStmt(objectId, parsetree);
 			break;
 
 		case T_IndexStmt:
-- 
1.9.1

0023-deparse-support-ALTER-THING-OWNER-TO.patchtext/x-diff; charset=us-asciiDownload
>From 23e42b53289adc911e7158f1e0ebd4ad98c65fd2 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 23/27] 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 330f33f..740956f 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3646,6 +3646,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;
@@ -4296,7 +4319,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterOwnerStmt:
-			command = NULL;
+			command = deparse_AlterOwnerStmt(objectId, parsetree);
 			break;
 
 		case T_CommentStmt:
-- 
1.9.1

0024-deparse-Support-ALTER-EXTENSION-UPDATE-TO.patchtext/x-diff; charset=us-asciiDownload
>From fcba100ba5da8c806b0521ad6d960677219aa7c2 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 24/27] 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 740956f..155776d 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1535,6 +1535,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
@@ -4177,7 +4219,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterExtensionStmt:
-			command = NULL;
+			command = deparse_AlterExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionContentsStmt:
-- 
1.9.1

0025-deparse-Support-GRANT-REVOKE.patchtext/x-diff; charset=us-asciiDownload
>From 9f4f967742cc4749cf6486d310f4d71b1b686e24 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 25/27] deparse: Support GRANT/REVOKE

---
 src/backend/catalog/aclchk.c         |  36 ++----
 src/backend/commands/event_trigger.c |  65 +++++++++++
 src/backend/tcop/deparse_utility.c   | 216 +++++++++++++++++++++++++++++++++++
 src/include/commands/event_trigger.h |   2 +
 src/include/tcop/deparse_utility.h   |  10 +-
 src/include/utils/aclchk.h           |  45 ++++++++
 6 files changed, 347 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 d30612c..175b1a1 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "foreign/foreign.h"
@@ -56,6 +57,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
 #include "utils/acl.h"
+#include "utils/aclchk.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
@@ -65,32 +67,6 @@
 
 
 /*
- * The information about one Grant/Revoke statement, in internal format: object
- * and grantees names have been turned into Oids, the privilege list is an
- * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
- * all_privs is true, 'privileges' will be internally set to the right kind of
- * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
- * InternalGrant struct!)
- *
- * Note: 'all_privs' and 'privileges' represent object-level privileges only.
- * There might also be column-level privilege specifications, which are
- * represented in col_privs (this is a list of untransformed AccessPriv nodes).
- * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
- */
-typedef struct
-{
-	bool		is_grant;
-	GrantObjectType objtype;
-	List	   *objects;
-	bool		all_privs;
-	AclMode		privileges;
-	List	   *col_privs;
-	List	   *grantees;
-	bool		grant_option;
-	DropBehavior behavior;
-} InternalGrant;
-
-/*
  * Internal format used by ALTER DEFAULT PRIVILEGES.
  */
 typedef struct
@@ -602,6 +578,14 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	EventTriggerStashGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 136c86a..be8c3fb 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1440,6 +1440,48 @@ EventTriggerComplexCmdEnd(void)
 	currentEventTriggerState->curcmd = NULL;
 }
 
+/*
+ * EventTriggerStashGrant
+ * 		Save data about a GRANT/REVOKE command being executed
+ *
+ * This function creates a copy of the InternalGrant, as the original might
+ * not have the right lifetime.
+ */
+void
+EventTriggerStashGrant(InternalGrant *istmt)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+	InternalGrant  *icopy;
+	ListCell	   *cell;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	/*
+	 * copying the node is moderately challenging ... XXX should we consider
+	 * changing InternalGrant into a full-fledged node instead?
+	 */
+	icopy = palloc(sizeof(InternalGrant));
+	memcpy(icopy, istmt, sizeof(InternalGrant));
+	icopy->objects = list_copy(istmt->objects);
+	icopy->grantees = list_copy(istmt->grantees);
+	icopy->col_privs = NIL;
+	foreach(cell, istmt->col_privs)
+		icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell)));
+
+	stashed = palloc(sizeof(StashedCommand));
+	stashed->type = SCT_Grant;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.grant.istmt = icopy;
+	stashed->parsetree = NULL;
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1607,6 +1649,29 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 				/* command */
 				values[i++] = CStringGetTextDatum(command);
 			}
+			else
+			{
+				Assert(cmd->type == SCT_Grant);
+
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum("GRANT");	/* XXX maybe REVOKE or something else */
+				/* object_type */
+				values[i++] = CStringGetTextDatum("TABLE"); /* XXX maybe something else */
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+			}
 
 			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 		}
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 155776d..8a9223e 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"
@@ -3779,6 +3784,214 @@ 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 = "SERVER";
+			classId = ForeignServerRelationId;
+			break;
+		case ACL_OBJECT_FUNCTION:
+			objtype = "FUNCTION";
+			classId = ProcedureRelationId;
+			break;
+		case ACL_OBJECT_LANGUAGE:
+			objtype = "LANGUAGE";
+			classId = LanguageRelationId;
+			break;
+		case ACL_OBJECT_LARGEOBJECT:
+			objtype = "LARGE OBJECT";
+			classId = LargeObjectRelationId;
+			break;
+		case ACL_OBJECT_NAMESPACE:
+			objtype = "SCHEMA";
+			classId = NamespaceRelationId;
+			break;
+		case ACL_OBJECT_TYPE:
+			objtype = "TYPE";
+			classId = TypeRelationId;
+			break;
+		case ACL_OBJECT_DATABASE:
+		case ACL_OBJECT_TABLESPACE:
+			objtype = "";
+			classId = InvalidOid;
+			elog(ERROR, "global objects not supported");
+		default:
+			elog(ERROR, "invalid ACL_OBJECT value %d", istmt->objtype);
+	}
+
+	/* GRANT TO or REVOKE FROM */
+	if (istmt->is_grant)
+		fmt = psprintf("GRANT %%{privileges:, }s ON %s %%{privtarget:, }s "
+					   "TO %%{grantees:, }s %%{grant_option}s",
+					   objtype);
+	else
+		fmt = psprintf("REVOKE %%{grant_option}s %%{privileges:, }s ON %s %%{privtarget:, }s "
+					   "FROM %%{grantees:, }s %%{cascade}s",
+					   objtype);
+
+	grantStmt = new_objtree_VA(fmt, 0);
+
+	/* build list of privileges to grant/revoke */
+	if (istmt->all_privs)
+	{
+		tmp = new_objtree_VA("ALL PRIVILEGES", 0);
+		list = list_make1(new_object_object(NULL, tmp));
+	}
+	else
+	{
+		list = NIL;
+
+		if (istmt->privileges & ACL_INSERT)
+			list = lappend(list, new_string_object(NULL, "INSERT"));
+		if (istmt->privileges & ACL_SELECT)
+			list = lappend(list, new_string_object(NULL, "SELECT"));
+		if (istmt->privileges & ACL_UPDATE)
+			list = lappend(list, new_string_object(NULL, "UPDATE"));
+		if (istmt->privileges & ACL_DELETE)
+			list = lappend(list, new_string_object(NULL, "DELETE"));
+		if (istmt->privileges & ACL_TRUNCATE)
+			list = lappend(list, new_string_object(NULL, "TRUNCATE"));
+		if (istmt->privileges & ACL_REFERENCES)
+			list = lappend(list, new_string_object(NULL, "REFERENCES"));
+		if (istmt->privileges & ACL_TRIGGER)
+			list = lappend(list, new_string_object(NULL, "TRIGGER"));
+		if (istmt->privileges & ACL_EXECUTE)
+			list = lappend(list, new_string_object(NULL, "EXECUTE"));
+		if (istmt->privileges & ACL_USAGE)
+			list = lappend(list, new_string_object(NULL, "USAGE"));
+		if (istmt->privileges & ACL_CREATE)
+			list = lappend(list, new_string_object(NULL, "CREATE"));
+		if (istmt->privileges & ACL_CREATE_TEMP)
+			list = lappend(list, new_string_object(NULL, "TEMPORARY"));
+		if (istmt->privileges & ACL_CONNECT)
+			list = lappend(list, new_string_object(NULL, "CONNECT"));
+
+		if (istmt->col_privs != NIL)
+		{
+			ListCell   *ocell;
+
+			foreach(ocell, istmt->col_privs)
+			{
+				AccessPriv *priv = lfirst(ocell);
+				List   *cols = NIL;
+
+				tmp = new_objtree_VA("%{priv}s (%{cols:, }I)", 0);
+				foreach(cell, priv->cols)
+				{
+					Value *colname = lfirst(cell);
+
+					cols = lappend(cols,
+								   new_string_object(NULL,
+													 strVal(colname)));
+				}
+				append_array_object(tmp, "cols", cols);
+				if (priv->priv_name == NULL)
+					append_string_object(tmp, "priv", "ALL PRIVILEGES");
+				else
+					append_string_object(tmp, "priv", priv->priv_name);
+
+				list = lappend(list, new_object_object(NULL, tmp));
+			}
+		}
+	}
+	append_array_object(grantStmt, "privileges", list);
+
+	/* target objects.  We use object identities here */
+	list = NIL;
+	foreach(cell, istmt->objects)
+	{
+		Oid		objid = lfirst_oid(cell);
+		ObjectAddress addr;
+
+		addr.classId = classId;
+		addr.objectId = objid;
+		addr.objectSubId = 0;
+
+		tmp = new_objtree_VA("%{identity}s", 0);
+		append_string_object(tmp, "identity",
+							 getObjectIdentity(&addr));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+	append_array_object(grantStmt, "privtarget", list);
+
+	/* list of grantees */
+	list = NIL;
+	foreach(cell, istmt->grantees)
+	{
+		Oid		grantee = lfirst_oid(cell);
+
+		if (grantee == ACL_ID_PUBLIC)
+			tmp = new_objtree_VA("PUBLIC", 0);
+		else
+		{
+			HeapTuple	roltup;
+			char	   *rolname;
+
+			roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(grantee));
+			if (!HeapTupleIsValid(roltup))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("role with OID %u does not exist", grantee)));
+
+			tmp = new_objtree_VA("%{name}I", 0);
+			rolname = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname);
+			append_string_object(tmp, "name", pstrdup(rolname));
+			ReleaseSysCache(roltup);
+		}
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+	append_array_object(grantStmt, "grantees", list);
+
+	/* the wording of the grant option is variable ... */
+	if (istmt->is_grant)
+		append_string_object(grantStmt, "grant_option",
+							 istmt->grant_option ?  "WITH GRANT OPTION" : "");
+	else
+		append_string_object(grantStmt, "grant_option",
+							 istmt->grant_option ?  "GRANT OPTION FOR" : "");
+
+	if (!istmt->is_grant)
+	{
+		if (istmt->behavior == DROP_CASCADE)
+			append_string_object(grantStmt, "cascade", "CASCADE");
+		else
+			append_string_object(grantStmt, "cascade", "");
+	}
+
+	return grantStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -4448,6 +4661,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 07eb515..672a6f5 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
 {
@@ -54,6 +55,7 @@ extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
 
 extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
 						 ObjectType objtype, Node *parsetree);
+extern void EventTriggerStashGrant(InternalGrant *istmt);
 extern void EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype);
 extern void EventTriggerComplexCmdSetOid(Oid objectId);
 extern void EventTriggerRecordSubcmd(Node *subcmd, Oid relid,
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 910c716..6783de7 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,11 @@ typedef struct StashedCommand
 			ObjectType objtype;
 			List   *subcmds;
 		} alterTable;
+
+		struct GrantCommand
+		{
+			InternalGrant *istmt;
+		} grant;
 	} d;
 } StashedCommand;
 
diff --git a/src/include/utils/aclchk.h b/src/include/utils/aclchk.h
new file mode 100644
index 0000000..1ca7095
--- /dev/null
+++ b/src/include/utils/aclchk.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * aclchk.h
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/aclchk.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLCHK_H
+#define ACLCHK_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+/*
+ * The information about one Grant/Revoke statement, in internal format: object
+ * and grantees names have been turned into Oids, the privilege list is an
+ * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
+ * all_privs is true, 'privileges' will be internally set to the right kind of
+ * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
+ * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
+ */
+typedef struct
+{
+	bool		is_grant;
+	GrantObjectType objtype;
+	List	   *objects;
+	bool		all_privs;
+	AclMode		privileges;
+	List	   *col_privs;
+	List	   *grantees;
+	bool		grant_option;
+	DropBehavior behavior;
+} InternalGrant;
+
+
+#endif	/* ACLCHK_H */
-- 
1.9.1

0026-deparse-Support-ALTER-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From cc1f87a9d0245165dc8b7aa936ceb10d0d0383ac Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 8 Aug 2014 14:55:47 +0200
Subject: [PATCH 26/27] 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 8a9223e..cd5fc2a 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2898,6 +2898,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(NULL, 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}s", 1,
+								 "cost", ObjTypeString,
+								 psprintf("%f", defGetNumeric(defel)));
+		}
+		else if (strcmp(defel->defname, "rows") == 0)
+		{
+			tmp = new_objtree_VA("ROWS %{rows}s", 0);
+			if (defGetNumeric(defel) == 0)
+				append_bool_object(tmp, "present", false);
+			else
+				append_string_object(tmp, "rows",
+									 psprintf("%f", defGetNumeric(defel)));
+		}
+
+		if (tmp)
+			elems = lappend(elems, new_object_object(NULL, tmp));
+	}
+
+	append_array_object(alterFunc, "definition", elems);
+
+	ReleaseSysCache(procTup);
+
+	return alterFunc;
+}
+
+/*
  * Return the given object type as a string.
  */
 static const char *
@@ -4497,7 +4604,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterFunctionStmt:
-			command = NULL;
+			command = deparse_AlterFunction(objectId, parsetree);
 			break;
 
 		case T_RuleStmt:
-- 
1.9.1

0027-deparse-support-COMMENT-ON.patchtext/x-diff; charset=us-asciiDownload
>From c006f81af13da076d6d50ad9d055bf142f8a7305 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 27/27] deparse: support COMMENT ON

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index cd5fc2a..a7184d7 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3823,6 +3823,29 @@ deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	ObjectAddress addr;
+	char	   *fmt;
+
+	fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}L",
+				   stringify_objtype(node->objtype));
+	comment = new_objtree_VA(fmt, 0);
+	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)
 {
 	HeapTuple   conTup;
@@ -4685,7 +4708,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:
-- 
1.9.1

#3Andres Freund
andres@anarazel.de
In reply to: Alvaro Herrera (#2)

On 2014-09-25 18:59:31 -0300, Alvaro Herrera wrote:

/* tid.c */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
new file mode 100644
index 0000000..520b066
--- /dev/null
+++ b/src/include/utils/ruleutils.h
@@ -0,0 +1,34 @@
+/*-------------------------------------------------------------------------
+ *
+ * ruleutils.h
+ *		Declarations for ruleutils.c
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/ruleutils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef RULEUTILS_H
+#define RULEUTILS_H
+
+#include "nodes/nodes.h"
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+
+extern char *pg_get_indexdef_string(Oid indexrelid);
+extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
+
+extern char *pg_get_constraintdef_string(Oid constraintId);
+extern char *deparse_expression(Node *expr, List *dpcontext,
+				   bool forceprefix, bool showimplicit);
+extern List *deparse_context_for(const char *aliasname, Oid relid);
+extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
+							  List *rtable, List *rtable_names);
+extern List *select_rtable_names_for_explain(List *rtable,
+								Bitmapset *rels_used);
+extern char *generate_collation_name(Oid collid);
+
+#endif	/* RULEUTILS_H */
-- 
1.9.1

I wondered for a minute whether any of these are likely to cause
problems for code just including builtins.h - but I think there will be
sufficiently few callers for them to make that not much of a concern.

From 5e3555058a1bbb93e25a3a104c26e6b96dce994d 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/27] deparse/core: have RENAME return attribute number

Looks "harmless". I.e. +1.

From 72c4d0ef875f9e9b0f3b424499738fd1bd946c32 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/27] deparse/core: event triggers support GRANT/REVOKE

Hm. The docs don't mention whether these only work for database local
objects or also shared objects. Generally event trigger are only
triggered for the former... But the code doesn't seem to make a
difference?

From 5bb35d2e51fe6c6e219fe5cf7ddbab5423e6be1b 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/27] deparse/core: event triggers support COMMENT

---
doc/src/sgml/event-trigger.sgml | 8 +++++++-
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, 30 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 49b8384..39ecd94 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
@@ -281,6 +281,12 @@
<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>
+       </row>
+       <row>

Hm. No drop support because COMMENTs are odd and use odd NULL
semantics. Fair enough.

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

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

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b69b75b..a2445f1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -723,6 +723,121 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
}
/*
+ * Return the OID of the catalog corresponding to the given object type
+ */
+Oid
+get_objtype_catalog_oid(ObjectType objtype)
+{
+		case OBJECT_ATTRIBUTE:
+			catalog_id = TypeRelationId;	/* XXX? */
+			break;

I'm indeed not clear why thats a meaningful choice?

+/* XXX merge this with ObjectTypeMap? */

hm. I think that's obsolete by now?

@@ -924,6 +929,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_ATTRIBUTE:
case OBJECT_CAST:
case OBJECT_COLUMN:
+ case OBJECT_COMPOSITE:

Is it actually correct that we return false here up to now? I.e. isn't
this a bug?

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

I'm not really sure why that subtype is called 'simple'?

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

I wonder wether we shouldn't introduce ERRCODE_WRONG_CONTEXT or
such. Seems useful here and in a fair number of other situations. But
not really required, just something that annoyed me before.

+	foreach(lc, currentEventTriggerState->stash)
+	{
+		StashedCommand *cmd = lfirst(lc);
+		char	   *command;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid; in those cases, return an empty
+		 * command instead of trying to soldier on.
+		 *
+		 * XXX an alternative would be to look up the Oid of the existing
+		 * object and run the deparse with that.  But since the parse tree
+		 * might be different from the one that created the object in the first
+		 * place, we might not end up in a consistent state anyway.
+		 */
+		if (cmd->type == SCT_Simple &&
+			!OidIsValid(cmd->d.simple.objectId))
+			continue;

I'd replace the XXX by 'One might think that'. You've perfectly
explained why that's not a viable alternative.

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

So: a) we only support SCT_Simple here. b) we add commands whether
they're supported or not? And afaics the only way to discern that is by
looking at 'schema'? If we're not skipping unsupported stuff, shouldn't
we at least set a column indicating that?

+/* ************************* JSON STUFF FROM HERE ************************* *
+ *	Code below is used to decode blobs returned by deparse_utility_command	*
+ *																			*/
+
+/*
+ * Note we only support types that are valid in command representation from
+ * deparse_utility_command.
+ */
+typedef enum
+{
+	JsonTypeArray,
+	JsonTypeObject,
+	JsonTypeString
+} JsonType;

There indeed seems to be no existing enum/define for this. Odd.

+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	SpecStringLiteral,
+	SpecIdentifier
+} convSpecifier;

Ok, these are all pretty specific to the usecase.

+/*
+ * Extract the named json field, which must be of type string, from the given
+ * JSON datum, which must be of type object.  If the field doesn't exist,
+ * NULL is returned.  Otherwise the string value is returned.
+ */
+static char *
+expand_get_strval(Datum json, char *field_name)
+{
+	FunctionCallInfoData fcinfo;
+	Datum		result;
+	char	   *value_str;
+
+	InitFunctionCallInfoData(fcinfo, NULL, 2, InvalidOid, NULL, NULL);
+
+	fcinfo.arg[0] = json;
+	fcinfo.argnull[0] = false;
+	fcinfo.arg[1] = CStringGetTextDatum(field_name);
+	fcinfo.argnull[1] = false;
+
+	result = (*json_object_field_text) (&fcinfo);

I've not looked it up, but is it actually legal to call functions
without flinfo setup? It seems nicer to revamp this to use
FunctionCallInfoInvoke() rather than doing it ourselves.

+/*
+ * Given a JSON value, return its type.
+ *
+ * We return both a JsonType (for easy control flow), and a string name (for
+ * error reporting).
+ */
+static JsonType
+jsonval_get_type(Datum jsonval, char **typename)
+{
+	JsonType	json_elt_type;
+	Datum		paramtype_datum;
+	char	   *paramtype;
+
+	paramtype_datum = DirectFunctionCall1(json_typeof, jsonval);
+	paramtype = TextDatumGetCString(paramtype_datum);

So, here you suddenly use a different method of invoking functions?

+	if (strcmp(paramtype, "array") == 0)
+		json_elt_type = JsonTypeArray;
+	else if (strcmp(paramtype, "object") == 0)
+		json_elt_type = JsonTypeObject;
+	else if (strcmp(paramtype, "string") == 0)
+		json_elt_type = JsonTypeString;
+	else
+		/* XXX improve this; need to specify array index or param name */
+		elog(ERROR, "unexpected JSON element type %s",
+			 paramtype);

What about that XXX? Seems like the current usage allows that to be
something for the future?

+/*
+ * dequote_jsonval
+ *		Take a string value extracted from a JSON object, and return a copy of it
+ *		with the quoting removed.
+ *
+ * Another alternative to this would be to run the extraction routine again,
+ * using the "_text" variant which returns the value without quotes; but this
+ * is expensive, and moreover it complicates the logic a lot because not all
+ * values are extracted in the same way (some are extracted using
+ * json_object_field, others using json_array_element).  Dequoting the object
+ * already at hand is a lot easier.
+ */
+static char *
+dequote_jsonval(char *jsonval)
+{
+	char	   *result;
+	int			inputlen = strlen(jsonval);
+	int			i;
+	int			j = 0;
+
+	result = palloc(strlen(jsonval) + 1);
+
+	/* skip the start and end quotes right away */
+	for (i = 1; i < inputlen - 1; i++)
+	{
+		if (jsonval[i] == '\\')
+		{
+			i++;
+
+			/* This uses same logic as json.c */
+			switch (jsonval[i])
+			{
+				case 'b':
+					result[j++] = '\b';
+					continue;
+				case 'f':
+					result[j++] = '\f';
+					continue;
+				case 'n':
+					result[j++] = '\n';
+					continue;
+				case 'r':
+					result[j++] = '\r';
+					continue;
+				case 't':
+					result[j++] = '\t';
+					continue;
+				case '"':
+				case '\\':
+				case '/':
+					break;
+				default:
+					/* XXX: ERROR? */
+					break;
+			}
+		}
+
+		result[j++] = jsonval[i];
+	}
+	result[j] = '\0';
+
+	return result;
+}

This looks like something that really should live at least partially in json.c?

+/*
+ * Expand a json value as a dotted-name.  The value must be of type object
+ * and must contain elements "schemaname" (optional), "objname" (mandatory),
+ * "attrname" (optional).
+ *
+ * XXX do we need a "catalogname" as well?
+ */

I'd replace the XXX by 'One day we might need "catalogname" as well, but
no current ..."

+static void
+expand_jsonval_dottedname(StringInfo buf, Datum jsonval)
+{
+	char	   *schema;
+	char	   *objname;
+	char	   *attrname;
+	const char *qschema;
+	const char *qname;
+
+	schema = expand_get_strval(jsonval, "schemaname");
+	objname = expand_get_strval(jsonval, "objname");
+	if (objname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL object name in %%D element")));
+	qname = quote_identifier(objname);
+	if (schema == NULL)
+	{
+		appendStringInfo(buf, "%s", qname);

*appendStringInfoString().

+	}
+	else
+	{
+		qschema = quote_identifier(schema);
+		appendStringInfo(buf, "%s.%s",
+						 qschema, qname);
+		if (qschema != schema)
+			pfree((char *) qschema);
+		pfree(schema);
+	}
+
+	attrname = expand_get_strval(jsonval, "attrname");
+	if (attrname)
+	{
+		const char *qattr;
+
+		qattr = quote_identifier(attrname);
+		appendStringInfo(buf, ".%s", qattr);
+		if (qattr != attrname)
+			pfree((char *) qattr);
+		pfree(attrname);
+	}

Hm. Does specifying schema, object and attrname result ins oemthing
valid with this? Might not ever be required, but in that case it should
probably error out

+/*
+ * expand a json value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, Datum jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		array_isnull;
+	bool		is_array;
+
+	typename = expand_get_strval(jsonval, "typename");
+	if (typename == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL type name in %%T element")));
+	typmodstr = expand_get_strval(jsonval, "typmod");	/* OK if null */
+	is_array = expand_get_boolval(jsonval, "is_array", &array_isnull);
+	schema = expand_get_strval(jsonval, "schemaname");
+
+	/*
+	 * If schema is NULL, then don't schema qualify, but do quote the type
+	 * name; if the schema is empty instead, then we don't quote the type name.
+	 * This is our (admittedly quite ugly) way of dealing with type names that
+	 * might require special treatment.
+	 */

Indeed, really quite ugly. What's it needed for? Maybe reference the
function that needs it to explain.

+
+/*
+ * Expand a json value as an operator name
+ */
+static void
+expand_jsonval_operator(StringInfo buf, Datum jsonval)
+{
+	char	   *schema = NULL;
+	char	   *operator;
+
+	operator = expand_get_strval(jsonval, "objname");
+	if (operator == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL operator name in %%O element")));
+	schema = expand_get_strval(jsonval, "schemaname");
+
+	/* schema might be NULL or empty */

Why can it be empty here?

+	if (schema == NULL || schema[0] == '\0')
+		appendStringInfo(buf, "%s", operator);
+	else
+		appendStringInfo(buf, "%s.%s",
+						 quote_identifier(schema),
+						 operator);
+}
+/*
+ * Expand a json value as a string.  The value must be of type string or of
+ * type object, in which case it must contain a "fmt" element which will be
+ * recursively expanded; also, if the object contains an element "present"
+ * and it is set to false, the expansion is the empty string.
+ */
+static void
+expand_jsonval_string(StringInfo buf, Datum jsonval, JsonType json_elt_type)
+{
+	if (json_elt_type == JsonTypeString)
+	{
+		char	   *str;
+		char	   *unquoted;
+
+		str = TextDatumGetCString(jsonval);
+		unquoted = dequote_jsonval(str);
+		appendStringInfo(buf, "%s", unquoted);
+		pfree(str);
+		pfree(unquoted);
+	}
+	else if (json_elt_type == JsonTypeObject)
+	{
+		bool		present;
+		bool		isnull;
+		present = expand_get_boolval(jsonval, "present", &isnull);

Nitpick, but I find isnull slightly confusing as a name here.

+		if (isnull || present)
+		{
+			Datum		inner;
+			char	   *str;
+
+			inner = DirectFunctionCall1(pg_event_trigger_expand_command,
+										jsonval);
+			str = TextDatumGetCString(inner);
+
+			appendStringInfoString(buf, str);
+			pfree(DatumGetPointer(inner));
+			pfree(str);
+		}
+	}
+}
+/*
+ * Expand a json value as a string literal
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, Datum jsonval)
+{
+	char   *str;
+	char   *unquoted;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int         dqnextchar = 0;
+
+	/* obtain the string, and remove the JSON quotes and stuff */
+	str = TextDatumGetCString(jsonval);
+	unquoted = dequote_jsonval(str);
+
+	/* easy case: if there are no ' and no \, just use a single quote */
+	if (strchr(unquoted, '\'') == NULL &&
+		strchr(unquoted, '\\') == NULL)
+	{
+		appendStringInfo(buf, "'%s'", unquoted);
+		return;
+	}
+
+	/* Find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(unquoted, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar %= sizeof(dqsuffixes) - 1;
+	}
+	/* add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, unquoted, dqdelim.data);
+}

Ugh. Do we really need this logic here? Doesn't that already exist
somewhere?

+/*
+ * Expand one json element according to rules.
+ */

What are rules?

+static void
+expand_one_element(StringInfo buf, char *param,
+				   Datum jsonval, char *valtype, JsonType json_elt_type,
+				   convSpecifier specifier)
+{
+	/*
+	 * Validate the parameter type.  If dotted-name was specified, then a JSON
+	 * object element is expected; if an identifier was specified, then a JSON
+	 * string is expected.	If a string was specified, then either a JSON
+	 * object or a string is expected.
+	 */
+	if (specifier == SpecDottedName && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%D element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecTypename && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%T element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecOperatorname && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%O element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecIdentifier && json_elt_type != JsonTypeString)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string for %%I element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecStringLiteral && json_elt_type != JsonTypeString)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string for %%L element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecString &&
+		json_elt_type != JsonTypeString && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string or object for %%s element \"%s\", got %s",
+						param, valtype)));

Isn't this essentially a duplication of the switch below?

+	switch (specifier)
+	{
+		case SpecIdentifier:
+			expand_jsonval_identifier(buf, jsonval);
+			break;
+
+		case SpecDottedName:
+			expand_jsonval_dottedname(buf, jsonval);
+			break;
+
+		case SpecString:
+			expand_jsonval_string(buf, jsonval, json_elt_type);
+			break;
+
+		case SpecStringLiteral:
+			expand_jsonval_strlit(buf, jsonval);
+			break;
+
+		case SpecTypename:
+			expand_jsonval_typename(buf, jsonval);
+			break;
+
+		case SpecOperatorname:
+			expand_jsonval_operator(buf, jsonval);
+			break;
+	}
+}
+
+/*
+ * Expand one JSON array element according to rules.
+ */

The generic "rules" again...

+#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \
+	do { \
+		if (++(ptr) >= (end_ptr)) \
+			ereport(ERROR, \
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
+					 errmsg("unterminated format specifier"))); \
+	} while (0)
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ *
+ * The element name may have an optional separator specification preceded
+ * by a colon.	Its presence indicates that the element is expected to be
+ * an array; the specified separator is used to join the array elements.
+ *
+ * XXX the current implementation works fine, but is likely to be slow: for
+ * each element found in the fmt string we parse the JSON object once.	It
+ * might be better to use jsonapi.h directly so that we build a hash or tree of
+ * elements and their values once before parsing the fmt string, and later scan
+ * fmt using the tree.
+ *------
+ */
+Datum
+pg_event_trigger_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	char	   *fmt_str;
+	int			fmt_len;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	StringInfoData str;
+
+	fmt_str = expand_get_strval(PointerGetDatum(json), "fmt");
+	if (fmt_str == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL format string")));
+	fmt_len = strlen(fmt_str);
+
+	start_ptr = fmt_str;
+	end_ptr = start_ptr + fmt_len;
+	initStringInfo(&str);
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{

I haven't checked, but I'm not sure the code here handles multibyte
stuff correctly. IIRC it's legal for multibyte characters to appear
directly in json?

+		/*
+		 * Obtain the element to be expanded.  Note we cannot use
+		 * DirectFunctionCall here, because the element might not exist.
+		 */

"and the return value thus could be NULL."

+/*
+ * Append a string parameter to a tree.
+ *
+ * Note: we don't pstrdup the source string.  Caller must ensure the
+ * source string lives long enough.
+ */
+static void

Why this difference to most other functions?

+/* Allocate a new object parameter */
+static ObjElem *
+new_object_object(char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeObject;
+	param->obj_value = value;	/* XXX not duped */

Aha? Why? Because it's freshly allocated anyhow? In which case the XXX
doesn't seem warranted?

+/*
+ * Create a JSON blob from our ad-hoc representation.
+ *
+ * Note this leaks some memory; caller is responsible for later clean up.
+ *
+ * XXX this implementation will fail if there are more JSON objects in the tree
+ * than the maximum number of columns in a heap tuple.  To fix we would first call
+ * construct_md_array and then json_object.
+ */

I have no idea what that XXX is supposed to say to me. Note that there's
no construct_md_array in the patchset.

+static char *
+jsonize_objtree(ObjTree *tree)
+{
+	slist_foreach(iter, &tree->params)
+	{
+
+		nulls[i - 1] = false;
+		switch (object->objtype)
+		{
+			case ObjTypeNull:
+				nulls[i - 1] = true;
+				break;
+			case ObjTypeBool:
+				values[i - 1] = BoolGetDatum(object->bool_value);
+				break;
+			case ObjTypeString:
+				values[i - 1] = CStringGetTextDatum(object->str_value);
+				break;
+			case ObjTypeArray:
+				{
+					ArrayType  *arrayt;
+					Datum	   *arrvals;
+					Datum		jsonary;
+					ListCell   *cell;
+					int			length = list_length(object->array_value);
+					int			j;
+
+					/*
+					 * Arrays are stored as Lists up to this point, with each
+					 * element being a ObjElem; we need to construct an
+					 * ArrayType with them to turn the whole thing into a JSON
+					 * array.
+					 */
+					j = 0;
+					arrvals = palloc(sizeof(Datum) * length);
+					foreach(cell, object->array_value)
+					{
+						ObjElem    *elem = lfirst(cell);
+
+						switch (elem->objtype)
+						{
+							case ObjTypeString:
+								arrvals[j] =
+									/*
+									 * XXX need quotes around the value.  This
+									 * needs to be handled differently because
+									 * it will fail for values of anything but
+									 * trivial complexity.
+									 */
+									CStringGetTextDatum(psprintf("\"%s\"",
+																 elem->str_value));
+								break;

Didn't you earlier add a function doing most of this? That XXX looks
like something that definitely needs to be addressed before the commit.

From ea8997de828931e954e1a52821ffc834370859d2 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/27] deparse: sprinkle EventTriggerStashCommand() calls

Seems like a odd separate commit ;). What's the point of not squashing
it?

XXX: I'm now too tired to continue honestly. Man, that's a fair amount
of code... Will try to continue it. Once I've made it through once I
hopefully also give some sensible higher level comments.

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

#4Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#2)
1 attachment(s)

On Fri, Sep 26, 2014 at 6:59 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Actually here's a different split of these patches, which I hope makes
more sense. My intention here is that patches 0001 to 0004 are simple
changes that can be pushed right away; they are not directly related to
the return-creation-command feature. Patches 0005 to 0027 implement
that feature incrementally. You can see in patch 0005 the DDL commands
that are still not implemented in deparse (they are the ones that have
an elog(ERROR) rather than a "command = NULL"). Patch 0006 adds calls
in ProcessUtilitySlow() to each command, so that the object(s) being
touched are added to the event trigger command stash.

Patches from 0007 to 0027 (excepting patch 0017) implement one or a
small number of commands in deparse. Patch 0017 is necessary
infrastructure in ALTER TABLE to support deparsing that one.

My intention with the later patches is that they would all be pushed as
a single commit, i.e. the deparse support would be implemented for all
commands in a fell swoop rather than piecemeal -- except possibly patch
0017 (the ALTER TABLE infrastructure). I split them up only for ease of
review. Of course, before pushing we (I) need to implement deparsing
for all the remaining commands.

Thanks for the update. This stuff is still on my TODO list and I was
planning to look at it at some extent today btw :) Andres has already
sent some comments (20140925235724.GH16581@awork2.anarazel.de) though,
I'll try to not overlap with what has already been mentioned.

Patch 1: I still like this patch as it gives a clear separation of the
built-in functions and the sub-functions of ruleutils.c that are
completely independent. Have you considered adding the external
declaration of quote_all_identifiers as well? It is true that this
impacts extensions (some of my stuff as well), but my point is to bite
the bullet and make the separation cleaner between builtins.h and
ruleutils.h. Attached is a patch that can be applied on top of patch 1
doing so... Feel free to discard for the potential breakage this would
create though.

Patch 2:
1) Declarations of renameatt are inconsistent:
@tablecmds.c:
-renameatt(RenameStmt *stmt)
+renameatt(RenameStmt *stmt, int32 *objsubid)
@tablecmds.h:
-extern Oid     renameatt(RenameStmt *stmt);
+extern Oid     renameatt(RenameStmt *stmt, int *attnum);
Looking at this code, for correctness renameatt should use everywhere
"int *attnum" and ExecRenameStmt should use "int32 *subobjid".
2) Just a smallish picky formatting remark, I would have done that on
a single line:
+       attnum =
+               renameatt_internal(relid,
3) in ExecRenameStmt, I think that you should set subobjid for
aggregate, collations, fdw, etc. There is an access to ObjectAddress
so that's easy to set using address.objectSubId.
4) This comment is misleading, as for an attribute what is returned is
actually an attribute number:
+ * Return value is the OID of the renamed object.  The objectSubId, if any,
+ * is returned in objsubid.
  */
5) Perhaps it is worth mentioning at the top of renameatt that the
attribute number is returned as well?

Patch 3: Looks fine, a bit of testing is showing up that this works as expected:
=# CREATE ROLE foo;
CREATE ROLE
=# CREATE TABLE aa (a int);
CREATE TABLE
=# CREATE OR REPLACE FUNCTION abort_grant()
RETURNS event_trigger AS $$
BEGIN
RAISE NOTICE 'Event: %', tg_event;
RAISE EXCEPTION 'Execution of command % forbidden', tg_tag;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION
=# CREATE EVENT TRIGGER abort_grant_trigger ON ddl_command_start
WHEN TAG IN ('GRANT', 'REVOKE') EXECUTE PROCEDURE abort_grant();
CREATE EVENT TRIGGER
=# GRANT SELECT ON aa TO foo;
NOTICE: 00000: Event: ddl_command_start
LOCATION: exec_stmt_raise, pl_exec.c:3068
ERROR: P0001: Execution of command GRANT forbidden
LOCATION: exec_stmt_raise, pl_exec.c:3068
=# DROP EVENT TRIGGER abort_grant_trigger;
DROP EVENT TRIGGER
=# CREATE EVENT TRIGGER abort_grant_trigger ON ddl_command_end
WHEN TAG IN ('GRANT', 'REVOKE') EXECUTE PROCEDURE abort_grant();
CREATE EVENT TRIGGER
=# REVOKE SELECT ON aa FROM foo;
NOTICE: 00000: Event: ddl_command_end
LOCATION: exec_stmt_raise, pl_exec.c:3068
ERROR: P0001: Execution of command REVOKE forbidden
LOCATION: exec_stmt_raise, pl_exec.c:3068

Patch 4: Shouldn't int32 be used instead of uint32 in the declaration
of CommentObject? And yes, adding support for only ddl_command_start
and ddl_command_end is enough. Let's not play with dropped objects in
this area... Similarly to the test above:
=# comment on table aa is 'test';
NOTICE: 00000: Event: ddl_command_start
LOCATION: exec_stmt_raise, pl_exec.c:3068
ERROR: P0001: Execution of command COMMENT forbidden
LOCATION: exec_stmt_raise, pl_exec.c:3068
Regards,
--
Michael

Attachments:

20140926_ruleutils_quote.patchtext/x-diff; charset=US-ASCII; name=20140926_ruleutils_quote.patchDownload
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index d77b3ee..fc8dc80 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -57,6 +57,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/tqual.h"
 
 #include "dblink.h"
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 2045774..03c6e67 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -50,6 +50,7 @@
 #include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 
 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 4c49776..69d19e3 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -36,6 +36,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/ruleutils.h"
 
 PG_MODULE_MAGIC;
 
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index fdbd313..f865ad7 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -27,6 +27,7 @@
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/relcache.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
diff --git a/contrib/worker_spi/worker_spi.c b/contrib/worker_spi/worker_spi.c
index 328c722..1502b61 100644
--- a/contrib/worker_spi/worker_spi.c
+++ b/contrib/worker_spi/worker_spi.c
@@ -38,6 +38,7 @@
 #include "lib/stringinfo.h"
 #include "pgstat.h"
 #include "utils/builtins.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "tcop/utility.h"
 
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5eb8fd4..dd1b633 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -53,6 +53,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b69b75b..249923b 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -74,6 +74,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 9a0afa4..1475934 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -52,6 +52,7 @@
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/tqual.h"
 
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index d1c8bb0..c6e69d9 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -36,6 +36,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9bf0098..a932ddd 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -53,6 +53,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 5d8a001..5e8001a 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -43,6 +43,7 @@
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 7c1939f..3578070 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -58,6 +58,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index e1763a3..4f262fa 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -23,6 +23,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/numeric.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "mb/pg_wchar.h"
 
diff --git a/src/backend/utils/adt/quote.c b/src/backend/utils/adt/quote.c
index d1336b4..6208c93 100644
--- a/src/backend/utils/adt/quote.c
+++ b/src/backend/utils/adt/quote.c
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "utils/builtins.h"
+#include "utils/ruleutils.h"
 
 
 /*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index c0314ee..a7c618e 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -38,6 +38,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index c0156fa..6b739ca 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -50,6 +50,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index c3171b5..8b27786 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -30,6 +30,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/pg_locale.h"
+#include "utils/ruleutils.h"
 #include "utils/sortsupport.h"
 
 
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index 5ff1461..43a7502 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -45,6 +45,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d208314..c81ca2c 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -79,6 +79,7 @@
 #include "utils/plancache.h"
 #include "utils/portal.h"
 #include "utils/ps_status.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/tzparser.h"
 #include "utils/xml.h"
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fb1b4a4..0ef0323 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -666,7 +666,6 @@ extern Datum record_image_ge(PG_FUNCTION_ARGS);
 extern Datum btrecordimagecmp(PG_FUNCTION_ARGS);
 
 /* ruleutils.c */
-extern bool quote_all_identifiers;
 extern Datum pg_get_ruledef(PG_FUNCTION_ARGS);
 extern Datum pg_get_ruledef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef(PG_FUNCTION_ARGS);
@@ -689,10 +688,6 @@ extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_arg_default(PG_FUNCTION_ARGS);
-extern const char *quote_identifier(const char *ident);
-extern char *quote_qualified_identifier(const char *qualifier,
-						   const char *ident);
-
 
 /* tid.c */
 extern Datum tidin(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 520b066..4a3fb77 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -17,6 +17,10 @@
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
 
+extern bool quote_all_identifiers;
+extern const char *quote_identifier(const char *ident);
+extern char *quote_qualified_identifier(const char *qualifier,
+										const char *ident);
 
 extern char *pg_get_indexdef_string(Oid indexrelid);
 extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 893f3a4..06b9b88 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -22,6 +22,7 @@
 #include "parser/scanner.h"
 #include "parser/scansup.h"
 #include "utils/builtins.h"
+#include "utils/ruleutils.h"
 
 
 /* Location tracking support --- simpler than bison's default */
#5Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#2)
26 attachment(s)

Alvaro Herrera wrote:

Actually here's a different split of these patches, which I hope makes
more sense. My intention here is that patches 0001 to 0004 are simple
changes that can be pushed right away; they are not directly related to
the return-creation-command feature. Patches 0005 to 0027 implement
that feature incrementally. You can see in patch 0005 the DDL commands
that are still not implemented in deparse (they are the ones that have
an elog(ERROR) rather than a "command = NULL"). Patch 0006 adds calls
in ProcessUtilitySlow() to each command, so that the object(s) being
touched are added to the event trigger command stash.

Patches from 0007 to 0027 (excepting patch 0017) implement one or a
small number of commands in deparse. Patch 0017 is necessary
infrastructure in ALTER TABLE to support deparsing that one.

My intention with the later patches is that they would all be pushed as
a single commit, i.e. the deparse support would be implemented for all
commands in a fell swoop rather than piecemeal -- except possibly patch
0017 (the ALTER TABLE infrastructure). I split them up only for ease of
review. Of course, before pushing we (I) need to implement deparsing
for all the remaining commands.

Here's a new version of this series. The main change is that I've
changed deparse_utility.c to generate JSON, and the code that was in
commands/event_trigger.c to decode that JSON, so that it uses the new
Jsonb API instead. In addition, I've moved the new code that was in
commands/event_trigger.c to utils/adt/ddl_json.c. (The only entry point
of the new file is the SQL-callable pg_event_trigger_expand_command()
function, and its purpose is to expand a JSON object emitted by the
deparse_utility.c code back into a plain text SQL command.)

I have also cleaned up the code per comments from Michael Paquier and
Andres Freund:

* the GRANT support for event triggers now correctly ignores global
objects.

* COMMENT ON .. IS NULL no longer causes a crash

* renameatt() and ExecRenameStmt are consistent in returning the
objSubId as an "int" (not int32). This is what is used as objectSubId
in ObjectAddress, which is what we're using this value for.

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

Attachments:

0014-deparse-Support-CREATE-DOMAIN.patchtext/x-diff; charset=us-asciiDownload
>From 85f5e09d110e9f8f0d5cfdc465510b5b64ddbabc 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 14/26] deparse: Support CREATE DOMAIN

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 7f90e6f..5e25327 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1559,6 +1559,60 @@ 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}D %{not_null}s %{constraints}s %{collation}s",
+								  0);
+
+	append_object_object(createDomain,
+						 "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	append_object_object(createDomain,
+						 "type",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 typForm->typbasetype));
+
+	if (typForm->typnotnull)
+		append_string_object(createDomain, "not_null", "NOT NULL");
+	else
+		append_string_object(createDomain, "not_null", "");
+
+	constraints = obtainConstraints(NIL, InvalidOid, objectId);
+	tmp = new_objtree_VA("%{elements: }s", 0);
+	if (constraints == NIL)
+		append_bool_object(tmp, "present", false);
+	else
+		append_array_object(tmp, "elements", constraints);
+	append_object_object(createDomain, "constraints", tmp);
+
+	tmp = new_objtree_VA("COLLATE %{collation}D", 0);
+	if (OidIsValid(typForm->typcollation))
+		append_object_object(tmp, "collation",
+							 new_objtree_for_qualname_id(CollationRelationId,
+														 typForm->typcollation));
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createDomain, "collation", tmp);
+
+	ReleaseSysCache(typTup);
+
+	return createDomain;
+}
+
 /*
  * Return the given object type as a string.
  */
@@ -2508,7 +2562,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateDomainStmt:
-			command = NULL;
+			command = deparse_CreateDomain(objectId, parsetree);
 			break;
 
 		case T_CreateConversionStmt:
-- 
1.9.1

0015-deparse-Support-CREATE-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From 5d48325fa3022af66b3d352f4849a2bee6a35d1b 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 15/26] deparse: Support CREATE FUNCTION

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 5e25327..07820a0 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"
@@ -1614,6 +1615,316 @@ 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)
+		{
+			pfree(probin);
+			probin = NULL;
+		}
+	}
+
+	if (probin == NULL)
+		definition = "%{definition}L";
+	else
+		definition = "%{objfile}L, %{symbol}L";
+
+	fmt = psprintf("CREATE %%{or_replace}s FUNCTION %%{signature}s "
+				   "RETURNS %%{return_type}s LANGUAGE %%{language}I "
+				   "%%{window}s %%{volatility}s %%{leakproof}s "
+				   "%%{strict}s %%{security_definer}s %%{cost}s %%{rows}s "
+				   "%%{set_options: }s "
+				   "AS %s", definition);
+
+	createFunc = new_objtree_VA(fmt, 1,
+								"or_replace", ObjTypeString,
+								node->replace ? "OR REPLACE" : "");
+
+	sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0);
+
+	/*
+	 * To construct the arguments array, extract the type OIDs from the
+	 * function's pg_proc entry.  If pronargs equals the parameter list length,
+	 * there are no OUT parameters and thus we can extract the type OID from
+	 * proargtypes; otherwise we need to decode proallargtypes, which is
+	 * a bit more involved.
+	 */
+	typarray = palloc(list_length(node->parameters) * sizeof(Oid));
+	if (list_length(node->parameters) > procForm->pronargs)
+	{
+		bool	isnull;
+		Datum	alltypes;
+		Datum  *values;
+		bool   *nulls;
+		int		nelems;
+
+		alltypes = SysCacheGetAttr(PROCOID, procTup,
+								   Anum_pg_proc_proallargtypes, &isnull);
+		if (isnull)
+			elog(ERROR, "NULL proallargtypes, but more parameters than args");
+		deconstruct_array(DatumGetArrayTypeP(alltypes),
+						  OIDOID, 4, 't', 'i',
+						  &values, &nulls, &nelems);
+		if (nelems != list_length(node->parameters))
+			elog(ERROR, "mismatched proallargatypes");
+		for (i = 0; i < list_length(node->parameters); i++)
+			typarray[i] = values[i];
+	}
+	else
+	{
+		for (i = 0; i < list_length(node->parameters); i++)
+			 typarray[i] = procForm->proargtypes.values[i];
+	}
+
+	/*
+	 * If there are any default expressions, we read the deparsed expression as
+	 * a list so that we can attach them to each argument.
+	 */
+	tmpdatum = SysCacheGetAttr(PROCOID, procTup,
+							   Anum_pg_proc_proargdefaults, &isnull);
+	if (!isnull)
+	{
+		defaults = FunctionGetDefaults(DatumGetTextP(tmpdatum));
+		curdef = list_head(defaults);
+	}
+	else
+	{
+		defaults = NIL;
+		curdef = NULL;
+	}
+
+	/*
+	 * Now iterate over each parameter in the parsetree to create the
+	 * parameters array.
+	 */
+	params = NIL;
+	typnum = 0;
+	foreach(cell, node->parameters)
+	{
+		FunctionParameter *param = (FunctionParameter *) lfirst(cell);
+		ObjTree	   *tmp2;
+		ObjTree	   *tmp3;
+
+		/*
+		 * A PARAM_TABLE parameter indicates end of input arguments; the
+		 * following parameters are part of the return type.  We ignore them
+		 * here, but keep track of the current position in the list so that
+		 * we can easily produce the return type below.
+		 */
+		if (param->mode == FUNC_PARAM_TABLE)
+		{
+			table_params = cell;
+			break;
+		}
+
+		/*
+		 * Note that %{name}s is a string here, not an identifier; the reason
+		 * for this is that an absent parameter name must produce an empty
+		 * string, not "", which is what would happen if we were to use
+		 * %{name}I here.  So we add another level of indirection to allow us
+		 * to inject a "present" parameter.
+		 */
+		tmp2 = new_objtree_VA("%{mode}s %{name}s %{type}T %{default}s", 0);
+		append_string_object(tmp2, "mode",
+							 param->mode == FUNC_PARAM_IN ? "IN" :
+							 param->mode == FUNC_PARAM_OUT ? "OUT" :
+							 param->mode == FUNC_PARAM_INOUT ? "INOUT" :
+							 param->mode == FUNC_PARAM_VARIADIC ? "VARIADIC" :
+							 "INVALID MODE");
+
+		/* optional wholesale suppression of "name" occurs here */
+		append_object_object(tmp2, "name",
+							 new_objtree_VA("%{name}I", 2,
+											"name", ObjTypeString,
+											param->name ? param->name : "NULL",
+											"present", ObjTypeBool,
+											param->name ? true : false));
+
+		tmp3 = new_objtree_VA("DEFAULT %{value}s", 0);
+		if (PointerIsValid(param->defexpr))
+		{
+			char *expr;
+
+			if (curdef == NULL)
+				elog(ERROR, "proargdefaults list too short");
+			expr = lfirst(curdef);
+
+			append_string_object(tmp3, "value", expr);
+			curdef = lnext(curdef);
+		}
+		else
+			append_bool_object(tmp3, "present", false);
+		append_object_object(tmp2, "default", tmp3);
+
+		append_object_object(tmp2, "type",
+							 new_objtree_for_type(typarray[typnum++], -1));
+
+		params = lappend(params, new_object_object(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}s", 1,
+										"cost", ObjTypeString,
+										psprintf("%f", procForm->procost)));
+
+	tmp = new_objtree_VA("ROWS %{rows}s", 0);
+	if (procForm->prorows == 0)
+		append_bool_object(tmp, "present", false);
+	else
+		append_string_object(tmp, "rows",
+							 psprintf("%f", procForm->prorows));
+	append_object_object(createFunc, "rows", tmp);
+
+	append_array_object(createFunc, "set_options", NIL);
+
+	if (probin == NULL)
+	{
+		append_string_object(createFunc, "definition",
+							 source);
+	}
+	else
+	{
+		append_string_object(createFunc, "objfile", probin);
+		append_string_object(createFunc, "symbol", source);
+	}
+
+	ReleaseSysCache(langTup);
+	ReleaseSysCache(procTup);
+
+	return createFunc;
+}
+
+/*
  * Return the given object type as a string.
  */
 static const char *
@@ -2526,7 +2837,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 7f79637..e0255c8 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9665,3 +9665,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 46b4d98..5f1f1e8 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -41,6 +41,7 @@ extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
 extern List *select_rtable_names_for_explain(List *rtable,
 								Bitmapset *rels_used);
 extern char *generate_collation_name(Oid collid);
+extern List *FunctionGetDefaults(text *proargdefaults);
 
 extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
 						 List *dpcontext);
-- 
1.9.1

0016-deparse-core-have-ALTER-TABLE-return-OIDs-and-col-of.patchtext/x-diff; charset=us-asciiDownload
>From fb87148bbe848a179f6be717e868c798cb718c8c 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 16/26] deparse/core: have ALTER TABLE return OIDs and col# of
 affected objs

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

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c0eade0..0bdc000 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -96,7 +96,7 @@ static Oid AddNewRelationType(const char *typeName,
 				   Oid new_row_type,
 				   Oid new_array_type);
 static void RelationRemoveInheritance(Oid relid);
-static void StoreRelCheck(Relation rel, char *ccname, Node *expr,
+static Oid StoreRelCheck(Relation rel, char *ccname, Node *expr,
 			  bool is_validated, bool is_local, int inhcount,
 			  bool is_no_inherit, bool is_internal);
 static void StoreConstraints(Relation rel, List *cooked_constraints,
@@ -1843,7 +1843,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)
 {
@@ -1948,6 +1948,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	 */
 	InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
 								  RelationGetRelid(rel), attnum, is_internal);
+
+	return attrdefOid;
 }
 
 /*
@@ -1955,8 +1957,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)
@@ -1966,6 +1970,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
+	Oid			constrOid;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2017,7 +2022,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 */
@@ -2048,11 +2053,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
@@ -2064,7 +2073,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 */
 
 	/*
@@ -2081,12 +2090,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:
@@ -2174,6 +2185,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,
@@ -2194,10 +2206,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;
@@ -2217,6 +2230,7 @@ AddRelationNewConstraints(Relation rel,
 	{
 		Constraint *cdef = (Constraint *) lfirst(cell);
 		char	   *ccname;
+		Oid			constrOid;
 
 		if (cdef->contype != CONSTR_CHECK)
 			continue;
@@ -2319,13 +2333,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 ee10594..b424f73 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1096,7 +1096,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
@@ -1113,7 +1114,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,
@@ -1301,6 +1302,8 @@ index_constraint_create(Relation heapRelation,
 		heap_freetuple(indexTuple);
 		heap_close(pg_index, RowExclusiveLock);
 	}
+
+	return conOid;
 }
 
 /*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 041f5ad..f04de42 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -40,6 +40,8 @@
  * Subsidiary records (such as triggers or indexes to implement the
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
+ *
+ * The new constraint's OID is returned.
  */
 Oid
 CreateConstraintEntry(const char *constraintName,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0e4379a..4855c24 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -46,6 +46,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -280,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);
@@ -322,26 +323,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);
@@ -349,20 +350,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,
@@ -373,9 +374,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);
@@ -388,7 +389,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);
@@ -621,6 +622,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 +1734,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;
@@ -3390,78 +3393,91 @@ static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
+	AttrNumber colno = InvalidAttrNumber;
+	Oid			newoid = InvalidOid;
+
 	switch (cmd->subtype)
 	{
 		case AT_AddColumn:		/* ADD COLUMN */
 		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
 										 * VIEW */
-			ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
-							false, false, false, lockmode);
+			colno = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+									false, false, false, lockmode);
 			break;
 		case AT_AddColumnRecurse:
-			ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
-							false, true, false, lockmode);
+			colno = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+									false, true, false, lockmode);
 			break;
 		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
-			ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
-			ATExecDropNotNull(rel, cmd->name, lockmode);
+			colno = ATExecDropNotNull(rel, cmd->name, lockmode);
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
-			ATExecSetNotNull(tab, rel, cmd->name, lockmode);
+			colno = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
 			break;
 		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
-			ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
-			ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
+			colno = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
 			break;
 		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
-			ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
+			colno = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
 			break;
 		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
-			ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
 			ATExecDropColumn(wqueue, rel, cmd->name,
-					 cmd->behavior, false, false, cmd->missing_ok, lockmode);
+							 cmd->behavior, false, false,
+							 cmd->missing_ok, lockmode);
 			break;
 		case AT_DropColumnRecurse:		/* DROP COLUMN with recursion */
 			ATExecDropColumn(wqueue, rel, cmd->name,
-					  cmd->behavior, true, false, cmd->missing_ok, lockmode);
+							 cmd->behavior, true, false,
+							 cmd->missing_ok, lockmode);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
-			ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false, lockmode);
+			newoid = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
+									lockmode);
 			break;
 		case AT_ReAddIndex:		/* ADD INDEX */
-			ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, lockmode);
+			newoid = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
+									lockmode);
 			break;
 		case AT_AddConstraint:	/* ADD CONSTRAINT */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								false, false, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									false, false, lockmode);
 			break;
 		case AT_AddConstraintRecurse:	/* ADD CONSTRAINT with recursion */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								true, false, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									true, false, lockmode);
 			break;
 		case AT_ReAddConstraint:		/* Re-add pre-existing check
 										 * constraint */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								false, true, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									false, true, lockmode);
 			break;
 		case AT_AddIndexConstraint:		/* ADD CONSTRAINT USING INDEX */
-			ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, lockmode);
+			newoid = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
+											  lockmode);
 			break;
 		case AT_AlterConstraint:		/* ALTER CONSTRAINT */
-			ATExecAlterConstraint(rel, cmd, false, false, lockmode);
+			newoid = ATExecAlterConstraint(rel, cmd, false, false, lockmode);
 			break;
 		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
-			ATExecValidateConstraint(rel, cmd->name, false, false, lockmode);
+			newoid = ATExecValidateConstraint(rel, cmd->name, false, false,
+											  lockmode);
 			break;
 		case AT_ValidateConstraintRecurse:		/* VALIDATE CONSTRAINT with
 												 * recursion */
-			ATExecValidateConstraint(rel, cmd->name, true, false, lockmode);
+			newoid = ATExecValidateConstraint(rel, cmd->name, true, false,
+											  lockmode);
 			break;
 		case AT_DropConstraint:	/* DROP CONSTRAINT */
 			ATExecDropConstraint(rel, cmd->name, cmd->behavior,
@@ -3474,10 +3490,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),
@@ -3485,7 +3503,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);
@@ -3586,7 +3604,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			ATExecDropOf(rel, lockmode);
 			break;
 		case AT_ReplicaIdentity:
-			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def,
+								  lockmode);
 			break;
 		case AT_EnableRowSecurity:
 			ATExecEnableRowSecurity(rel);
@@ -4581,7 +4600,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)
@@ -4666,7 +4685,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					  colDef->colname, RelationGetRelationName(rel))));
 
 			heap_close(attrdesc, RowExclusiveLock);
-			return;
+			return InvalidAttrNumber;
 		}
 	}
 
@@ -4912,6 +4931,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 		heap_close(childrel, NoLock);
 	}
+
+	return newattnum;
 }
 
 /*
@@ -5026,7 +5047,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;
@@ -5111,18 +5132,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)
 {
@@ -5166,18 +5191,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)
 {
@@ -5228,6 +5257,8 @@ ATExecColumnDefault(Relation rel, const char *colName,
 		AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
 								  false, true, false);
 	}
+
+	return attnum;
 }
 
 /*
@@ -5257,13 +5288,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);
@@ -5298,7 +5330,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\"",
@@ -5317,9 +5350,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)
 {
@@ -5327,6 +5362,7 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	HeapTuple	tuple,
 				newtuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 	Datum		datum,
 				newOptions;
 	bool		isnull;
@@ -5345,7 +5381,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\"",
@@ -5384,12 +5421,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;
@@ -5397,6 +5436,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);
@@ -5429,7 +5469,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\"",
@@ -5459,6 +5500,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return attnum;
 }
 
 
@@ -5684,7 +5727,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
  * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
  * us schedule creation of the index at the appropriate time during ALTER.
  */
-static void
+static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
@@ -5726,12 +5769,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)
 {
@@ -5741,6 +5786,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	IndexInfo  *indexInfo;
 	char	   *constraintName;
 	char		constraintType;
+	Oid			conOid;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(OidIsValid(index_oid));
@@ -5785,30 +5831,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));
 
 	/*
@@ -5819,9 +5869,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:
@@ -5852,17 +5903,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.
  *
@@ -5881,7 +5937,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)
@@ -5890,6 +5946,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)
@@ -5932,6 +5989,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 */
@@ -5947,7 +6011,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.
@@ -5955,7 +6019,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
@@ -5993,16 +6057,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)
 {
@@ -6401,6 +6468,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Close pk table, but keep lock until we've committed.
 	 */
 	heap_close(pkrel, NoLock);
+
+	return constrOid;
 }
 
 /*
@@ -6413,7 +6482,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)
 {
@@ -6424,6 +6493,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;
@@ -6483,6 +6553,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);
 
@@ -6530,6 +6602,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 /*
@@ -6540,7 +6614,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)
 {
@@ -6550,6 +6624,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);
 
@@ -6591,9 +6666,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;
 
 			/*
@@ -6608,7 +6684,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 conid);
+										 constrOid);
 			heap_close(refrel, NoLock);
 
 			/*
@@ -6689,6 +6765,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 
@@ -7775,7 +7853,7 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
 	}
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
@@ -8152,9 +8230,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 	/* Cleanup */
 	heap_freetuple(heapTup);
+
+	return attnum;
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnGenericOptions(Relation rel,
 								const char *colName,
 								List *options,
@@ -8173,9 +8253,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);
@@ -8202,7 +8283,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)));
@@ -8251,6 +8333,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	heap_close(attrel, RowExclusiveLock);
 
 	heap_freetuple(newtuple);
+
+	return attnum;
 }
 
 /*
@@ -8892,7 +8976,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;
@@ -8910,6 +8994,8 @@ ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 
 	/* And do the work */
 	mark_index_clustered(rel, indexOid, false);
+
+	return indexOid;
 }
 
 /*
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index e690e27..075c238 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -27,6 +27,7 @@ typedef struct RawColumnDefault
 typedef struct CookedConstraint
 {
 	ConstrType	contype;		/* CONSTR_DEFAULT or CONSTR_CHECK */
+	Oid			conoid;			/* OID of the new element */
 	char	   *name;			/* name, or NULL if none */
 	AttrNumber	attnum;			/* which attr (only for DEFAULT) */
 	Node	   *expr;			/* transformed default or check expr */
@@ -99,7 +100,7 @@ extern List *AddRelationNewConstraints(Relation rel,
 						  bool is_local,
 						  bool is_internal);
 
-extern void StoreAttrDefault(Relation rel, AttrNumber attnum,
+extern Oid StoreAttrDefault(Relation rel, AttrNumber attnum,
 				 Node *expr, bool is_internal);
 
 extern Node *cookDefault(ParseState *pstate,
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 006b180..acde583 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -62,7 +62,7 @@ extern Oid index_create(Relation heapRelation,
 			 bool concurrent,
 			 bool is_internal);
 
-extern void index_constraint_create(Relation heapRelation,
+extern Oid index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
 						const char *constraintName,
-- 
1.9.1

0017-deparse-Support-ALTER-TABLE.patchtext/x-diff; charset=us-asciiDownload
>From d56122ce643cc28e4970e9800d48233a7e7aaf9b 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 17/26] deparse: Support ALTER TABLE

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

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 48ba3ec..b66301f 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -52,6 +52,7 @@ typedef struct EventTriggerQueryState
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
 	MemoryContext cxt;
+	StashedCommand *curcmd;
 	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
@@ -1073,6 +1074,7 @@ EventTriggerBeginCompleteQuery(void)
 	state->cxt = cxt;
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
+	state->curcmd = NULL;
 	state->stash = NIL;
 
 	state->previous = currentEventTriggerState;
@@ -1360,6 +1362,113 @@ 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;
+	/* XXX is it necessary to have the whole parsetree? probably not ... */
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->curcmd = stashed;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+void
+EventTriggerComplexCmdSetOid(Oid objectId)
+{
+	currentEventTriggerState->curcmd->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerRecordSubcmd
+ * 		Save data about a single part of a complex DDL command
+ *
+ * Right now we only support ALTER TABLE; there are no other DDL commands that
+ * require this.  (ALTER TYPE can also generate multiple subcommands, but it's
+ * actually parsed as ALTER TABLE, so there is no difference at this level.)
+ */
+void
+EventTriggerRecordSubcmd(Node *subcmd, Oid relid, AttrNumber attnum,
+						 Oid newoid)
+{
+	MemoryContext	oldcxt;
+	StashedATSubcmd *newsub;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(OidIsValid(currentEventTriggerState->curcmd->d.alterTable.objectId));
+
+	/*
+	 * If we receive a subcommand intended for a relation other than the one
+	 * we've started the complex command for, ignore it.  This is chiefly
+	 * concerned with inheritance situations: in such cases, alter table
+	 * would dispatch multiple copies of the same command for various things,
+	 * but we're only concerned with the one for the main table.
+	 */
+	if (relid != currentEventTriggerState->curcmd->d.alterTable.objectId)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(StashedATSubcmd));
+	newsub->attnum = attnum;
+	newsub->oid = newoid;
+	newsub->parsetree = copyObject(subcmd);
+
+	currentEventTriggerState->curcmd->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->curcmd->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerEndRecordingSubcmds
+ * 		Finish up saving a complex DDL command
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerComplexCmdEnd(void)
+{
+	/* If no subcommands, don't stash anything */
+	if (list_length(currentEventTriggerState->curcmd->d.alterTable.subcmds) != 0)
+	{
+		currentEventTriggerState->stash =
+			lappend(currentEventTriggerState->stash,
+					currentEventTriggerState->curcmd);
+	}
+	else
+		pfree(currentEventTriggerState->curcmd);
+
+	currentEventTriggerState->curcmd = NULL;
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1439,7 +1548,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;
@@ -1455,6 +1565,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 4855c24..b7eea2e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2757,6 +2757,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerComplexCmdSetOid(relid);
+
 	ATController(rel, cmds, recurse, lockmode);
 }
 
@@ -3622,6 +3624,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
@@ -5726,6 +5731,8 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
  * There is no such command in the grammar, but parse_utilcmd.c converts
  * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
  * us schedule creation of the index at the appropriate time during ALTER.
+ *
+ * Return value is the OID of the new index.
  */
 static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
@@ -9565,7 +9572,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 07820a0..013ab51 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2719,6 +2719,389 @@ 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;
+
+	Assert(cmd->type == SCT_AlterTable);
+
+	rel = heap_open(cmd->d.alterTable.objectId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									cmd->d.alterTable.objectId);
+
+	alterTableStmt =
+		new_objtree_VA("ALTER TABLE %{identity}D %{subcmds:, }s", 0);
+	tmp = new_objtree_for_qualname(rel->rd_rel->relnamespace,
+								   RelationGetRelationName(rel));
+	append_object_object(alterTableStmt, "identity", tmp);
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		StashedATSubcmd	*substashed = (StashedATSubcmd *) lfirst(cell);
+		AlterTableCmd	*subcmd = (AlterTableCmd *) substashed->parsetree;
+		ObjTree	   *tree;
+
+		Assert(IsA(subcmd, AlterTableCmd));
+
+		switch (subcmd->subtype)
+		{
+			case AT_AddColumn:
+			case AT_AddColumnRecurse:
+				/* XXX need to set the "recurse" bit somewhere? */
+				Assert(IsA(subcmd->def, ColumnDef));
+				tree = deparse_ColumnDef(rel, dpcontext, false,
+										 (ColumnDef *) subcmd->def);
+				tmp = new_objtree_VA("ADD COLUMN %{definition}s",
+									 2, "type", ObjTypeString, "add column",
+									 "definition", ObjTypeObject, tree);
+				subcmds = lappend(subcmds, new_object_object(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:
+				/* not yet */
+				break;
+
+			case AT_SetOptions:
+				/* not yet */
+				break;
+
+			case AT_ResetOptions:
+				/* not yet */
+				break;
+
+			case AT_SetStorage:
+				Assert(IsA(subcmd->def, String));
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STORAGE %{storage}s",
+									 3, "type", ObjTypeString, "set storage",
+									 "column", ObjTypeString, subcmd->name,
+									 "storage", ObjTypeString,
+									 strVal((Value *) subcmd->def));
+				subcmds = lappend(subcmds, new_object_object(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:
+				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:
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DATA TYPE %{datatype}T collate_clause using_clause",
+									 2, "type", ObjTypeString, "alter column type",
+									 "column", ObjTypeString, subcmd->name);
+				/* FIXME figure out correct typid/typmod , collate clause, using_clause */
+				append_object_object(tmp, "datatype",
+									 new_objtree_for_type(INT4OID, -1));
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				break;
+
+			case AT_ChangeOwner:
+				tmp = new_objtree_VA("OWNER TO %{owner}I",
+									 2, "type", ObjTypeString, "change owner",
+									 "owner",  ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(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_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:
+				break;
+
+			case AT_ResetRelOptions:
+				break;
+
+				/*
+				 * FIXME --- should we unify representation of all these
+				 * ENABLE/DISABLE TRIGGER commands??
+				 */
+			case AT_EnableTrig:
+				tmp = new_objtree_VA("ENABLE TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "enable trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(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:
+				break;
+
+			case AT_EnableAlwaysRule:
+				break;
+
+			case AT_EnableReplicaRule:
+				break;
+
+			case AT_DisableRule:
+				break;
+
+			case AT_AddInherit:
+				/*
+				 * XXX this case is interesting: we cannot rely on parse node
+				 * because parent name might be unqualified; but there's no way
+				 * to extract it from catalog either, since we don't know which
+				 * of the parents is the new one.
+				 */
+				break;
+
+			case AT_DropInherit:
+				/* XXX ditto ... */
+				break;
+
+			case AT_AddOf:
+				break;
+
+			case AT_DropOf:
+				break;
+
+			case AT_ReplicaIdentity:
+				tmp = new_objtree_VA("REPLICA IDENTITY %{ident}s", 1,
+									 "type", ObjTypeString, "replica identity");
+				switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type)
+				{
+					case REPLICA_IDENTITY_DEFAULT:
+						append_string_object(tmp, "ident", "DEFAULT");
+						break;
+					case REPLICA_IDENTITY_FULL:
+						append_string_object(tmp, "ident", "FULL");
+						break;
+					case REPLICA_IDENTITY_NOTHING:
+						append_string_object(tmp, "ident", "NOTHING");
+						break;
+					case REPLICA_IDENTITY_INDEX:
+						tmp2 = new_objtree_VA("USING INDEX %{index}I", 1,
+											  "index", ObjTypeString,
+											  ((ReplicaIdentityStmt *) subcmd->def)->name);
+						append_object_object(tmp, "ident", tmp2);
+						break;
+				}
+				subcmds = lappend(subcmds, new_object_object(tmp));
+				break;
+
+			case AT_GenericOptions:
+				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.
  *
@@ -3005,6 +3388,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 6732715..38ee200 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1007,6 +1007,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)
 						{
@@ -1020,19 +1024,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,
@@ -1189,6 +1206,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,
@@ -1199,6 +1217,7 @@ ProcessUtilitySlow(Node *parsetree,
 									false); /* quiet */
 					EventTriggerStashCommand(objectId, 0, OBJECT_INDEX,
 											 parsetree);
+					EventTriggerComplexCmdEnd();
 				}
 				break;
 
@@ -1287,8 +1306,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, 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 da7f449..2a23edd 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -55,5 +55,10 @@ extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
 
 extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
 						 ObjectType objtype, Node *parsetree);
+extern void EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype);
+extern void EventTriggerComplexCmdSetOid(Oid objectId);
+extern void EventTriggerRecordSubcmd(Node *subcmd, Oid relid,
+						 AttrNumber attnum, Oid newoid);
+extern void EventTriggerComplexCmdEnd(void);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 1278df9..910c716 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;
 
 /*
@@ -52,6 +53,13 @@ typedef struct StashedCommand
 			uint32		objectSubId;
 			ObjectType	objtype;
 		} simple;
+
+		struct AlterTableCommand
+		{
+			Oid		objectId;
+			ObjectType objtype;
+			List   *subcmds;
+		} alterTable;
 	} d;
 } StashedCommand;
 
-- 
1.9.1

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

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 013ab51..5c62e15 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -644,6 +644,38 @@ 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)
+{
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	Relation	relation;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA("CREATE %{persistence}s VIEW %{identity}D AS %{query}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(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)
  *
@@ -3216,7 +3248,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 e0255c8..f47b90e 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 5f1f1e8..9c67b9a 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);
-- 
1.9.1

0019-deparse-Support-CREATE-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From d91345cdf40d111989ba5a99dfb874ef6715a566 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 19/26] 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 5c62e15..dee4250 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"
@@ -2752,6 +2753,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;
@@ -3304,7 +3340,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateOpFamilyStmt:
-			command = NULL;
+			command = deparse_CreateOpFamily(objectId, parsetree);
 			break;
 
 		case T_AlterOpFamilyStmt:
-- 
1.9.1

0020-deparse-Support-CREATE-CONVERSION.patchtext/x-diff; charset=us-asciiDownload
>From 84d144b9cbcd88ea29119749655a4d81b2dc2569 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 20/26] 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 dee4250..28494d7 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"
@@ -2753,6 +2755,39 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+	HeapTuple   conTup;
+	Form_pg_conversion conForm;
+	ObjTree	   *ccStmt;
+
+	conTup = SearchSysCache1(CONDEFAULT, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(conTup))
+		elog(ERROR, "cache lookup failed for conversion with OID %u", objectId);
+	conForm = (Form_pg_conversion) GETSTRUCT(conTup);
+
+	ccStmt = new_objtree_VA("CREATE %{default}s CONVERSION %{identity}D FOR "
+							"%{source}L TO %{dest}L FROM %{function}D", 0);
+
+	append_string_object(ccStmt, "default",
+						 conForm->condefault ? "DEFAULT" : "");
+	append_object_object(ccStmt, "identity",
+						 new_objtree_for_qualname(conForm->connamespace,
+												  NameStr(conForm->conname)));
+	append_string_object(ccStmt, "source", (char *)
+						 pg_encoding_to_char(conForm->conforencoding));
+	append_string_object(ccStmt, "dest", (char *)
+						 pg_encoding_to_char(conForm->contoencoding));
+	append_object_object(ccStmt, "function",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 conForm->conproc));
+
+	ReleaseSysCache(conTup);
+
+	return ccStmt;
+}
+
+static ObjTree *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -3328,7 +3363,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateConversionStmt:
-			command = NULL;
+			command = deparse_CreateConversion(objectId, parsetree);
 			break;
 
 		case T_CreateCastStmt:
-- 
1.9.1

0021-deparse-Support-DefineStmt-commands.patchtext/x-diff; charset=utf-8Download
>From e0b10565bac1dcfeffc2411c166b9b662ec36e74 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 21/26] 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 | 852 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 851 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 28494d7..d5ce0e7 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"
@@ -571,6 +576,851 @@ 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}s", 1,
+							 "space", ObjTypeString,
+							 psprintf("%d", 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("SSPACE=%{space}s", 1,
+							 "space", ObjTypeString,
+							 psprintf("%d", 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}O", 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}O", 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}O", 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 */
+	tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}s", 0);
+	if (typForm->typlen == -1)
+		append_string_object(tmp, "typlen", "VARIABLE");
+	else
+		append_string_object(tmp, "typlen",
+							 psprintf("%d", typForm->typlen));
+	list = lappend(list, new_object_object(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
@@ -3250,7 +4100,7 @@ deparse_simple_command(StashedCommand *cmd)
 
 			/* other local objects */
 		case T_DefineStmt:
-			command = NULL;
+			command = deparse_DefineStmt(objectId, parsetree);
 			break;
 
 		case T_IndexStmt:
-- 
1.9.1

0001-deparse-core-have-RENAME-return-attribute-number.patchtext/x-diff; charset=us-asciiDownload
>From d03a93c130e0b8c8e13e3945d60629cdd5772409 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 01/26] deparse/core: have RENAME return attribute number

---
 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 c9a9baf..298edad 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)
 	{
@@ -330,7 +333,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 ecdff1e..2e289a4 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 4a2a339..ab223ad 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -803,7 +803,7 @@ standard_ProcessUtility(Node *parsetree,
 									   context, params,
 									   dest, completionTag);
 				else
-					ExecRenameStmt(stmt);
+					ExecRenameStmt(stmt, NULL);
 			}
 			break;
 
@@ -1302,7 +1302,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 5907184..b8615ab 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 932322f..213bb29 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);
 
-- 
1.9.1

0002-deparse-core-event-triggers-support-GRANT-REVOKE.patchtext/x-diff; charset=us-asciiDownload
>From d53070c5b5192457b383757733de4756355d65b2 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 02/26] deparse/core: event triggers support GRANT/REVOKE

---
 doc/src/sgml/event-trigger.sgml      | 15 ++++++++++++++-
 src/backend/commands/event_trigger.c | 30 ++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           | 21 ++++++++++++++++-----
 src/include/commands/event_trigger.h |  1 +
 4 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 6f71a27..49b8384 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
@@ -622,12 +623,24 @@
         <entry align="center"><literal>X</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>
+       </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>
         <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>
+       </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 1b8c94b..6abd9fa 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -251,6 +251,8 @@ check_ddl_tag(const char *tag)
 		pg_strcasecmp(tag, "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;
@@ -1013,6 +1015,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 ab223ad..559a3d3 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -547,11 +547,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);
@@ -781,6 +776,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;
@@ -1313,6 +1320,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 0233f4c..1348997 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -43,6 +43,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);
-- 
1.9.1

0003-deparse-core-event-triggers-support-COMMENT.patchtext/x-diff; charset=us-asciiDownload
>From 5e6217a0ad2a9d5e5ec80a8a7859e865b696d76c 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 03/26] deparse/core: event triggers support COMMENT

---
 doc/src/sgml/event-trigger.sgml      |  8 +++++++-
 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, 30 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 49b8384..39ecd94 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
@@ -281,6 +281,12 @@
         <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>
+       </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 34f6cae..59781e5 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 6abd9fa..964a010 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -251,6 +251,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 559a3d3..3564732 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -512,10 +512,6 @@ standard_ProcessUtility(Node *parsetree,
 			ExecuteTruncate((TruncateStmt *) parsetree);
 			break;
 
-		case T_CommentStmt:
-			CommentObject((CommentStmt *) parsetree);
-			break;
-
 		case T_SecLabelStmt:
 			ExecSecLabelStmt((SecLabelStmt *) parsetree);
 			break;
@@ -840,6 +836,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,
@@ -1320,6 +1329,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 05fe0c6..46a6fa9 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);
 
-- 
1.9.1

0004-deparse-infrastructure-needed-for-command-deparsing.patchtext/x-diff; charset=us-asciiDownload
>From e76d174a47f2e549053968abe89e599eb805116b 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 04/26] deparse: infrastructure needed for command deparsing

---
 src/backend/catalog/objectaddress.c  | 115 +++++
 src/backend/commands/event_trigger.c | 217 ++++++++-
 src/backend/tcop/Makefile            |   2 +-
 src/backend/tcop/deparse_utility.c   | 856 +++++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           |   2 +
 src/backend/utils/adt/Makefile       |   2 +-
 src/backend/utils/adt/ddl_json.c     | 600 ++++++++++++++++++++++++
 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        |   4 +
 src/include/commands/event_trigger.h |   3 +
 src/include/commands/extension.h     |   2 +-
 src/include/nodes/parsenodes.h       |   2 +
 src/include/tcop/deparse_utility.h   |  60 +++
 src/include/utils/builtins.h         |   7 +
 16 files changed, 1977 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/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b69b75b..a2445f1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -723,6 +723,121 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 }
 
 /*
+ * Return the OID of the catalog corresponding to the given object type
+ */
+Oid
+get_objtype_catalog_oid(ObjectType objtype)
+{
+	Oid		catalog_id;
+
+	switch (objtype)
+	{
+		case OBJECT_INDEX:
+		case OBJECT_COMPOSITE:
+		case OBJECT_SEQUENCE:
+		case OBJECT_TABLE:
+		case OBJECT_VIEW:
+		case OBJECT_MATVIEW:
+		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_COLUMN:
+			catalog_id = RelationRelationId;
+			break;
+		case OBJECT_RULE:
+			catalog_id = RewriteRelationId;
+			break;
+		case OBJECT_TRIGGER:
+			catalog_id = TriggerRelationId;
+			break;
+		case OBJECT_CONSTRAINT:
+			catalog_id = ConstraintRelationId;
+			break;
+		case OBJECT_DATABASE:
+			catalog_id = DatabaseRelationId;
+			break;
+		case OBJECT_EXTENSION:
+			catalog_id = ExtensionRelationId;
+			break;
+		case OBJECT_TABLESPACE:
+			catalog_id = TableSpaceRelationId;
+			break;
+		case OBJECT_ROLE:
+			catalog_id = AuthIdRelationId;
+			break;
+		case OBJECT_SCHEMA:
+			catalog_id = NamespaceRelationId;
+			break;
+		case OBJECT_LANGUAGE:
+			catalog_id = LanguageRelationId;
+			break;
+		case OBJECT_FDW:
+			catalog_id = ForeignDataWrapperRelationId;
+			break;
+		case OBJECT_FOREIGN_SERVER:
+			catalog_id = ForeignServerRelationId;
+			break;
+		case OBJECT_USER_MAPPING:
+			catalog_id = UserMappingRelationId;
+			break;
+		case OBJECT_EVENT_TRIGGER:
+			catalog_id = EventTriggerRelationId;
+			break;
+		case OBJECT_TYPE:
+		case OBJECT_DOMAIN:
+			catalog_id = TypeRelationId;
+			break;
+		case OBJECT_ATTRIBUTE:
+			catalog_id = TypeRelationId;	/* XXX? */
+			break;
+		case OBJECT_AGGREGATE:
+			catalog_id = ProcedureRelationId;
+			break;
+		case OBJECT_FUNCTION:
+			catalog_id = ProcedureRelationId;
+			break;
+		case OBJECT_OPERATOR:
+			catalog_id = OperatorRelationId;
+			break;
+		case OBJECT_COLLATION:
+			catalog_id = CollationRelationId;
+			break;
+		case OBJECT_CONVERSION:
+			catalog_id = ConversionRelationId;
+			break;
+		case OBJECT_OPCLASS:
+			catalog_id = OperatorClassRelationId;
+			break;
+		case OBJECT_OPFAMILY:
+			catalog_id = OperatorFamilyRelationId;
+			break;
+		case OBJECT_LARGEOBJECT:
+			catalog_id = LargeObjectRelationId;
+			break;
+		case OBJECT_CAST:
+			catalog_id = CastRelationId;
+			break;
+		case OBJECT_TSPARSER:
+			catalog_id = TSParserRelationId;
+			break;
+		case OBJECT_TSDICTIONARY:
+			catalog_id = TSDictionaryRelationId;
+			break;
+		case OBJECT_TSTEMPLATE:
+			catalog_id = TSTemplateRelationId;
+			break;
+		case OBJECT_TSCONFIGURATION:
+			catalog_id = TSConfigRelationId;
+			break;
+		default:
+				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
+				/* placate compiler, in case it thinks elog might return */
+				catalog_id = InvalidOid;
+	}
+
+	/* Return the object address and the relation. */
+	return catalog_id;
+}
+
+/*
  * Find an ObjectAddress for a type of object that is identified by an
  * unqualified name.
  */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 964a010..48ba3ec 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"
@@ -48,6 +52,7 @@ typedef struct EventTriggerQueryState
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
 	MemoryContext cxt;
+	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -66,6 +71,7 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
 	{"AGGREGATE", true},
 	{"CAST", true},
@@ -924,6 +930,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
 		case OBJECT_COLUMN:
+		case OBJECT_COMPOSITE:
 		case OBJECT_CONSTRAINT:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
@@ -951,6 +958,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
+		case OBJECT_USER_MAPPING:
 		case OBJECT_VIEW:
 			return true;
 	}
@@ -1056,13 +1064,6 @@ EventTriggerBeginCompleteQuery(void)
 	EventTriggerQueryState *state;
 	MemoryContext cxt;
 
-	/*
-	 * Currently, sql_drop events are the only reason to have event trigger
-	 * state at all; so if there are none, don't install one.
-	 */
-	if (!trackDroppedObjectsNeeded())
-		return false;
-
 	cxt = AllocSetContextCreate(TopMemoryContext,
 								"event trigger state",
 								ALLOCSET_DEFAULT_MINSIZE,
@@ -1072,6 +1073,7 @@ EventTriggerBeginCompleteQuery(void)
 	state->cxt = cxt;
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
+	state->stash = NIL;
 
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
@@ -1328,3 +1330,204 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
+ * EventTriggerStashCommand
+ * 		Save data about a simple DDL command that was just executed
+ */
+void
+EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype,
+						 Node *parsetree)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_Simple;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.simple.objectId = objectId;
+	stashed->d.simple.objtype = objtype;
+	stashed->d.simple.objectSubId = objectSubId;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+Datum
+pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_get_creation_commands()")));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->stash)
+	{
+		StashedCommand *cmd = lfirst(lc);
+		char	   *command;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid; in those cases, return an empty
+		 * command instead of trying to soldier on.
+		 *
+		 * 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..8ef1758
--- /dev/null
+++ b/src/backend/tcop/deparse_utility.c
@@ -0,0 +1,856 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjTree
+{
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+
+	union
+	{
+		bool		boolean;
+		char	   *string;
+		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 void append_null_object(ObjTree *tree, char *name);
+static void append_bool_object(ObjTree *tree, char *name, bool value);
+static void append_string_object(ObjTree *tree, char *name, char *value);
+static void append_object_object(ObjTree *tree, char *name, ObjTree *value);
+static void append_array_object(ObjTree *tree, char *name, List *array);
+static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
+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, type and value must be supplied.  Note we don't have the luxury of
+ * sprintf-like compiler warnings for malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree();
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		ObjTree    *value;
+		ObjType		type;
+		ObjElem	   *elem;
+		char	   *name;
+		char	   *strval;
+		bool		boolval;
+		List	   *list;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/* Null params don't have a value (obviously) */
+		if (type == ObjTypeNull)
+		{
+			append_null_object(tree, name);
+			continue;
+		}
+
+		/*
+		 * For all other param types there must be a value in the varargs.
+		 * Fetch it and add the fully formed subobject into the main object.
+		 */
+		switch (type)
+		{
+			case ObjTypeBool:
+				boolval = va_arg(args, int);
+				elem = new_bool_object(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;
+			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);
+}
+
+/* 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 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)));
+
+	pfree(catobj);
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function contains a large switch that mirrors that in
+ * ProcessUtilitySlow.  All cases covered there should also be covered here.
+ */
+static ObjTree *
+deparse_simple_command(StashedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+	ObjTree	   *command;
+
+	Assert(cmd->type == SCT_Simple);
+
+	parsetree = cmd->parsetree;
+	objectId = cmd->d.simple.objectId;
+
+	/* This switch needs to handle everything that ProcessUtilitySlow does */
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			command = NULL;
+			break;
+
+		case T_CreateStmt:
+			command = NULL;
+			break;
+
+		case T_CreateForeignTableStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTableStmt:
+		case T_AlterTableMoveAllStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterDomainStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+			/* other local objects */
+		case T_DefineStmt:
+			command = NULL;
+			break;
+
+		case T_IndexStmt:
+			command = NULL;
+			break;
+
+		case T_CreateExtensionStmt:
+			command = NULL;
+			break;
+
+		case T_AlterExtensionStmt:
+			command = NULL;
+			break;
+
+		case T_AlterExtensionContentsStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropUserMappingStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_ImportForeignSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
+			command = NULL;
+			break;
+
+		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
+			command = NULL;
+			break;
+
+		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+			command = NULL;
+			break;
+
+		case T_AlterEnumStmt:
+			command = NULL;
+			break;
+
+		case T_ViewStmt:		/* CREATE VIEW */
+			command = NULL;
+			break;
+
+		case T_CreateFunctionStmt:
+			command = NULL;
+			break;
+
+		case T_AlterFunctionStmt:
+			command = NULL;
+			break;
+
+		case T_RuleStmt:
+			command = NULL;
+			break;
+
+		case T_CreateSeqStmt:
+			command = NULL;
+			break;
+
+		case T_AlterSeqStmt:
+			command = NULL;
+			break;
+
+		case T_CreateTableAsStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_RefreshMatViewStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateTrigStmt:
+			command = NULL;
+			break;
+
+		case T_CreatePLangStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateDomainStmt:
+			command = NULL;
+			break;
+
+		case T_CreateConversionStmt:
+			command = NULL;
+			break;
+
+		case T_CreateCastStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpClassStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpFamilyStmt:
+			command = NULL;
+			break;
+
+		case T_AlterOpFamilyStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSDictionaryStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSConfigurationStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_RenameStmt:
+			command = NULL;
+			break;
+
+		case T_AlterObjectSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterOwnerStmt:
+			command = NULL;
+			break;
+
+		case T_CommentStmt:
+			command = NULL;
+			break;
+
+		case T_GrantStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropOwnedStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_AlterDefaultPrivilegesStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePolicyStmt:	/* CREATE POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterPolicyStmt:		/* ALTER POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		default:
+			command = NULL;
+			elog(LOG, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return command;
+}
+
+/*
+ * Given a utility command parsetree and the OID of the corresponding object,
+ * return a JSON representation of the command.
+ *
+ * The command is expanded fully, so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+char *
+deparse_utility_command(StashedCommand *cmd)
+{
+	OverrideSearchPath *overridePath;
+	MemoryContext	oldcxt;
+	MemoryContext	tmpcxt;
+	ObjTree		   *tree;
+	char		   *command;
+	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 3564732..3d046fc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -862,6 +862,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 7b4391b..5a75b96 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..5b51572
--- /dev/null
+++ b/src/backend/utils/adt/ddl_json.c
@@ -0,0 +1,600 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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,
+	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);
+static void expand_jsonb_array(StringInfo out, char *param,
+				   JsonbValue *jsonarr, char *arraysep,
+				   convSpecifier specifier);
+
+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;
+			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);
+		else
+			expand_one_jsonb_element(out, param, value, specifier);
+	}
+}
+
+/*
+ * 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 = jsonval->val.string.val;
+
+	/* easy case: if there are no ' and no \, just use a single quote */
+	if (strchr(str, '\'') == NULL &&
+		strchr(str, '\\') == NULL)
+	{
+		appendStringInfo(buf, "'%s'", 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);
+}
+
+/*
+ * 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)
+{
+	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;
+	}
+}
+
+/*
+ * 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)
+{
+	JsonbContainer *container;
+	JsonbIterator  *it;
+	JsonbValue	v;
+	int			type;
+	bool		first = true;
+
+	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);
+				break;
+		}
+	}
+}
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ *
+ * The element name may have an optional separator specification preceded
+ * by a colon.	Its presence indicates that the element is expected to be
+ * an array; the specified separator is used to join the array elements.
+ *------
+ */
+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));
+}
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index e1763a3..d9958d6 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
 	return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
@@ -323,6 +326,106 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
+/*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - is_array indicates whether []s must be added
+ *
+ * Also, we don't try to decode type names to their standard-mandated names,
+ * except in the cases of unusual typmod rules, as specified above.
+ *
+ * XXX there is a lot of code duplication between this routine and
+ * format_type_internal.  (One thing that doesn't quite match is the whole
+ * allow_invalid business.)
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *is_array)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID)
+	{
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPOID:
+			case TIMESTAMPTZOID:
+				/* the TZ part is added by typmod */
+				*typname = pstrdup("TIMESTAMP");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		*is_array = false;
+
+		ReleaseSysCache(tuple);
+		return;
+	}
+
+	/*
+	 * Check if it's a regular (variable length) array type.  As above,
+	 * fixed-length array types such as "name" shouldn't get deconstructed.
+	 */
+	array_base_type = typeform->typelem;
+
+	if (array_base_type != InvalidOid &&
+		typeform->typstorage != 'p')
+	{
+		/* Switch our attention to the array element type */
+		ReleaseSysCache(tuple);
+		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+		typeform = (Form_pg_type) GETSTRUCT(tuple);
+		type_oid = array_base_type;
+		*is_array = true;
+	}
+	else
+		*is_array = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
 
 /*
  * Add typmod decoration to the basic type name
@@ -338,7 +441,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,7 +453,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 9beebb3..1d65b25 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -372,7 +372,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;
@@ -454,7 +454,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 2a9431d..fd20291 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -32,6 +32,8 @@ extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
 				   List *objargs, Relation *relp,
 				   LOCKMODE lockmode, bool missing_ok);
 
+extern Oid get_objtype_catalog_oid(ObjectType objtype);
+
 extern void check_object_ownership(Oid roleid,
 					   ObjectType objtype, ObjectAddress address,
 					   List *objname, List *objargs, Relation relation);
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 4736532..c7ce195 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4990,6 +4990,10 @@ DESCR("peek at binary changes from replication slot");
 /* event triggers */
 DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
 DESCR("list objects dropped by the current command");
+DATA(insert OID = 3590 (  pg_event_trigger_get_creation_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,114}" "{o,o,o,o,o,o,o,o,o}" "{classid,objid,objsubid,command_tag,object_type,schema,identity,in_extension,command}" _null_ pg_event_trigger_get_creation_commands _null_ _null_ _null_ ));
+DESCR("list JSON-formatted commands executed by the current command");
+DATA(insert OID = 3591 (  pg_event_trigger_expand_command PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 25 "114" _null_ _null_ _null_ _null_ pg_event_trigger_expand_command _null_ _null_ _null_ ));
+DESCR("format JSON command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 1348997..da7f449 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -53,4 +53,7 @@ extern void EventTriggerEndCompleteQuery(void);
 extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
 
+extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
+						 ObjectType objtype, Node *parsetree);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 2cf784b..6e53320 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -23,7 +23,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cef9544..2f8a6da 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1208,6 +1208,7 @@ typedef enum ObjectType
 	OBJECT_ATTRIBUTE,			/* type's attribute, when distinct from column */
 	OBJECT_CAST,
 	OBJECT_COLUMN,
+	OBJECT_COMPOSITE,
 	OBJECT_CONSTRAINT,
 	OBJECT_COLLATION,
 	OBJECT_CONVERSION,
@@ -1239,6 +1240,7 @@ typedef enum ObjectType
 	OBJECT_TSPARSER,
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
+	OBJECT_USER_MAPPING,
 	OBJECT_VIEW
 } ObjectType;
 
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..1278df9
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,60 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+#include "access/attnum.h"
+#include "nodes/nodes.h"
+
+/*
+ * Support for keeping track of a command to deparse.
+ *
+ * When a command is run, we collect some information about it for later
+ * deparsing; deparse_utility_command can later be used to obtain a usable
+ * representation of it.
+ */
+
+typedef enum StashedCommandType
+{
+	SCT_Simple,
+} StashedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct StashedATSubcmd
+{
+	AttrNumber		attnum;	/* affected column number */
+	Oid				oid;	/* affected constraint, default value or index */
+	Node		   *parsetree;
+} StashedATSubcmd;
+
+typedef struct StashedCommand
+{
+	StashedCommandType type;
+	bool		in_extension;
+	Node	   *parsetree;
+
+	union
+	{
+		struct SimpleCommand
+		{
+			Oid			objectId;
+			uint32		objectSubId;
+			ObjectType	objtype;
+		} simple;
+	} d;
+} StashedCommand;
+
+extern char *deparse_utility_command(StashedCommand *cmd);
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fb1b4a4..22fe802 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1068,6 +1068,9 @@ extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 extern Datum oidvectortypes(PG_FUNCTION_ARGS);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
+extern void format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname,
+					 char **typemodstr, bool *is_array);
 
 /* quote.c */
 extern Datum quote_ident(PG_FUNCTION_ARGS);
@@ -1189,6 +1192,10 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 
 /* commands/event_trigger.c */
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS);
+
+/* 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);
-- 
1.9.1

0005-deparse-sprinkle-EventTriggerStashCommand-calls.patchtext/x-diff; charset=us-asciiDownload
>From 9cb23b936652773c55db9ba8e20a897245a4e84f 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 05/26] deparse: sprinkle EventTriggerStashCommand() calls

---
 src/backend/commands/schemacmds.c |   9 ++
 src/backend/tcop/utility.c        | 259 ++++++++++++++++++++++++++------------
 2 files changed, 185 insertions(+), 83 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 03f5514..4548dfd 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, (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 3d046fc..6732715 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -876,6 +876,7 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	Oid			objectId;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -894,6 +895,10 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * CreateSchemaCommand calls EventTriggerStashCommand
+				 * internally, for reasons explained there.
+				 */
 				break;
 
 			case T_CreateStmt:
@@ -901,7 +906,6 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					List	   *stmts;
 					ListCell   *l;
-					Oid			relOid;
 
 					/* Run parse analysis ... */
 					stmts = transformCreateStmt((CreateStmt *) parsetree,
@@ -918,9 +922,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,
+													 stmt);
 
 							/*
 							 * Let NewRelationCreateToastTable decide if this
@@ -942,20 +948,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,
+													 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,
@@ -1044,36 +1057,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,
+											 parsetree);
 				}
 				break;
 
@@ -1087,46 +1108,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, parsetree);
 				}
 				break;
 
@@ -1161,50 +1189,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,
+											 parsetree);
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				objectId = CreateExtension((CreateExtensionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				objectId = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, parsetree);
 				break;
 
 			case T_AlterExtensionContentsStmt:
-				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
+				objectId = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, parsetree);
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FDW, parsetree);
 				break;
 
 			case T_AlterFdwStmt:
-				AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				objectId = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FDW, parsetree);
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FOREIGN_SERVER,
+										 parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
-				AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				objectId = AlterForeignServer((AlterForeignServerStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FOREIGN_SERVER,
+										 parsetree);
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_USER_MAPPING,
+										 parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
-				AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				objectId = AlterUserMapping((AlterUserMappingStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_USER_MAPPING,
+										 parsetree);
 				break;
 
 			case T_DropUserMappingStmt:
@@ -1213,105 +1257,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,
+											 parsetree);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				objectId = DefineEnum((CreateEnumStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				objectId = DefineRange((CreateRangeStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				objectId = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				objectId = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_VIEW, parsetree);
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, parsetree);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				objectId = AlterFunction((AlterFunctionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				objectId = DefineRule((RuleStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, 0, OBJECT_RULE, parsetree);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				objectId = DefineSequence((CreateSeqStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				objectId = AlterSequence((AlterSeqStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TABLE, parsetree);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+				objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
 								   queryString, params, completionTag);
+				EventTriggerStashCommand(objectId, 0, OBJECT_MATVIEW, 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, parsetree);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_LANGUAGE, parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				objectId = DefineDomain((CreateDomainStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_DOMAIN, parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				objectId = CreateConversionCommand((CreateConversionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_CONVERSION, parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				objectId = CreateCast((CreateCastStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_CAST, parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				objectId = DefineOpClass((CreateOpClassStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPCLASS, parsetree);
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPFAMILY, parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
-				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				objectId = AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_OPFAMILY, parsetree);
 				break;
 
 			case T_AlterTSDictionaryStmt:
-				AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				objectId = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TSDICTIONARY, parsetree);
 				break;
 
 			case T_AlterTSConfigurationStmt:
-				AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				objectId = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_TSCONFIGURATION, parsetree);
 				break;
 
 			case T_AlterTableMoveAllStmt:
+				/* commands are stashed in AlterTableMoveAll */
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
 				break;
 
@@ -1320,23 +1390,44 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree, NULL);
+				{
+					RenameStmt *stmt = (RenameStmt *) parsetree;
+					int			objsubid;
+
+					objectId = ExecRenameStmt(stmt, &objsubid);
+					EventTriggerStashCommand(objectId, objsubid,
+											 stmt->renameType, 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,
+										 parsetree);
 				break;
 
 			case T_CommentStmt:
-				CommentObject((CommentStmt *) parsetree, NULL);
+				{
+					uint32		objsubid;
+
+					objectId = CommentObject((CommentStmt *) parsetree, &objsubid);
+					EventTriggerStashCommand(objectId, objsubid,
+											 ((CommentStmt *) parsetree)->objtype,
+											 parsetree);
+				}
 				break;
 
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
+				/* commands are stashed in ExecGrantStmt_oids */
 				break;
 
 			case T_DropOwnedStmt:
@@ -1348,11 +1439,13 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
-				CreatePolicy((CreatePolicyStmt *) parsetree);
+				objectId = CreatePolicy((CreatePolicyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_POLICY, parsetree);
 				break;
 
 			case T_AlterPolicyStmt:		/* ALTER POLICY */
-				AlterPolicy((AlterPolicyStmt *) parsetree);
+				objectId = AlterPolicy((AlterPolicyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, 0, OBJECT_POLICY, parsetree);
 				break;
 
 			default:
-- 
1.9.1

0006-deparse-Support-CREATE-TYPE-AS.patchtext/x-diff; charset=us-asciiDownload
>From d4ffc80906e0320110f8cd74342e227af17224fe 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 06/26] 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 8ef1758..643388a 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -544,6 +544,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
@@ -641,7 +903,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 09fa377..1fc7e34 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9325,3 +9325,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 520b066..05f0fc4 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -31,4 +31,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 */
-- 
1.9.1

0007-deparse-Support-CREATE-TYPE-AS-ENUM.patchtext/x-diff; charset=us-asciiDownload
>From 0d672839463c260cdc2d42479b0059b60ee5f725 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 07/26] 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 643388a..aa1a936 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -806,6 +806,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
@@ -907,7 +939,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 */
-- 
1.9.1

0008-deparse-Support-CREATE-SCHEMA-TABLE-SEQUENCE-INDEX-T.patchtext/x-diff; charset=us-asciiDownload
>From 2737dd3676838de2ac3eb0c8a80bc2a0cf183e47 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 08/26] 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 | 954 ++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  | 355 ++++++++++++--
 src/include/commands/sequence.h    |   1 +
 src/include/utils/ruleutils.h      |  11 +-
 6 files changed, 1298 insertions(+), 60 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 3b89dd0..0c5d5d7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1492,6 +1492,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 2e289a4..0e4379a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -7940,7 +7940,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 aa1a936..49f5a3e 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -544,6 +544,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
  *
@@ -770,6 +987,333 @@ 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 "
+			"%{on_commit}s WITH (%{with:, }s) %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D "
+			"(%{table_elements:, }s) %{inherits}s "
+			"%{on_commit}s WITH (%{with:, }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   *fmt;
+		char   *value;
+
+		/* already handled above */
+		if (strcmp(opt->defname, "oids") == 0)
+			continue;
+
+		if (opt->defnamespace)
+			fmt = psprintf("%s.%s=%%{value}s", opt->defnamespace, opt->defname);
+		else
+			fmt = psprintf("%s=%%{value}s", opt->defname);
+		value = opt->arg ? defGetString(opt) :
+			defGetBoolean(opt) ? "TRUE" : "FALSE";
+		tmp = new_objtree_VA(fmt, 2,
+							 "option", ObjTypeString, opt->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)
  *
@@ -837,6 +1381,404 @@ 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 %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   5,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(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.
  *
@@ -859,11 +1801,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:
@@ -886,7 +1828,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_IndexStmt:
-			command = NULL;
+			command = deparse_IndexStmt(objectId, parsetree);
 			break;
 
 		case T_CreateExtensionStmt:
@@ -967,11 +1909,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:
@@ -983,7 +1925,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 1fc7e34..1907a08 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 914d155..984631a 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -70,6 +70,7 @@ extern Datum setval3_oid(PG_FUNCTION_ARGS);
 extern Datum lastval(PG_FUNCTION_ARGS);
 
 extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS);
+extern Form_pg_sequence get_sequence_values(Oid sequenceId);
 
 extern Oid	DefineSequence(CreateSeqStmt *stmt);
 extern Oid	AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 05f0fc4..0503816 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);
-- 
1.9.1

0009-deparse-Support-CREATE-TYPE-AS-RANGE.patchtext/x-diff; charset=us-asciiDownload
>From 46320c66be3ef4f13e8dc8d719fa82ad4744126a 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 09/26] 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 49f5a3e..8672637 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"
@@ -1381,6 +1383,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)
 {
@@ -1885,7 +1985,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:
-- 
1.9.1

0010-deparse-Support-CREATE-EXTENSION.patchtext/x-diff; charset=us-asciiDownload
>From e40dd29d5f99d84f35ba1d6c47063f4183357f0d 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 10/26] 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 8672637..3d69653 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"
@@ -565,6 +566,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)
  *
@@ -1932,7 +2008,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_CreateExtensionStmt:
-			command = NULL;
+			command = deparse_CreateExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionStmt:
-- 
1.9.1

0011-deparse-Support-CREATE-RULE.patchtext/x-diff; charset=us-asciiDownload
>From 3e25bd05a2ed53f539ef427044307698a50f004e 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 11/26] 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 3d69653..c6b9b46 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"
@@ -1916,6 +1918,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
@@ -2081,7 +2172,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 1907a08..7f79637 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 0503816..46b4d98 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);
-- 
1.9.1

0012-deparse-Support-ALTER-TYPE-ADD-VALUE-enums.patchtext/x-diff; charset=us-asciiDownload
>From a12ef3f0312742e7614125b01777713cf4a368b8 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 12/26] 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 c6b9b46..e40b029 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2046,6 +2046,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.
  *
@@ -2156,7 +2189,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterEnumStmt:
-			command = NULL;
+			command = deparse_AlterEnumStmt(objectId, parsetree);
 			break;
 
 		case T_ViewStmt:		/* CREATE VIEW */
-- 
1.9.1

0013-deparse-Support-for-ALTER-OBJECT-RENAME.patchtext/x-diff; charset=us-asciiDownload
>From 0e8f605d351a2348cc812c6bdec29ba4bcf1a173 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 13/26] deparse: Support for ALTER <OBJECT> RENAME

It supports everything but functions, aggregates, operator classes and
operator families.
---
 src/backend/tcop/deparse_utility.c | 277 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 276 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index e40b029..7f90e6f 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1559,6 +1559,281 @@ 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_COLUMN:
+			return "COLUMN";
+		case OBJECT_DOMAIN:
+			return "DOMAIN";
+		case OBJECT_COLLATION:
+			return "COLLATION";
+		case OBJECT_CONVERSION:
+			return "CONVERSION";
+		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_OPERATOR:
+			return "OPERATOR";
+		case OBJECT_OPCLASS:
+			return "OPERATOR CLASS";
+		case OBJECT_OPFAMILY:
+			return "OPERATOR FAMILY";
+		case OBJECT_SCHEMA:
+			return "SCHEMA";
+		case OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case OBJECT_TABLE:
+			return "TABLE";
+		case OBJECT_TSCONFIGURATION:
+			return "TEXT SEARCH CONFIGURATION";
+		case OBJECT_TSDICTIONARY:
+			return "TEXT SEARCH DICTIONARY";
+		case OBJECT_TSPARSER:
+			return "TEXT SEARCH PARSER";
+		case OBJECT_TSTEMPLATE:
+			return "TEXT SEARCH TEMPLATE";
+		case OBJECT_TYPE:
+			return "TYPE";
+		case OBJECT_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;
+	const char *subthing;
+
+	/*
+	 * FIXME --- this code is missing support for inheritance behavioral flags,
+	 * i.e. the "*" and ONLY elements.
+	 */
+
+	/*
+	 * In a ALTER .. RENAME command, we don't have the original name of the
+	 * object in system catalogs: since we inspect them after the command has
+	 * executed, the old name is already gone.  Therefore, we extract it from
+	 * the parse node.  Note we still extract the schema name from the catalog
+	 * (it might not be present in the parse node); it cannot possibly have
+	 * changed anyway.
+	 *
+	 * XXX what if there's another event trigger running concurrently that
+	 * renames the schema or moves the object to another schema?  Seems
+	 * pretty far-fetched, but possible nonetheless.
+	 */
+	switch (node->renameType)
+	{
+		case OBJECT_TABLE:
+		case OBJECT_SEQUENCE:
+		case OBJECT_VIEW:
+		case OBJECT_MATVIEW:
+		case OBJECT_INDEX:
+		case OBJECT_FOREIGN_TABLE:
+			fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME TO %%{newname}I",
+							  stringify_objtype(node->renameType));
+			relation = relation_open(objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_object_object(renameStmt, "identity",
+								 new_objtree_for_qualname(schemaId,
+														  node->relation->relname));
+			append_string_object(renameStmt, "if_exists",
+								 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+			break;
+
+		case OBJECT_COLUMN:
+		case OBJECT_ATTRIBUTE:
+			relation = relation_open(objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			if (node->renameType == OBJECT_COLUMN)
+				subthing = "COLUMN";
+			else
+				subthing = "ATTRIBUTE";
+
+			fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME %s %%{colname}I TO %%{newname}I",
+							  stringify_objtype(node->relationType),
+							  subthing);
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_object_object(renameStmt, "identity",
+								 new_objtree_for_qualname(schemaId,
+														  node->relation->relname));
+			append_string_object(renameStmt, "colname", node->subname);
+			append_string_object(renameStmt, "if_exists",
+								 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+			break;
+
+		case OBJECT_SCHEMA:
+		case OBJECT_FDW:
+		case OBJECT_LANGUAGE:
+		case OBJECT_FOREIGN_SERVER:
+			fmtstr = psprintf("ALTER %s %%{identity}I RENAME TO %%{newname}I",
+							  stringify_objtype(node->renameType));
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_string_object(renameStmt, "identity",
+								 node->subname);
+			break;
+
+		case OBJECT_COLLATION:
+		case OBJECT_CONVERSION:
+		case OBJECT_DOMAIN:
+		case OBJECT_TSDICTIONARY:
+		case OBJECT_TSPARSER:
+		case OBJECT_TSTEMPLATE:
+		case OBJECT_TSCONFIGURATION:
+		case OBJECT_TYPE:
+			{
+				ObjTree    *ident;
+				HeapTuple	objTup;
+				Oid			catalogId;
+				Relation	catalog;
+				bool		isnull;
+				AttrNumber	nspnum;
+
+				catalogId = get_objtype_catalog_oid(node->renameType);
+				catalog = heap_open(catalogId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, objectId);
+				nspnum = get_object_attnum_namespace(catalogId);
+
+				schemaId = DatumGetObjectId(heap_getattr(objTup,
+														 nspnum,
+														 RelationGetDescr(catalog),
+														 &isnull));
+
+				fmtstr = psprintf("ALTER %s %%{identity}D RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				ident = new_objtree_for_qualname(schemaId,
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+				relation_close(catalog, AccessShareLock);
+
+			}
+			break;
+
+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			elog(ERROR, "renaming of functions and aggregates is not supported yet");
+
+		case OBJECT_CONSTRAINT:
+			{
+				HeapTuple		conTup;
+				Form_pg_constraint	constrForm;
+				ObjTree		   *ident;
+
+				conTup = SearchSysCache1(CONSTROID, objectId);
+				constrForm = (Form_pg_constraint) GETSTRUCT(conTup);
+				fmtstr = psprintf("ALTER %s %%{identity}D RENAME CONSTRAINT %%{conname}I TO %%{newname}I",
+								  stringify_objtype(node->relationType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+
+				switch (node->relationType)
+				{
+					case OBJECT_DOMAIN:
+						ident = new_objtree_for_qualname_id(TypeRelationId,
+															constrForm->contypid);
+						break;
+					case OBJECT_TABLE:
+						ident = new_objtree_for_qualname_id(RelationRelationId,
+															constrForm->conrelid);
+						break;
+					default:
+						elog(ERROR, "invalid relation type %d", node->relationType);
+				}
+
+				append_string_object(renameStmt, "conname", node->subname);
+				append_object_object(renameStmt, "identity", ident);
+				ReleaseSysCache(conTup);
+			}
+			break;
+
+		case OBJECT_OPCLASS:
+		case OBJECT_OPFAMILY:
+			ereport(ERROR,
+					(errmsg("renaming of operator classes and families is not supported")));
+			break;
+
+		case OBJECT_RULE:
+			{
+				HeapTuple	rewrTup;
+				Form_pg_rewrite rewrForm;
+				Relation	pg_rewrite;
+
+				pg_rewrite = relation_open(RewriteRelationId, AccessShareLock);
+				rewrTup = get_catalog_object_by_oid(pg_rewrite, objectId);
+				rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup);
+
+				renameStmt = new_objtree_VA("ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "rulename", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 rewrForm->ev_class));
+				relation_close(pg_rewrite, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_TRIGGER:
+			{
+				HeapTuple	trigTup;
+				Form_pg_trigger trigForm;
+				Relation	pg_trigger;
+
+				pg_trigger = relation_open(TriggerRelationId, AccessShareLock);
+				trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+				trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+				renameStmt = new_objtree_VA("ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "triggername", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 trigForm->tgrelid));
+				relation_close(pg_trigger, AccessShareLock);
+			}
+			break;
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	append_string_object(renameStmt, "newname", node->newname);
+
+	return renameStmt;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -2270,7 +2545,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RenameStmt:
-			command = NULL;
+			command = deparse_RenameStmt(objectId, parsetree);
 			break;
 
 		case T_AlterObjectSchemaStmt:
-- 
1.9.1

0022-deparse-support-ALTER-THING-OWNER-TO.patchtext/x-diff; charset=us-asciiDownload
>From 949c75a7c8f4256f9257598a41da91a04c4af359 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 22/26] 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 d5ce0e7..05a481e 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3605,6 +3605,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;
@@ -4254,7 +4277,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterOwnerStmt:
-			command = NULL;
+			command = deparse_AlterOwnerStmt(objectId, parsetree);
 			break;
 
 		case T_CommentStmt:
-- 
1.9.1

0023-deparse-Support-ALTER-EXTENSION-UPDATE-TO.patchtext/x-diff; charset=us-asciiDownload
>From 58fb60d8d05afee0968774f987e88c12d891ac03 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 23/26] 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 05a481e..c91fc9b 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1496,6 +1496,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
@@ -4135,7 +4177,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterExtensionStmt:
-			command = NULL;
+			command = deparse_AlterExtensionStmt(objectId, parsetree);
 			break;
 
 		case T_AlterExtensionContentsStmt:
-- 
1.9.1

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

---
 src/backend/catalog/aclchk.c         |  37 ++----
 src/backend/commands/event_trigger.c |  65 +++++++++++
 src/backend/tcop/deparse_utility.c   | 215 +++++++++++++++++++++++++++++++++++
 src/include/commands/event_trigger.h |   2 +
 src/include/tcop/deparse_utility.h   |  10 +-
 src/include/utils/aclchk.h           |  45 ++++++++
 6 files changed, 347 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 d30612c..f9c068f 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 b66301f..62a3332 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1469,6 +1469,48 @@ EventTriggerComplexCmdEnd(void)
 	currentEventTriggerState->curcmd = NULL;
 }
 
+/*
+ * EventTriggerStashGrant
+ * 		Save data about a GRANT/REVOKE command being executed
+ *
+ * This function creates a copy of the InternalGrant, as the original might
+ * not have the right lifetime.
+ */
+void
+EventTriggerStashGrant(InternalGrant *istmt)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+	InternalGrant  *icopy;
+	ListCell	   *cell;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	/*
+	 * copying the node is moderately challenging ... XXX should we consider
+	 * changing InternalGrant into a full-fledged node instead?
+	 */
+	icopy = palloc(sizeof(InternalGrant));
+	memcpy(icopy, istmt, sizeof(InternalGrant));
+	icopy->objects = list_copy(istmt->objects);
+	icopy->grantees = list_copy(istmt->grantees);
+	icopy->col_privs = NIL;
+	foreach(cell, istmt->col_privs)
+		icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell)));
+
+	stashed = palloc(sizeof(StashedCommand));
+	stashed->type = SCT_Grant;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.grant.istmt = icopy;
+	stashed->parsetree = NULL;
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1637,6 +1679,29 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 				/* command */
 				values[i++] = CStringGetTextDatum(command);
 			}
+			else
+			{
+				Assert(cmd->type == SCT_Grant);
+
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum("GRANT");	/* XXX maybe REVOKE or something else */
+				/* object_type */
+				values[i++] = CStringGetTextDatum("TABLE"); /* XXX maybe something else */
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+			}
 
 			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 		}
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index c91fc9b..051a4e8 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"
@@ -3738,6 +3743,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 = "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;
@@ -4409,6 +4621,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 2a23edd..9573459 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
 {
@@ -55,6 +56,7 @@ extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
 
 extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
 						 ObjectType objtype, Node *parsetree);
+extern void EventTriggerStashGrant(InternalGrant *istmt);
 extern void EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype);
 extern void EventTriggerComplexCmdSetOid(Oid objectId);
 extern void EventTriggerRecordSubcmd(Node *subcmd, Oid relid,
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 910c716..6783de7 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,11 @@ typedef struct StashedCommand
 			ObjectType objtype;
 			List   *subcmds;
 		} alterTable;
+
+		struct GrantCommand
+		{
+			InternalGrant *istmt;
+		} grant;
 	} d;
 } StashedCommand;
 
diff --git a/src/include/utils/aclchk.h b/src/include/utils/aclchk.h
new file mode 100644
index 0000000..1ca7095
--- /dev/null
+++ b/src/include/utils/aclchk.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * aclchk.h
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/aclchk.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLCHK_H
+#define ACLCHK_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+/*
+ * The information about one Grant/Revoke statement, in internal format: object
+ * and grantees names have been turned into Oids, the privilege list is an
+ * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
+ * all_privs is true, 'privileges' will be internally set to the right kind of
+ * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
+ * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
+ */
+typedef struct
+{
+	bool		is_grant;
+	GrantObjectType objtype;
+	List	   *objects;
+	bool		all_privs;
+	AclMode		privileges;
+	List	   *col_privs;
+	List	   *grantees;
+	bool		grant_option;
+	DropBehavior behavior;
+} InternalGrant;
+
+
+#endif	/* ACLCHK_H */
-- 
1.9.1

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

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

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 051a4e8..bb08a5d 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2857,6 +2857,112 @@ 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}s", 1,
+								 "cost", ObjTypeString,
+								 psprintf("%f", defGetNumeric(defel)));
+		}
+		else if (strcmp(defel->defname, "rows") == 0)
+		{
+			tmp = new_objtree_VA("ROWS %{rows}s", 0);
+			if (defGetNumeric(defel) == 0)
+				append_bool_object(tmp, "present", false);
+			else
+				append_string_object(tmp, "rows",
+									 psprintf("%f", defGetNumeric(defel)));
+		}
+
+		if (tmp)
+			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 *
@@ -4454,7 +4560,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_AlterFunctionStmt:
-			command = NULL;
+			command = deparse_AlterFunction(objectId, parsetree);
 			break;
 
 		case T_RuleStmt:
-- 
1.9.1

0026-deparse-support-COMMENT-ON.patchtext/x-diff; charset=us-asciiDownload
>From 384131042a7a8d40a1f583d693ba46e19739e9a4 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 26/26] deparse: support COMMENT ON

---
 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 bb08a5d..8e3ab33 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3781,6 +3781,38 @@ deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree)
+{
+	CommentStmt *node = (CommentStmt *) parsetree;
+	ObjTree	   *comment;
+	ObjectAddress addr;
+	char	   *fmt;
+
+	if (node->comment)
+	{
+		fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}L",
+					   stringify_objtype(node->objtype));
+		comment = new_objtree_VA(fmt, 0);
+		append_string_object(comment, "comment", node->comment);
+	}
+	else
+	{
+		fmt = psprintf("COMMENT ON %s %%{identity}s IS NULL",
+					   stringify_objtype(node->objtype));
+		comment = new_objtree_VA(fmt, 0);
+	}
+
+	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)
 {
 	HeapTuple   conTup;
@@ -4641,7 +4673,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:
-- 
1.9.1

#6Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#5)

On Mon, Oct 13, 2014 at 12:45 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Here's a new version of this series. The main change is that I've
changed deparse_utility.c to generate JSON, and the code that was in
commands/event_trigger.c to decode that JSON, so that it uses the new
Jsonb API instead. In addition, I've moved the new code that was in
commands/event_trigger.c to utils/adt/ddl_json.c. (The only entry point
of the new file is the SQL-callable pg_event_trigger_expand_command()
function, and its purpose is to expand a JSON object emitted by the
deparse_utility.c code back into a plain text SQL command.)
I have also cleaned up the code per comments from Michael Paquier and
Andres Freund:

* the GRANT support for event triggers now correctly ignores global
objects.

* COMMENT ON .. IS NULL no longer causes a crash

Why would this not fail? Patch 3 in this set is identical to the last
one. I tested that and it worked well...

* renameatt() and ExecRenameStmt are consistent in returning the
objSubId as an "int" (not int32). This is what is used as objectSubId
in ObjectAddress, which is what we're using this value for.

In patch 1, ExecRenameStmt returns objsubid for an attribute name when
rename is done on an attribute. Now could you clarify why we skip this
list of objects even if their sub-object ID is available with
address.objectSubId?
case OBJECT_AGGREGATE:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
case OBJECT_EVENT_TRIGGER:
case OBJECT_FDW:
case OBJECT_FOREIGN_SERVER:
case OBJECT_FUNCTION:
case OBJECT_OPCLASS:
case OBJECT_OPFAMILY:
case OBJECT_LANGUAGE:
case OBJECT_TSCONFIGURATION:
case OBJECT_TSDICTIONARY:
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:

Patch 2 fails on make check for the tests privileges and foreign_data
by showing up double amount warnings for some object types:
*** /Users/ioltas/git/postgres/src/test/regress/expected/privileges.out
Fri Oct 10 14:34:10 2014
--- /Users/ioltas/git/postgres/src/test/regress/results/privileges.out
 Thu Oct 16 15:47:42 2014
***************
*** 118,123 ****
--- 118,124 ----
  ERROR:  permission denied for relation atest2
  GRANT ALL ON atest1 TO PUBLIC; -- fail
  WARNING:  no privileges were granted for "atest1"
+ WARNING:  no privileges were granted for "atest1"
  -- checks in subquery, both ok
  SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) );
   a | b
EventTriggerSupportsGrantObjectType is fine to remove
ACL_OBJECT_DATABASE and ACL_OBJECT_TABLESPACE from the list of
supported objects. That's as well in line with the current firing
matrix. I think that it would be appropriate to add a comment on top
of this function.

Patch 3 looks good.
Regards,
--
Michael

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

#7Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#6)

On Thu, Oct 16, 2014 at 4:01 PM, Michael Paquier <michael.paquier@gmail.com>
wrote:

On Mon, Oct 13, 2014 at 12:45 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Here's a new version of this series.

Here is some input on patch 4:

1) Use of OBJECT_ATTRIBUTE:
+               case OBJECT_ATTRIBUTE:
+                       catalog_id = TypeRelationId;    /* XXX? */
+                       break;
I think that the XXX mark could be removed, using TypeRelationId is correct
IMO as OBJECT ATTRIBUTE is used when managing an attribute object, which is
a custom type used for CREATE/ALTER TYPE.
2) This patch is showing up many warnings, among them:
event_trigger.c:1460:20: note: uninitialized use occurs here
                                addr.classId = classId;
                                               ^~~~~~~
event_trigger.c:1446:21: note: initialize the variable 'objSubId' to
silence this warning
                                uint32          objSubId;
                                                        ^
                                                         = 0
Or:
deparse_utility.c:301:1: warning: unused function 'append_object_object'
[-Wunused-function]
append_object_object(ObjTree *tree, char *name, ObjTree *value)
^
In the 2nd case though I imagine that those functions in deparse_utility.c
are used afterwards... There are a couple of other warning related to
SCT_Simple but that's normal as long as 17 or 24 are not combined with it.
3) What's that actually?
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
We may be able to do something smarter with event_trigger_support[], but as
this consists in a mapping of subcommands associated with CREATE/DROP/ALTER
and is only a reverse engineering operation of somewhat
AlterObjectTypeCommandTag  or CreateCommandTag, I am not sure how you could
merge that. Some input perhaps?
4)
+/*
+ * EventTriggerStashCommand
+ *             Save data about a simple DDL command that was just executed
+ */
Shouldn't this be "single" instead of "simple"?
5) I think that SCT_Simple should be renamed as SCT_Single
+typedef enum StashedCommandType
+{
+       SCT_Simple,
+} StashedCommandType;
This comment holds as well for deparse_simple_command.
6)
+               command = deparse_utility_command(cmd);
+
+               /*
+                * Some parse trees return NULL when deparse is attempted;
we don't
+                * emit anything for them.
+                */
+               if (command != NULL)
+               {
Small detail, but you may here just use a continue to make the code a bit
more readable after deparsing the command.
7) pg_event_trigger_get_creation_commands is modified as well in patches 17
and 24. You may as well use an enum on cmd->type.
8) Rejoining a comment done earlier by Andres, it would be nice to have
ERRCODE_WRONG_CONTEXT (unrelated to this patch).
ERRCODE_FEATURE_NOT_SUPPORTED seems rather a different error type...
9) Those declarations are not needed in event_trigger.c:
+#include "utils/json.h"
+#include "utils/jsonb.h"
10) Would you mind explaining what means "fmt"?
+ * 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.
11) deparse_utility.c mentions here and there JSON objects, but what is
created are JSONB objects. I'd rather be clear here.
12) Already mentioned before, but this reverse engineering machine for
types would be more welcome as a set of functions in lsyscache (one for
namespace, one for type name and one for is_array). For typemodstr the need
is different as it uses printTypmod.
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+                                        Oid *nspid, char **typname, char
**typemodstr,
+                                        bool *is_array)
13) This change seems unrelated to this patch...
-       int                     type = 0;
+       JsonbIteratorToken type = WJB_DONE;
        JsonbValue      v;
        int                     level = 0;
        bool            redo_switch = false;
@@ -454,7 +454,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");
14) This could already be pushed as a separate commit:
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;

A couple of comments: this patch introduces a basic infrastructure able to
do the following set of operations:
- Obtention of parse tree using StashedCommand
- Reorganization of parse tree to become an ObjTree, with boolean, array
- Reorganization of ObjTree to a JsonB value
I am actually a bit doubtful about why we actually need this intermediate
ObjTree step... What would be wrong in manipulating a JSON(B) parsed tree,
that is first empty, and pushing key/value objects to it when processing
each command? Something moving toward in this direction is that ObjTree has
some logic to manipulate booleans, null values, etc. This results in some
duplication with what jsonb and json can actually do when creating when
manipulating strings, as well as the extra processing like
objtree_to_jsonb_element. ddl_json.c (should be ddl_jsonb.c?) would as well
take more its sense as it directly manipulates JSONB containers.
Regards,
--
Michael

#8Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#7)

Hi Michael, thanks for the review.

Michael Paquier wrote:

On Thu, Oct 16, 2014 at 4:01 PM, Michael Paquier <michael.paquier@gmail.com>
wrote:

On Mon, Oct 13, 2014 at 12:45 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Here's a new version of this series.

Here is some input on patch 4:

1) Use of OBJECT_ATTRIBUTE:
+               case OBJECT_ATTRIBUTE:
+                       catalog_id = TypeRelationId;    /* XXX? */
+                       break;
I think that the XXX mark could be removed, using TypeRelationId is correct
IMO as OBJECT ATTRIBUTE is used when managing an attribute object, which is
a custom type used for CREATE/ALTER TYPE.

Agreed.

2) This patch is showing up many warnings, among them:

Will check.

3) What's that actually?
+/* XXX merge this with ObjectTypeMap? */
static event_trigger_support_data event_trigger_support[] = {
We may be able to do something smarter with event_trigger_support[], but as
this consists in a mapping of subcommands associated with CREATE/DROP/ALTER
and is only a reverse engineering operation of somewhat
AlterObjectTypeCommandTag or CreateCommandTag, I am not sure how you could
merge that. Some input perhaps?

ObjectTypeMap is part of the patch that handles DROP for replication;
see my other patch in commitfest. I am also not sure how to merge all
this stuff; with these patches, we would have three "do event triggers
support this object type" functions, so I was thinking in having maybe a
text file from which these functions are generated from a perl script or
something. But for now I think it's okay to keep things like this.
That comment is only there to remind me that some cleanup might be in
order.

4)
+/*
+ * EventTriggerStashCommand
+ *             Save data about a simple DDL command that was just executed
+ */
Shouldn't this be "single" instead of "simple"?

In an older version it was "basic". Not wedded to "simple", but I don't
think "single" is the right thing. A later patch in the series
introduces type Grant, and there's also type AlterTable. The idea
behind Simple is to include command types that do not require special
handling; but all these commands are single commands.

6)
+               command = deparse_utility_command(cmd);
+
+               /*
+                * Some parse trees return NULL when deparse is attempted;
we don't
+                * emit anything for them.
+                */
+               if (command != NULL)
+               {
Small detail, but you may here just use a continue to make the code a bit
more readable after deparsing the command.

Will check.

9) Those declarations are not needed in event_trigger.c:
+#include "utils/json.h"
+#include "utils/jsonb.h"

Will check. I split ddl_json.c at the last minute and I may have
forgotten to remove these.

10) Would you mind explaining what means "fmt"?
+ * 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.

"fmt" is equivalent to sprintf and friends' fmt argument. I guess this
commands needs to be expanded a bit.

11) deparse_utility.c mentions here and there JSON objects, but what is
created are JSONB objects. I'd rather be clear here.

Good point.

12) Already mentioned before, but this reverse engineering machine for
types would be more welcome as a set of functions in lsyscache (one for
namespace, one for type name and one for is_array). For typemodstr the need
is different as it uses printTypmod.
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+                                        Oid *nspid, char **typname, char
**typemodstr,
+                                        bool *is_array)

I am unsure about this. Other things that require many properties of
the same object do a single lookup and return all of them in a single
call, rather than repeated calls.

13) This change seems unrelated to this patch...
-       int                     type = 0;
+       JsonbIteratorToken type = WJB_DONE;
JsonbValue      v;
int                     level = 0;
bool            redo_switch = false;
@@ -454,7 +454,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");

Yes, sorry. I was trying to figure out how to use the jsonb stuff and
I found this error message was quite unclear. In general, jsonb code
seems to have random warts ...

A couple of comments: this patch introduces a basic infrastructure able to
do the following set of operations:
- Obtention of parse tree using StashedCommand
- Reorganization of parse tree to become an ObjTree, with boolean, array
- Reorganization of ObjTree to a JsonB value
I am actually a bit doubtful about why we actually need this intermediate
ObjTree step... What would be wrong in manipulating a JSON(B) parsed tree,
that is first empty, and pushing key/value objects to it when processing
each command? Something moving toward in this direction is that ObjTree has
some logic to manipulate booleans, null values, etc. This results in some
duplication with what jsonb and json can actually do when creating when
manipulating strings, as well as the extra processing like
objtree_to_jsonb_element. ddl_json.c (should be ddl_jsonb.c?) would as well
take more its sense as it directly manipulates JSONB containers.

Uhm. Obviously we didn't have jsonb when I started this and we do have
them now, so I could perhaps see about updating the patch to do things
this way; but I'm not totally sold on that idea, as my ObjTree stuff is
a lot easier to manage and the jsonb API is pretty ugly.

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

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

#9Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#8)

On 2014-10-28 05:30:43 -0300, Alvaro Herrera wrote:

A couple of comments: this patch introduces a basic infrastructure able to
do the following set of operations:
- Obtention of parse tree using StashedCommand
- Reorganization of parse tree to become an ObjTree, with boolean, array
- Reorganization of ObjTree to a JsonB value
I am actually a bit doubtful about why we actually need this intermediate
ObjTree step... What would be wrong in manipulating a JSON(B) parsed tree,
that is first empty, and pushing key/value objects to it when processing
each command? Something moving toward in this direction is that ObjTree has
some logic to manipulate booleans, null values, etc. This results in some
duplication with what jsonb and json can actually do when creating when
manipulating strings, as well as the extra processing like
objtree_to_jsonb_element. ddl_json.c (should be ddl_jsonb.c?) would as well
take more its sense as it directly manipulates JSONB containers.

Uhm. Obviously we didn't have jsonb when I started this and we do have
them now, so I could perhaps see about updating the patch to do things
this way; but I'm not totally sold on that idea, as my ObjTree stuff is
a lot easier to manage and the jsonb API is pretty ugly.

I looked at this as well, and I think trying to do so would not result
in readable code.

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

#10Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#9)

On Tue, Oct 28, 2014 at 6:00 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Uhm. Obviously we didn't have jsonb when I started this and we do have
them now, so I could perhaps see about updating the patch to do things
this way; but I'm not totally sold on that idea, as my ObjTree stuff is
a lot easier to manage and the jsonb API is pretty ugly.

I looked at this as well, and I think trying to do so would not result
in readable code.

That doesn't speak very well of jsonb. :-(

--
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

#11Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#10)

On Thu, Oct 30, 2014 at 2:40 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Oct 28, 2014 at 6:00 AM, Andres Freund <andres@2ndquadrant.com>
wrote:

Uhm. Obviously we didn't have jsonb when I started this and we do have
them now, so I could perhaps see about updating the patch to do things
this way; but I'm not totally sold on that idea, as my ObjTree stuff is
a lot easier to manage and the jsonb API is pretty ugly.

I looked at this as well, and I think trying to do so would not result
in readable code.

That doesn't speak very well of jsonb. :-(

Just did the same and I played a bit with the APIs. And I am getting the
impression that the jsonb API is currently focused on the fact of deparsing
and parsing Jsonb strings to/from containers but there is no real interface
that allows to easily manipulate the containers where the values are
located. So, what I think is missing is really a friendly interface to
manipulate JsonbContainers directly, and I think that we are not far from
it with something like this set, roughly:
- Initialization of an empty container
- Set of APIs to directly push a value to a container (boolean, array,
null, string, numeric or other jsonb object)
- Initialization of JsonbValue objects
With this basic set of APIs patch 4 could for example use JsonbToCString to
then convert the JSONB bucket back to a string it sends to client. Note as
well that there is already findJsonbValueFromContainer present to get back
a value in a container.

In short, my point is: instead of re-creating the wheel like what this
series of patch is trying to do with ObjTree, I think that it would be more
fruitful to have a more solid in-place JSONB infrastructure that allows to
directly manipulate JSONB objects. This feature as well as future
extensions could benefit from that.
Feel free to comment.
Regards,
--
Michael

#12Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#11)

On Fri, Oct 31, 2014 at 11:27 AM, Michael Paquier <michael.paquier@gmail.com

wrote:

So, what I think is missing is really a friendly interface to manipulate
JsonbContainers directly, and I think that we are not far from it with
something like this set, roughly:
- Initialization of an empty container
- Set of APIs to directly push a value to a container (boolean, array,
null, string, numeric or other jsonb object)
- Initialization of JsonbValue objects

Here are more thoughts among those lines looking at the current state of
the patch 4 that introduces the infrastructure of the whole feature. This
would make possible in-memory manipulation of jsonb containers without
relying on a 3rd-part set of APIs like what this patch is doing with
ObjTree to deparse the DDL parse trees.
1) Set of constructor functions for JsonbValue: null, bool, string, array,
JSONB object for nested values. Note that keys for can be used as Jsonb
string objects
2) Lookup functions for values in a JsonbContainer. Patch 4 is introducing
that with find_string_in_jsonbcontainer and find_bool_in_jsonbcontainer. We
may as well extend it to be able to look for another Jsonb object for
nested searches for example.
3) Functions to push JsonbValue within a container, using a key and a
value. This is where most of the work would be necessary, for bool, null,
string, Jsonb object and numeric.

This infrastructure would allow in-memory manipulation of jsonb containers.
Containers that can then be easily be manipulated to be changed back to
strings and for value lookups using key strings.
Regards,
--
Michael

#13Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#12)

Michael Paquier wrote:

Here are more thoughts among those lines looking at the current state of
the patch 4 that introduces the infrastructure of the whole feature. This
would make possible in-memory manipulation of jsonb containers without
relying on a 3rd-part set of APIs like what this patch is doing with
ObjTree to deparse the DDL parse trees.

Thanks for the thoughts. I have to say that I have no intention of
reworking the jsonb code. If somebody else wants to do the legwork and
add that API as you suggest, I'm happy to remove all the ObjTree stuff
from this patch. I don't expect this to happen too soon, though, so I
would instead consider committing this patch based on ObjTree. Later,
when somebody comes around to reworking jsonb, we can rip ObjTree out.

This infrastructure would allow in-memory manipulation of jsonb containers.
Containers that can then be easily be manipulated to be changed back to
strings and for value lookups using key strings.

Honestly, I had hoped that the jsonb code would have already included
this kind of functionality. I wasn't too happy when I discovered that I
needed to keep the ObjTree crap. But I don't want to do that work
myself.

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

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

#14Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#13)

On Fri, Nov 7, 2014 at 10:45 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Michael Paquier wrote:

Here are more thoughts among those lines looking at the current state of
the patch 4 that introduces the infrastructure of the whole feature. This
would make possible in-memory manipulation of jsonb containers without
relying on a 3rd-part set of APIs like what this patch is doing with
ObjTree to deparse the DDL parse trees.

Thanks for the thoughts. I have to say that I have no intention of
reworking the jsonb code. If somebody else wants to do the legwork and
add that API as you suggest, I'm happy to remove all the ObjTree stuff
from this patch. I don't expect this to happen too soon, though, so I
would instead consider committing this patch based on ObjTree. Later,
when somebody comes around to reworking jsonb, we can rip ObjTree out.

This infrastructure would allow in-memory manipulation of jsonb containers.
Containers that can then be easily be manipulated to be changed back to
strings and for value lookups using key strings.

Honestly, I had hoped that the jsonb code would have already included
this kind of functionality. I wasn't too happy when I discovered that I
needed to keep the ObjTree crap. But I don't want to do that work
myself.

If we're going to have infrastructure for this in core, we really
ought to make the effort to make it general instead of not.

I still think this whole idea is a mess. It adds what looks to be a
LOT of code that, at least as I understand it, we have no compelling
way to regression test and which will likely receive very limited
testing from users to support a feature that is not in core, may never
be, and which I strongly suspect may be too clever by half. Once
you've committed it, you're going to move onto other things and leave
it to everyone who comes after to try to maintain it. I bet we'll
still be running into bugs and half-implemented features five years
from now, and maybe in ten. Ramming through special-purpose
infrastructure instead of generalizing it is merely icing on the cake,
but it's still moving in the wrong direction.

--
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

#15Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#14)

On 2014-11-07 21:41:17 -0500, Robert Haas wrote:

On Fri, Nov 7, 2014 at 10:45 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Michael Paquier wrote:

Here are more thoughts among those lines looking at the current state of
the patch 4 that introduces the infrastructure of the whole feature. This
would make possible in-memory manipulation of jsonb containers without
relying on a 3rd-part set of APIs like what this patch is doing with
ObjTree to deparse the DDL parse trees.

Thanks for the thoughts. I have to say that I have no intention of
reworking the jsonb code. If somebody else wants to do the legwork and
add that API as you suggest, I'm happy to remove all the ObjTree stuff
from this patch. I don't expect this to happen too soon, though, so I
would instead consider committing this patch based on ObjTree. Later,
when somebody comes around to reworking jsonb, we can rip ObjTree out.

This infrastructure would allow in-memory manipulation of jsonb containers.
Containers that can then be easily be manipulated to be changed back to
strings and for value lookups using key strings.

Honestly, I had hoped that the jsonb code would have already included
this kind of functionality. I wasn't too happy when I discovered that I
needed to keep the ObjTree crap. But I don't want to do that work
myself.

If we're going to have infrastructure for this in core, we really
ought to make the effort to make it general instead of not.

I still think this whole idea is a mess. It adds what looks to be a
LOT of code that, at least as I understand it, we have no compelling
way to regression test

I don't understand why this is particularly difficult to regresssion
test. It actually is comparatively simple?

and which will likely receive very limited
testing from users to support a feature that is not in core,

Just like half of the features you worked on yourself lately? Why is
this an argument?

Being able to replicate DDL is a feature wish that has been around
pretty much since the inception of trigger based replication
solution. It's not some current fancy. And the json stuff only got there
because people wanted some way to manipulate the names in the replicated
- which this abstraction provides them with.

may never be, and which I strongly suspect may be too clever by half.
Once you've committed it, you're going to move onto other things and
leave it to everyone who comes after to try to maintain it. I bet
we'll still be running into bugs and half-implemented features five
years from now, and maybe in ten. Ramming through special-purpose
infrastructure instead of generalizing it is merely icing on the cake,
but it's still moving in the wrong direction.

You're just as much to blame for not writing a general json abstraction
layer for EXPLAIN. I'd say that's not much blame, but still, there's
really not much difference there.

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

#16Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#13)

On Sat, Nov 8, 2014 at 12:45 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Michael Paquier wrote:

Here are more thoughts among those lines looking at the current state of
the patch 4 that introduces the infrastructure of the whole feature. This
would make possible in-memory manipulation of jsonb containers without
relying on a 3rd-part set of APIs like what this patch is doing with
ObjTree to deparse the DDL parse trees.

Thanks for the thoughts. I have to say that I have no intention of
reworking the jsonb code. If somebody else wants to do the legwork and
add that API as you suggest, I'm happy to remove all the ObjTree stuff
from this patch. I don't expect this to happen too soon, though, so I
would instead consider committing this patch based on ObjTree. Later,
when somebody comes around to reworking jsonb, we can rip ObjTree out.

The thing freaking me out in this case is when would that really
happen? Maybe years from now, and perhaps at that point we would
regret to not have put in place the infrastructure that we knew we
could have done.

This infrastructure would allow in-memory manipulation of jsonb containers.
Containers that can then be easily be manipulated to be changed back to
strings and for value lookups using key strings.

Honestly, I had hoped that the jsonb code would have already included
this kind of functionality. I wasn't too happy when I discovered that I
needed to keep the ObjTree crap. But I don't want to do that work
myself.

I can't blame you for that.

In any case, if this code goes in as-is (I am against it at this point
but I am just giving my opinion as a reviewer here), I think that at
least the following things could be done with a minimal effort:
- Provide the set of constructor functions for JsonbValue
- Move the jsonb APIs (find_*_in_jsonbcontainer) doing value lookups
using keys from ddl_json.c to jsonb_util.c, rename them to something
more consistent and complete the set for the other object types.
- Add a TODO item in the wiki, and a TODO item in where ObjTree is defined.
Thanks,
--
Michael

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

#17Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#15)

On Sat, Nov 8, 2014 at 4:37 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I don't understand why this is particularly difficult to regresssion
test. It actually is comparatively simple?

If it is, great. I previously wrote this email:

/messages/by-id/CA+TgmoZ=vZriJMxLkqi_V0jg4k4LEAPmwUSC6RWXS5MquXUJNA@mail.gmail.com

Alvaro came up with a way of addressing the second point I raised
there, which I'm quite pleased about, but AFAIK there's been no
progress on the first one. Maybe I missed something?

and which will likely receive very limited
testing from users to support a feature that is not in core,

Just like half of the features you worked on yourself lately? Why is
this an argument?

Because the stuff I'm adding doesn't break every time someone adds a
new DDL command, something that we do regularly.

If it were a question of writing this code once and being done with
it, that would be unobjectionable in my view. But it isn't.
Practically every change to gram.y is going to require a corresponding
change to this stuff. As far as I can see, nobody except me has
commented on the burden that places on everyone who may wish to add
syntax support for a new construct in the future, which might mean
that I'm worrying about something that isn't worth worrying about, but
what I think is more likely is that nobody's worrying about it right
now because they haven't had to do it yet.

Just to illustrate the point, consider the CREATE TABLE name OF type
syntax that Peter added a few years ago. That patch
(e7b3349a8ad7afaad565c573fbd65fb46af6abbe) had the following impact on
gram.y:

src/backend/parser/gram.y | 56 ++++++++++++++++++++++++++++++++++-----------
1 file changed, 43 insertions(+), 13 deletions(-)

Now let's have a look at what impact it has on the deparsing code.
Patch 6 has deparse_ColumnDef_Typed from lines 134 to 193. There's 3
or so lines in deparseTableElements that decide whether to call it.
Patch 8 has more handling for this case, lines 439 to 443 and 463 to
490. So, if this feature had been committed before TABLE OF, it would
have needed about 100 lines of net new code to handle this case -
exclusive of refactoring. The actual size of the patch would probably
have been modestly larger than that, because some code would need to
be reindented when it got iffed out, and quite possibly some
rearrangement would have been needed. But even ignoring all that, the
deparse footprint of the patch would have been MORE THAN TWICE the
parser footprint.

I think that's going to be typical; and I think the deparse code is
going to be significantly more labor-intensive to write than bison
productions are. Do you really think that's not going to be a burden?

Being able to replicate DDL is a feature wish that has been around
pretty much since the inception of trigger based replication
solution. It's not some current fancy. And the json stuff only got there
because people wanted some way to manipulate the names in the replicated
- which this abstraction provides them with.

I understand that being able to replicate DDL is an important need,
and there may be no better way to do it than this. But that doesn't
mean that this code is going to be bug-free or easy to maintain.

may never be, and which I strongly suspect may be too clever by half.
Once you've committed it, you're going to move onto other things and
leave it to everyone who comes after to try to maintain it. I bet
we'll still be running into bugs and half-implemented features five
years from now, and maybe in ten. Ramming through special-purpose
infrastructure instead of generalizing it is merely icing on the cake,
but it's still moving in the wrong direction.

You're just as much to blame for not writing a general json abstraction
layer for EXPLAIN. I'd say that's not much blame, but still, there's
really not much difference there.

Well, this patch is series is at least an order of magnitude larger,
and it's apparently doing significantly more complex stuff with JSON,
because the explain.c patch just writes it out into a StringInfo.

--
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

#18Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#17)

Robert Haas <robertmhaas@gmail.com> writes:

If it were a question of writing this code once and being done with
it, that would be unobjectionable in my view. But it isn't.
Practically every change to gram.y is going to require a corresponding
change to this stuff. As far as I can see, nobody except me has
commented on the burden that places on everyone who may wish to add
syntax support for a new construct in the future, which might mean
that I'm worrying about something that isn't worth worrying about, but
what I think is more likely is that nobody's worrying about it right
now because they haven't had to do it yet.

I haven't been paying much attention to this thread, but I concur with
Robert that adding yet another set of overhead requirements to any
addition of new SQL is not a good thing.

Just to illustrate the point, consider the CREATE TABLE name OF type
syntax that Peter added a few years ago. That patch
(e7b3349a8ad7afaad565c573fbd65fb46af6abbe) had the following impact on
gram.y:

This analysis is kind of cheating, because adding new syntax hasn't been
only a matter of touching gram.y for a very long time. You invariably
have to touch pg_dump, and you have to touch ruleutils.c unless it's
strictly a DDL-command change. But people are used to those, and the
value of keeping pg_dump working is clear to everybody. Adding a similar
level of burden to support a feature with a narrow use-case seems like
a nonstarter from here. ESPECIALLY if we also have to manually add
regression test cases because there's no easy way to test it directly.

regards, tom lane

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

#19Andres Freund
andres@2ndquadrant.com
In reply to: Tom Lane (#18)

On 2014-11-08 11:52:43 -0500, Tom Lane wrote:

Adding a similar
level of burden to support a feature with a narrow use-case seems like
a nonstarter from here.

I don't understand this statement. In my experience the lack of a usable
replication solution that allows temporary tables and major version
differences is one of the most, if not *the* most, frequent criticisms
of postgres I hear. How is this a narrow use case?

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

#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#19)

Andres Freund <andres@2ndquadrant.com> writes:

On 2014-11-08 11:52:43 -0500, Tom Lane wrote:

Adding a similar
level of burden to support a feature with a narrow use-case seems like
a nonstarter from here.

I don't understand this statement. In my experience the lack of a usable
replication solution that allows temporary tables and major version
differences is one of the most, if not *the* most, frequent criticisms
of postgres I hear. How is this a narrow use case?

[ shrug... ] I don't personally give a damn about logical replication,
especially not logical replication implemented in this fashion. It looks
large and rickety (ie full of opportunities for bugs) and I would never
trust data I cared about to it.

Or in short: AFAICS you're not building the next WAL-shipping replication
solution, you're building the next Slony, and Slony never has and never
will be more than a niche use-case. Putting half of it into core wouldn't
fix that, it would just put a lot more maintenance burden on core
developers. Core developers are entitled to push back on such proposals.

regards, tom lane

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

#21Andres Freund
andres@2ndquadrant.com
In reply to: Tom Lane (#20)

On 2014-11-08 12:07:41 -0500, Tom Lane wrote:

Andres Freund <andres@2ndquadrant.com> writes:

On 2014-11-08 11:52:43 -0500, Tom Lane wrote:

Adding a similar
level of burden to support a feature with a narrow use-case seems like
a nonstarter from here.

I don't understand this statement. In my experience the lack of a usable
replication solution that allows temporary tables and major version
differences is one of the most, if not *the* most, frequent criticisms
of postgres I hear. How is this a narrow use case?

[ shrug... ] I don't personally give a damn about logical replication,
especially not logical replication implemented in this fashion.

"In this fashion" meaning ddl replication via event triggers? If you
have an actual suggestion how to do it better I'm all ears. So far
nobody has come up with anything.

Or in short: AFAICS you're not building the next WAL-shipping replication
solution, you're building the next Slony, and Slony never has and never
will be more than a niche use-case.

A good number of the sites out there use either londiste or slony. Not
because they like it, but because there's no other alternative.

I'd love to simply say that we can make WAL based replication work
across versions, platforms and subsets of relations in PG
clusters. Since that seems quite unrealistic people have to go different
ways.

Putting half of it into core wouldn't fix that, it would just put a
lot more maintenance burden on core developers.

Imo stuff that can't be done sanely outside core needs to be put into
core if it's actually desired by many users. And working DDL replication
for logical replication solutions surely is.

Core developers are entitled to push back on such proposals.

I'm not saying "core developers" (whover that is) aren't allowed to do
so. But just because they think something is (too) invasive doesn't make
it a niche application.

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

#22Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#21)

On Sat, Nov 8, 2014 at 12:20 PM, Andres Freund <andres@2ndquadrant.com> wrote:

Putting half of it into core wouldn't fix that, it would just put a
lot more maintenance burden on core developers.

Imo stuff that can't be done sanely outside core needs to be put into
core if it's actually desired by many users. And working DDL replication
for logical replication solutions surely is.

I don't buy it. This patch series is *all about* transferring the
maintenance burden of this feature from the BDR developers to the core
project. There's nothing to keep you from exposing the parse trees to
C functions that can live in an extension, and you can do all of this
deparsing there. Nobody will stop you, and when it breaks (not if)
you can fix it in your code. The overhead of deparsing new types of
parse nodes can be born by you. The only benefit of pushing it into
core is that some other logical replication solution could also take
advantage of that, but I know of nobody with any plans to do such a
thing. On the flip side, the *cost* of pushing it into core is that
it now becomes the PostgreSQL's community problem to update the
deparsing code every time the grammar changes. That cost-benefit
trade-off does not look favorable to me, especially given the absence
of any kind of robust testing framework.

--
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

#23Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#22)

On 2014-11-08 12:30:29 -0500, Robert Haas wrote:

On Sat, Nov 8, 2014 at 12:20 PM, Andres Freund <andres@2ndquadrant.com> wrote:

Putting half of it into core wouldn't fix that, it would just put a
lot more maintenance burden on core developers.

Imo stuff that can't be done sanely outside core needs to be put into
core if it's actually desired by many users. And working DDL replication
for logical replication solutions surely is.

I don't buy it. This patch series is *all about* transferring the
maintenance burden of this feature from the BDR developers to the core
project.

What? Not at all. It's *much* less work to do these kind of things out
of core. As you probably have experienced more than once. If it were
possible to do this entirely in a extension I'm pretty sure nobody would
have bothered.

There's nothing to keep you from exposing the parse trees to
C functions that can live in an extension, and you can do all of this
deparsing there.

Not really. There's some core functions that need to be touched. Like
most of the stuff in patches 1,2,3,5,16 does.

We could just integrate those parts, and be done with it. But would that
actually be a good thing for the community? Then slony needs to do it
and potentially others as well? Then auditing can't use it? Then
potential schema tracking solutions can't use it?

Nobody will stop you, and when it breaks (not if)
you can fix it in your code. The overhead of deparsing new types of
parse nodes can be born by you. The only benefit of pushing it into
core is that some other logical replication solution could also take
advantage of that, but I know of nobody with any plans to do such a
thing.

There've been people for a long while asking about triggers on catalogs
for that purpose. IIRC Jan was one of them.

On the flip side, the *cost* of pushing it into core is that
it now becomes the PostgreSQL's community problem to update the
deparsing code every time the grammar changes. That cost-benefit
trade-off does not look favorable to me, especially given the absence
of any kind of robust testing framework.

I agree that there should be testing in this.

But I think you're quite overstating the effort of maintaining
this. Looking at gram.y between the stamping of 9.3 devel and 9.4 devel
for commits that'd require DDL deparsing changes:
* ALTER TABLE .. ALTER CONSTRAINT: About 7 lines for the deparse code,
out of a ~350 line patch
* REFRESH MATERIALIZED VIEW ... CONCURRENTLY: About 1 line for deparse,
out of ~700
* WITH CHECK OPTION for views: About 3 lines for deparse, out of ~1300
* REPLICA IDENTITY: About 24 lines for deparse, out of ~950
* ALTER TABLESPACE MOVE: About 20 lines for deparse, out of
~340. Although that patch was essentially scrapped afterwards. And
rewritten differently.
* CREATE TABLESPACE ... WITH ...: Not supported by event triggers right
now as it's a global object.

That's really not a whole lot.

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

#24Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#17)

On 2014-11-08 10:42:15 -0500, Robert Haas wrote:

On Sat, Nov 8, 2014 at 4:37 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I don't understand why this is particularly difficult to regresssion
test. It actually is comparatively simple?

If it is, great. I previously wrote this email:

/messages/by-id/CA+TgmoZ=vZriJMxLkqi_V0jg4k4LEAPmwUSC6RWXS5MquXUJNA@mail.gmail.com

Alvaro came up with a way of addressing the second point I raised
there, which I'm quite pleased about, but AFAIK there's been no
progress on the first one. Maybe I missed something?

I unfortunately don't think so. And that sounds like a completely
reasonable criticism.

Just to illustrate the point, consider the CREATE TABLE name OF type
syntax that Peter added a few years ago. That patch
(e7b3349a8ad7afaad565c573fbd65fb46af6abbe) had the following impact on
gram.y:

src/backend/parser/gram.y | 56 ++++++++++++++++++++++++++++++++++-----------
1 file changed, 43 insertions(+), 13 deletions(-)

Now let's have a look at what impact it has on the deparsing code.
Patch 6 has deparse_ColumnDef_Typed from lines 134 to 193. There's 3
or so lines in deparseTableElements that decide whether to call it.
Patch 8 has more handling for this case, lines 439 to 443 and 463 to
490. So, if this feature had been committed before TABLE OF, it would
have needed about 100 lines of net new code to handle this case -
exclusive of refactoring. The actual size of the patch would probably
have been modestly larger than that, because some code would need to
be reindented when it got iffed out, and quite possibly some
rearrangement would have been needed. But even ignoring all that, the
deparse footprint of the patch would have been MORE THAN TWICE the
parser footprint.

Well, you disregarded the related costs of adjusting pg_dump et al, as
Tom mentioned, that's a significant part. And yes, there's some
additions that aren't entirely trivial to add. But the majority of
additions are pretty simple.

I think that's going to be typical; and I think the deparse code is
going to be significantly more labor-intensive to write than bison
productions are. Do you really think that's not going to be a burden?

I've looked into a fair number of cases and almost all are vastly
simpler than this. Most of the DDL changes that have been done lately
are things like adding IF NOT EXISTS somewhere; expanding existing
syntax for new types of objects (ALTER ... RENAME for foreign servers,
wrappers; LABEL for new types).

I think given the complexity of newly added features the overhead of
adding deparsing code isn't all that high.

Being able to replicate DDL is a feature wish that has been around
pretty much since the inception of trigger based replication
solution. It's not some current fancy. And the json stuff only got there
because people wanted some way to manipulate the names in the replicated
- which this abstraction provides them with.

I understand that being able to replicate DDL is an important need,
and there may be no better way to do it than this. But that doesn't
mean that this code is going to be bug-free or easy to maintain.

Agreed. There's definitely no free lunch (here). This has been discussed
more than once, and so far I've read anything superior that also has a
chance of handling ALTER.

may never be, and which I strongly suspect may be too clever by half.
Once you've committed it, you're going to move onto other things and
leave it to everyone who comes after to try to maintain it. I bet
we'll still be running into bugs and half-implemented features five
years from now, and maybe in ten. Ramming through special-purpose
infrastructure instead of generalizing it is merely icing on the cake,
but it's still moving in the wrong direction.

You're just as much to blame for not writing a general json abstraction
layer for EXPLAIN. I'd say that's not much blame, but still, there's
really not much difference there.

Well, this patch is series is at least an order of magnitude larger,
and it's apparently doing significantly more complex stuff with JSON,
because the explain.c patch just writes it out into a StringInfo.

And this code builds up the data in memory and then calls into the json
code to build the json value. And to parse json it uses the functions
underlying the SQL accessors.

There's one function it should either not need anymore (dequote_jsonval)
by using json_object_field_text instead of json_object_field or by
exposing dequote_jsonval's functionality from json.c.

I think the json angle here is a red herring. Sure, a nicer API could
save some FunctionCallInfoData boilerplate, but that's pretty much it.

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

#25Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#23)

On Sat, Nov 8, 2014 at 1:05 PM, Andres Freund <andres@2ndquadrant.com> wrote:

There's nothing to keep you from exposing the parse trees to
C functions that can live in an extension, and you can do all of this
deparsing there.

Not really. There's some core functions that need to be touched. Like
most of the stuff in patches 1,2,3,5,16 does.

Patch 1 is fine. We've done similar things in the past (cf.
c504513f83a9ee8dce4a719746ca73102cae9f13,
82b1b213cad3a69cf5f3dfaa81687c14366960fc). I'd just commit that.

Patch 2 adds support for GRANT and REVOKE to the event trigger
mechanism. I wonder if it's a bad idea to make the
ddl_command_start/end events fire for DCL. We discussed a lot of
these issues when this patch originally went in, and I think it'd be
worth revisiting that discussion.

Patch 3 is the same kind of idea as patch 2, only for COMMENT.

Patch 5 depends on patch 4, which does a bunch of things. I *think*
the upshot of patch 5 is that we're not currently firing event
triggers in some situations where we should, in which case +1 for
fixing that. It would help if there were a real commit message,
and/or some more contents, and I think it could be more completely
disentangled from patch 4.

Patch 16 again contains almost no comments and no description of its
specific purpose, but it appears to be similar to patch 1, so probably
mostly uncontroversial.

We could just integrate those parts, and be done with it. But would that
actually be a good thing for the community? Then slony needs to do it
and potentially others as well? Then auditing can't use it? Then
potential schema tracking solutions can't use it?

Do you think Slony is really going to use this? I guess we can let
the Slony guys speak for themselves, but I've been skeptical since day
one that this is the best way to do DDL replication, and I still am.
There are lots of ways that a replicated DDL statement can fail on the
replicas, and what are you going to do then? It's too late to show
the user the error message, so you can throw it in a log someplace and
hope that somebody notices, but that's it. It makes a lot more sense
to me to use some kind of a tool that applies the DDL in a coordinated
fashion on all nodes - or even just do it manually, since it might
very well be desirable to take the lock on different nodes at widely
different times, separated by a switchover. I certainly think there's
a use-case for what you're trying to do here, but I don't think it'll
be right for everyone.

Certainly, if the Slony guys - or some other team building an
out-of-core replication solutions says, hey, we really want this in
core, that would considerably strengthen the argument for putting it
there. But I haven't heard anyone say that yet - unlike logical
decoding, were we did have other people expressing clear interest in
using it.

There've been people for a long while asking about triggers on catalogs
for that purpose. IIRC Jan was one of them.

My impression, based on something Christopher Brown said a few years
ago, is that Slony's DDL trigger needs are largely satisfied by the
existing event trigger stuff. It would be helpful to get confirmation
as to whether that's the case.

On the flip side, the *cost* of pushing it into core is that
it now becomes the PostgreSQL's community problem to update the
deparsing code every time the grammar changes. That cost-benefit
trade-off does not look favorable to me, especially given the absence
of any kind of robust testing framework.

I agree that there should be testing in this.

But I think you're quite overstating the effort of maintaining
this. Looking at gram.y between the stamping of 9.3 devel and 9.4 devel
for commits that'd require DDL deparsing changes:
* ALTER TABLE .. ALTER CONSTRAINT: About 7 lines for the deparse code,
out of a ~350 line patch
* REFRESH MATERIALIZED VIEW ... CONCURRENTLY: About 1 line for deparse,
out of ~700
* WITH CHECK OPTION for views: About 3 lines for deparse, out of ~1300
* REPLICA IDENTITY: About 24 lines for deparse, out of ~950
* ALTER TABLESPACE MOVE: About 20 lines for deparse, out of
~340. Although that patch was essentially scrapped afterwards. And
rewritten differently.
* CREATE TABLESPACE ... WITH ...: Not supported by event triggers right
now as it's a global object.

That's really not a whole lot.

Those are pretty minor syntax patches, though. I think it's more
helpful to look at things like the row-level security stuff, or
materialized views per se, or DDL support for collations. In any
case, the additional coding burden concerns me less than the
additional testing burden - but there's another email further down
that's more to that specific point, so I'll stop here.

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

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

#26Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#25)

On 2014-11-08 17:49:30 -0500, Robert Haas wrote:

Patch 2 adds support for GRANT and REVOKE to the event trigger
mechanism. I wonder if it's a bad idea to make the
ddl_command_start/end events fire for DCL. We discussed a lot of
these issues when this patch originally went in, and I think it'd be
worth revisiting that discussion.

Well, it doesn't generally support it for all GRANT statement, but just
for ones that are database local. I think that mostly skirts the
problems from the last round of discussion. But I only vaguely remember
them.

Patch 5 depends on patch 4, which does a bunch of things. I *think*
the upshot of patch 5 is that we're not currently firing event
triggers in some situations where we should, in which case +1 for
fixing that. It would help if there were a real commit message,
and/or some more contents, and I think it could be more completely
disentangled from patch 4.

We could just integrate those parts, and be done with it. But would that
actually be a good thing for the community? Then slony needs to do it
and potentially others as well? Then auditing can't use it? Then
potential schema tracking solutions can't use it?

Do you think Slony is really going to use this?

There was a fair amount noise about it at one of the past cluster
hackers thingies.

I guess we can let
the Slony guys speak for themselves, but I've been skeptical since day
one that this is the best way to do DDL replication, and I still am.

Well, I've yet to hear anything that's realistic otherwise.

There are lots of ways that a replicated DDL statement can fail on the
replicas, and what are you going to do then? It's too late to show
the user the error message, so you can throw it in a log someplace and
hope that somebody notices, but that's it.

Sure. And absolutely the same is true for DML. And the lack of DDL
integration makes it happen really rather frequently...

It makes a lot more sense
to me to use some kind of a tool that applies the DDL in a coordinated
fashion on all nodes - or even just do it manually, since it might
very well be desirable to take the lock on different nodes at widely
different times, separated by a switchover. I certainly think there's
a use-case for what you're trying to do here, but I don't think it'll
be right for everyone.

I agree that it's not the right.

Certainly, if the Slony guys - or some other team building an
out-of-core replication solutions says, hey, we really want this in
core, that would considerably strengthen the argument for putting it
there. But I haven't heard anyone say that yet - unlike logical
decoding, were we did have other people expressing clear interest in
using it.

As I said, there was clear interest at at least two of the cluster
hackers meetings...

There've been people for a long while asking about triggers on catalogs
for that purpose. IIRC Jan was one of them.

My impression, based on something Christopher Brown said a few years
ago, is that Slony's DDL trigger needs are largely satisfied by the
existing event trigger stuff. It would be helpful to get confirmation
as to whether that's the case.

Oh, that's contrary to what I remember, but yes, it'd be interesting to
hear about htat.

On the flip side, the *cost* of pushing it into core is that
it now becomes the PostgreSQL's community problem to update the
deparsing code every time the grammar changes. That cost-benefit
trade-off does not look favorable to me, especially given the absence
of any kind of robust testing framework.

I agree that there should be testing in this.

But I think you're quite overstating the effort of maintaining
this. Looking at gram.y between the stamping of 9.3 devel and 9.4 devel
for commits that'd require DDL deparsing changes:
* ALTER TABLE .. ALTER CONSTRAINT: About 7 lines for the deparse code,
out of a ~350 line patch
* REFRESH MATERIALIZED VIEW ... CONCURRENTLY: About 1 line for deparse,
out of ~700
* WITH CHECK OPTION for views: About 3 lines for deparse, out of ~1300
* REPLICA IDENTITY: About 24 lines for deparse, out of ~950
* ALTER TABLESPACE MOVE: About 20 lines for deparse, out of
~340. Although that patch was essentially scrapped afterwards. And
rewritten differently.
* CREATE TABLESPACE ... WITH ...: Not supported by event triggers right
now as it's a global object.

That's really not a whole lot.

Those are pretty minor syntax patches, though.

Sure, but these are all the ones from the stamping of 9.3devel to
9.4devel. I wanted to look at a release cycle, and that seemed the
easiest way to do it. I didn't choose that cycle, because I knew it was
"light" on ddl, I chose it because it was the last complete one.

I think it's more helpful to look at things like the row-level
security stuff, or materialized views per se, or DDL support for
collations.

I can't imagine that writing the relatively straight forward stuff that
deparsing requires is a noticeable part of a patch like RLS. The actual
coding in anything bigger really is the minor part - especially the
relatively trivial bits. And for prototyping you can just leave it out,
just as frequently done today for pg_dump (which usually is noticeably
more complex).
I can't imagine the MATERIALIZED VIEW stuff to be much different. The
CREATE MATERIALIZED VIEW stuff is basically CREATE VIEW with three extra
clauses. I really can't see that mattering that much in the course of a
4.5k line patch.

The same with collations. The first patch adding collations to
table/index columns, just needs to add print the collation name - that's
it. The patch for the collation DDL support needs to add CREATE
COLLATION (~27 lines, including newlines and function header). DROP is
handled generically. Most of ALTER is as well, and SET SCHEMA can't be
very hard.

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

#27Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#24)

On Sat, Nov 8, 2014 at 4:35 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I don't understand why this is particularly difficult to regresssion
test. It actually is comparatively simple?

If it is, great. I previously wrote this email:

/messages/by-id/CA+TgmoZ=vZriJMxLkqi_V0jg4k4LEAPmwUSC6RWXS5MquXUJNA@mail.gmail.com

Alvaro came up with a way of addressing the second point I raised
there, which I'm quite pleased about, but AFAIK there's been no
progress on the first one. Maybe I missed something?

I unfortunately don't think so. And that sounds like a completely
reasonable criticism.

I'm glad you agree. If you can find a way to address that point, I
can live with the rest of it. I don't think it's dumb to be concerned
about features that increase the cost of adding more features. But
what really concerns me is that that code won't be well-tested, and if
there are cases missing or somebody forgets to do it altogether, it's
very likely that we won't notice. That seems like a huge problem from
where I sit.

--
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

#28Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#26)

On Sat, Nov 8, 2014 at 6:24 PM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2014-11-08 17:49:30 -0500, Robert Haas wrote:

Patch 2 adds support for GRANT and REVOKE to the event trigger
mechanism. I wonder if it's a bad idea to make the
ddl_command_start/end events fire for DCL. We discussed a lot of
these issues when this patch originally went in, and I think it'd be
worth revisiting that discussion.

Well, it doesn't generally support it for all GRANT statement, but just
for ones that are database local. I think that mostly skirts the
problems from the last round of discussion. But I only vaguely remember
them.

The issue I was alluding to was terminological: it's not clear that
GRANT and REVOKE should be called DDL rather than DCL, although we do
have precedent in some of the logging settings.

The other issue I remember is that if you have a separate event
trigger for GRANT/REVOKE, you can expose fields like operation/object
type/permission/target. That's much harder to do for an event trigger
that's very broad.

But anyway, I think it would be worth going back and looking at the
previous discussion.

As I said, there was clear interest at at least two of the cluster
hackers meetings...

We need to get some better data here.

But again, my core concern is that we have no good way to test this
code for bugs, including of omission, without which I think we will be
very sad.

--
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

#29Christopher Browne
cbbrowne@gmail.com
In reply to: Robert Haas (#25)

On 8 November 2014 17:49, Robert Haas <robertmhaas@gmail.com> wrote:

We could just integrate those parts, and be done with it. But would that
actually be a good thing for the community? Then slony needs to do it
and potentially others as well? Then auditing can't use it? Then
potential schema tracking solutions can't use it?

Do you think Slony is really going to use this? I guess we can let
the Slony guys speak for themselves, but I've been skeptical since day
one that this is the best way to do DDL replication, and I still am.
There are lots of ways that a replicated DDL statement can fail on the
replicas, and what are you going to do then? It's too late to show
the user the error message, so you can throw it in a log someplace and
hope that somebody notices, but that's it. It makes a lot more sense
to me to use some kind of a tool that applies the DDL in a coordinated
fashion on all nodes - or even just do it manually, since it might
very well be desirable to take the lock on different nodes at widely
different times, separated by a switchover. I certainly think there's
a use-case for what you're trying to do here, but I don't think it'll
be right for everyone.

Certainly, if the Slony guys - or some other team building an
out-of-core replication solutions says, hey, we really want this in
core, that would considerably strengthen the argument for putting it
there. But I haven't heard anyone say that yet - unlike logical
decoding, were we did have other people expressing clear interest in
using it.

There've been people for a long while asking about triggers on catalogs
for that purpose. IIRC Jan was one of them.

My impression, based on something Christopher Brown said a few years
ago, is that Slony's DDL trigger needs are largely satisfied by the
existing event trigger stuff. It would be helpful to get confirmation
as to whether that's the case.

I'm not sure that a replication system that intends to do partial
replication
(e.g. - being selective of what objects are to be replicated) will
necessarily
want to use the CREATE event triggers to capture creates.

Several cases pop up with different answers:
a) I certainly don't want to replicate temporary tables
b) I almost certainly don't want to replicate unlogged tables
c) For "more ordinary" tables, I'm not sure I want to extend Slony
to detect them and add them automatically, because there
are annoying sub-cases

c.1) If I'm working on data conversion, I may create not totally
temporary tables that are nonetheless not worthy to replicate.
(I'm working on such right now)

Long and short: it seems likely that I'd frequently NOT want all new tables
added to replication, at least not all of them, all the time.

What would seem valuable, to me, would be to have a CREATE event
trigger that lets me know the OID and/or fully qualified name of the new
object so that perhaps the replication system:

a) Has some kind of rule system to detect if it wants to replicate it,

b) Logs the change so a human might know later that there's new stuff
that probably ought to be replicated

c) Perhaps a human might put replication into a new "suggestive"
mode, a bit akin to Slony's "EXECUTE SCRIPT", but where the human
essentially says, "Here, I'm running DDL against this connection for a
while, and I'd be grateful if Postgres told Slony to capture all the new
tables and sequences and replicated them."

There are kind of two approaches:

a) Just capture the OIDs, and have replication go back later and grab
the table definition once the dust clears on the master

b) We need to capture ALL the DDL, whether CREATE or ALTER, and
forward it, altered to have fully qualified names on everything so that
we don't need to duplicate all the "set search_path" requests and
such.

I suppose there's also a third...

c) Have a capability to put an event trigger function in place that makes
DDL requests fail.

That's more useful than you'd think; if, by default, we make them fail,
and with an error messages such as
"DDL request failed as it was not submitted using slonik DDL TOOL"

then we have protection against uncontrolled application of DDL.

DDL TOOL would switch off the "fail trigger", possibly trying to
capture the DDL, or perhaps just capturing the statements passed
to it so they get passed everywhere. (That heads back to a) and b);
what should get captured...)

I'm not sure that all of that is totally internally coherent, but I hope
there
are some ideas worth thinking about.
--
When confronted by a difficult problem, solve it by reducing it to the
question, "How would the Lone Ranger handle this?"

#30Petr Jelinek
petr@2ndquadrant.com
In reply to: Christopher Browne (#29)

On 10/11/14 23:37, Christopher Browne wrote:

On 8 November 2014 17:49, Robert Haas <robertmhaas@gmail.com

My impression, based on something Christopher Brown said a few years
ago, is that Slony's DDL trigger needs are largely satisfied by the
existing event trigger stuff. It would be helpful to get confirmation
as to whether that's the case.

I'm not sure that a replication system that intends to do partial
replication
(e.g. - being selective of what objects are to be replicated) will
necessarily
want to use the CREATE event triggers to capture creates.

Several cases pop up with different answers:
a) I certainly don't want to replicate temporary tables
b) I almost certainly don't want to replicate unlogged tables
c) For "more ordinary" tables, I'm not sure I want to extend Slony
to detect them and add them automatically, because there
are annoying sub-cases

c.1) If I'm working on data conversion, I may create not totally
temporary tables that are nonetheless not worthy to replicate.
(I'm working on such right now)

Long and short: it seems likely that I'd frequently NOT want all new tables
added to replication, at least not all of them, all the time.

I don't see how this is problem with using CREATE event triggers, just
put logic in your trigger that handles this, you get the object identity
of the object that is being created/altered so you can get any info
about it you wish and you can easily filter however you want.

There are kind of two approaches:

a) Just capture the OIDs, and have replication go back later and grab
the table definition once the dust clears on the master

b) We need to capture ALL the DDL, whether CREATE or ALTER, and
forward it, altered to have fully qualified names on everything so that
we don't need to duplicate all the "set search_path" requests and
such.

This is basically what this patch gives you (actually both the canonized
command and the identity)?

I suppose there's also a third...

c) Have a capability to put an event trigger function in place that makes
DDL requests fail.

That's more useful than you'd think; if, by default, we make them fail,
and with an error messages such as
"DDL request failed as it was not submitted using slonik DDL TOOL"

You can do that already, it's even the example in the event trigger
documentation.

--
Petr Jelinek 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

#31Andres Freund
andres@2ndquadrant.com
In reply to: Christopher Browne (#29)

On 2014-11-10 17:37:40 -0500, Christopher Browne wrote:

On 8 November 2014 17:49, Robert Haas <robertmhaas@gmail.com> wrote:

We could just integrate those parts, and be done with it. But would that
actually be a good thing for the community? Then slony needs to do it
and potentially others as well? Then auditing can't use it? Then
potential schema tracking solutions can't use it?

Do you think Slony is really going to use this? I guess we can let
the Slony guys speak for themselves, but I've been skeptical since day
one that this is the best way to do DDL replication, and I still am.
There are lots of ways that a replicated DDL statement can fail on the
replicas, and what are you going to do then? It's too late to show
the user the error message, so you can throw it in a log someplace and
hope that somebody notices, but that's it. It makes a lot more sense
to me to use some kind of a tool that applies the DDL in a coordinated
fashion on all nodes - or even just do it manually, since it might
very well be desirable to take the lock on different nodes at widely
different times, separated by a switchover. I certainly think there's
a use-case for what you're trying to do here, but I don't think it'll
be right for everyone.

Certainly, if the Slony guys - or some other team building an
out-of-core replication solutions says, hey, we really want this in
core, that would considerably strengthen the argument for putting it
there. But I haven't heard anyone say that yet - unlike logical
decoding, were we did have other people expressing clear interest in
using it.

There've been people for a long while asking about triggers on catalogs
for that purpose. IIRC Jan was one of them.

My impression, based on something Christopher Brown said a few years
ago, is that Slony's DDL trigger needs are largely satisfied by the
existing event trigger stuff. It would be helpful to get confirmation
as to whether that's the case.

I'm not sure that a replication system that intends to do partial
replication
(e.g. - being selective of what objects are to be replicated) will
necessarily
want to use the CREATE event triggers to capture creates.

Several cases pop up with different answers:
a) I certainly don't want to replicate temporary tables
b) I almost certainly don't want to replicate unlogged tables

Those are quite easy to recognize and skip.

c) For "more ordinary" tables, I'm not sure I want to extend Slony
to detect them and add them automatically, because there
are annoying sub-cases

c.1) If I'm working on data conversion, I may create not totally
temporary tables that are nonetheless not worthy to replicate.
(I'm working on such right now)

Sure. you might not want to do it automatically all the time - but I
think it's a very useful default mode. Once you can replicate CREATEs
per se, it's easy to add logic (in a couple lines of plpgsql or
whatever) to only do so in a certain schema or similar.

But the main reason all this is interesting isn't so much CREATE
itself. But that it can be (and Alvaro has mostly done it!) for ALTER as
well. And there it imo becomes really interesting. Because you can quite
easily check whether the affected relation is being replicated you can
just emit the DDL when that's the case. And that makes DDL in a
logically replicated setup *much* easier.

Long and short: it seems likely that I'd frequently NOT want all new tables
added to replication, at least not all of them, all the time.

Agreed. That's quite possible with the design here - you get the
creation commands and can decide whether you want to do anything with
them. You're not forced to insert them into your replication queue or
whatever you're using for that.

What would seem valuable, to me, would be to have a CREATE event
trigger that lets me know the OID and/or fully qualified name of the new
object so that perhaps the replication system:

a) Has some kind of rule system to detect if it wants to replicate it,

Sure.

b) Logs the change so a human might know later that there's new stuff
that probably ought to be replicated

Sure.

c) Perhaps a human might put replication into a new "suggestive"
mode, a bit akin to Slony's "EXECUTE SCRIPT", but where the human
essentially says, "Here, I'm running DDL against this connection for a
while, and I'd be grateful if Postgres told Slony to capture all the new
tables and sequences and replicated them."

Sure.

Some of that already is possible with the current event triggers - and
all of it would be possible with the suggested functionality here.

An old version of bdr, employing the functionality presented here, had
the following (simplified) event trigger:

CREATE OR REPLACE FUNCTION bdr.queue_commands()
RETURNS event_trigger
LANGUAGE plpgsql
AS $function$
DECLARE
r RECORD;
BEGIN
-- don't recursively log ddl commands
IF pg_replication_identifier_is_replaying() THEN
RETURN;
END IF;

IF current_setting('bdr.skip_ddl_replication')::boolean THEN
RETURN;
END IF;

FOR r IN SELECT * FROM pg_event_trigger_get_creation_commands()
LOOP
/* ignore temporary objects */
IF r.schema = 'pg_temp' THEN
CONTINUE;
END IF;

/* ignore objects that are part of an extension */
IF r.in_extension THEN
CONTINUE;
END IF;

INSERT INTO bdr.bdr_queued_commands(
lsn, queued_at,
command_tag, command, executed
)
VALUES (
pg_current_xlog_location(),
NOW(),
r.command_tag,
pg_catalog.pg_event_trigger_expand_command(r.command),
'false'
);

IF r.command_tag = 'CREATE TABLE' and r.object_type = 'table' THEN
EXECUTE 'CREATE TRIGGER truncate_trigger AFTER TRUNCATE ON ' ||
r.identity ||
' FOR EACH STATEMENT EXECUTE PROCEDURE bdr.queue_truncate()';
END IF;
END LOOP;
END;
$function$;

It seems to me that'd pretty much allow all of your wishes above?

There are kind of two approaches:

a) Just capture the OIDs, and have replication go back later and grab
the table definition once the dust clears on the master

That's problematic imo if there's further changes to the table
definition - not exactly a infrequent thing.

b) We need to capture ALL the DDL, whether CREATE or ALTER, and
forward it, altered to have fully qualified names on everything so that
we don't need to duplicate all the "set search_path" requests and
such.

That's essentially where this patch is going. The submission is only
CREATE, but once the design is agreed upon, ALTER is coming. The patch
we're currently using for BDR has a good chunk of ALTER support.

I suppose there's also a third...

c) Have a capability to put an event trigger function in place that makes
DDL requests fail.

That's actually already quite possible.

CREATE OR REPLACE FUNCTION prevent_ddl_outside_slonik()
RETURNS event_trigger
LANGUAGE plpgsql
AS $function$
BEGIN
IF current_setting('slony.inside_slonik')::boolean THEN
RETURN;
END IF;

RAISE ERROR 'hey there, use slonik!!!';

END $function$;

What's missing is that you probably want to look into the object to see
whether it's temporary, unlogged, et al.

That's more useful than you'd think; if, by default, we make them fail,
and with an error messages such as
"DDL request failed as it was not submitted using slonik DDL TOOL"

then we have protection against uncontrolled application of DDL.

DDL TOOL would switch off the "fail trigger", possibly trying to
capture the DDL, or perhaps just capturing the statements passed
to it so they get passed everywhere. (That heads back to a) and b);
what should get captured...)

I'm not sure that all of that is totally internally coherent, but I
hope there are some ideas worth thinking about.

I think it's actually quite coherent - and at least partially mirrors
the thoughts that have gone into this...

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

#32Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Andres Freund (#31)

On 11/10/14, 4:58 PM, Andres Freund wrote:

But the main reason all this is interesting isn't so much CREATE
itself. But that it can be (and Alvaro has mostly done it!) for ALTER as
well. And there it imo becomes really interesting. Because you can quite
easily check whether the affected relation is being replicated you can
just emit the DDL when that's the case. And that makes DDL in a
logically replicated setup*much* easier.

+1. Adding columns is a PITA, you have to manually ensure you do it on all slaves first.

Drop is somewhat worse, because you have to do it on the master first, opposite of the (more usual) case of adding a column.

RENAME is a complete disaster.

Handing scripts to your replication system to execute isn't a very good alternative either; it assumes that you actually have a script (bad assumption with ORMs), and that you have a reasonable way to get that script to wherever you run your replication system.

I will also weigh in that there are a LOT of cases that binary replication doesn't cover. I find it interesting that prior to creating built in replication, the community stance was "We won't build that because there's too many different use cases", but now some folks are saying that everyone should just use streaming rep and be done with it. :P
--
Jim Nasby, Data Architect, Blue Treble Consulting
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

#33Robert Haas
robertmhaas@gmail.com
In reply to: Jim Nasby (#32)

On Mon, Nov 10, 2014 at 9:02 PM, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:

+1. Adding columns is a PITA, you have to manually ensure you do it on all
slaves first.

Drop is somewhat worse, because you have to do it on the master first,
opposite of the (more usual) case of adding a column.

RENAME is a complete disaster.

Handing scripts to your replication system to execute isn't a very good
alternative either; it assumes that you actually have a script (bad
assumption with ORMs), and that you have a reasonable way to get that script
to wherever you run your replication system.

I don't disagree with any of that, but running the command on the
master and then propagating it to the slaves where it may succeed or
fail - and if it fails, you won't know unless you're watching the logs
on those machines, and, oh by the way, replication will also be broken
- is not good either. We would never have shipped physical
replication solution with that kind of limitation. What has made
streaming replication so popular and successful with PostgreSQL users
over the last five years is that, while it's a bit of a pain to get
set up, once you have it set up, it is rock-solid. If there were a
series of legal SQL commands that you could execute without error on a
cluster of servers connected by streaming replication such that, when
you got done, replication was broken, our users would scream bloody
murder, or just stop using PostgreSQL. I think the approach to DDL
replication that Alvaro, Andres, et al. are proposing here is
absolutely fine - even praiseworthy - as an out-of-core solution that
users can adopt if they are willing to accept the associated risks, as
many users probably will be. But you wouldn't convince me to run it
on any production system for which I was responsible.

--
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

#34Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#33)

On 2014-11-12 16:36:30 -0500, Robert Haas wrote:

On Mon, Nov 10, 2014 at 9:02 PM, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:

+1. Adding columns is a PITA, you have to manually ensure you do it on all
slaves first.

Drop is somewhat worse, because you have to do it on the master first,
opposite of the (more usual) case of adding a column.

RENAME is a complete disaster.

Handing scripts to your replication system to execute isn't a very good
alternative either; it assumes that you actually have a script (bad
assumption with ORMs), and that you have a reasonable way to get that script
to wherever you run your replication system.

I don't disagree with any of that, but running the command on the
master and then propagating it to the slaves where it may succeed or
fail - and if it fails, you won't know unless you're watching the logs
on those machines, and, oh by the way, replication will also be broken
- is not good either.

That's already the situation today with all the logical replication
solutions. They *constantly* break in the field. Most commonly because
of DDL differences.

I don't understand why you think it's likely for logical replication to
break due to this? You mean because deparse yielded a invalid statement?
In a normal single master setup there really shouldn't be scenarios
where that happens? Except bugs - but as you know we had more than in
HS/SR as well?

Or are you worried about stuff like ALTER TABLE ... USING()? I think
that's the replication solution's job to take care of/prevent.

For multimaster the situation is more complex, I agree, but I don't
think in core stuff needs to solve that for now?

We are thinking about extending 2PC to be usable across logical
decoding. That's a relatively simple patch. Then it's possible to do the
DDL on the primary, ship it to the standby, apply it there, and only
afterwards commit the prepared xact if that was successfull. That's
quite cool - but somewhat in the remit of the replication solution.

I think the approach to DDL
replication that Alvaro, Andres, et al. are proposing here is
absolutely fine - even praiseworthy - as an out-of-core solution that
users can adopt if they are willing to accept the associated risks, as
many users probably will be. But you wouldn't convince me to run it
on any production system for which I was responsible.

The solution here doesn't force you to do that, does it? It's something
that can be used by more than replication solution?

I just don't see the alternative you're proposing? I've so far not even
seen a credible *sketch* of an alternative design that also can handle
ALTER. The only current alternatives are 1) the user inserts some
events into the queue manually. If they depend on any local state you're
screwed. If they have syntax errors they're often screwed. 2). The user
does all actions on the standby first. Then on the primary. That's hard
for ALTER ADD COLUMN and similar, and just about impossible for renaming
things.

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

#35Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#34)

On Wed, Nov 12, 2014 at 4:58 PM, Andres Freund <andres@2ndquadrant.com> wrote:

That's already the situation today with all the logical replication
solutions. They *constantly* break in the field. Most commonly because
of DDL differences.

Right. And that's why it's cool that logical decoding can operate
through DDL differences. The apply side might not be able to cope
with what pops out, but that's not logical decoding's fault, and
different apply-sides can adopt different policies as to how to deal
with whatever problems crop up.

I don't understand why you think it's likely for logical replication to
break due to this? You mean because deparse yielded a invalid statement?
In a normal single master setup there really shouldn't be scenarios
where that happens? Except bugs - but as you know we had more than in
HS/SR as well?

I don't know exactly what you mean by "a normal single master setup".
Surely the point of logical decoding is that the replica might not be
identical to the master. And if it isn't, then a command that
succeeded on the master might fail on the standby - for example,
because an object by that name already exists there, or because a type
doesn't exist there. (Even if you replicate a CREATE EXTENSION
command, there's no guarantee that the .so exists on the target.) Then
what?

This is basically the same problem as multi-master replication
conflicts, except with DDL. Resolving replication conflicts is not a
very easy thing to get right even if you're only concerned about the
rows in the tables. It's probably harder if you're worried about the
schema, too.

We are thinking about extending 2PC to be usable across logical
decoding. That's a relatively simple patch. Then it's possible to do the
DDL on the primary, ship it to the standby, apply it there, and only
afterwards commit the prepared xact if that was successfull. That's
quite cool - but somewhat in the remit of the replication solution.

That would certainly make the user aware of a quite a few kinds of
errors that might otherwise go undetected.

The solution here doesn't force you to do that, does it? It's something
that can be used by more than replication solution?

In theory, yes.

I just don't see the alternative you're proposing? I've so far not even
seen a credible *sketch* of an alternative design that also can handle
ALTER. The only current alternatives are 1) the user inserts some
events into the queue manually. If they depend on any local state you're
screwed. If they have syntax errors they're often screwed. 2). The user
does all actions on the standby first. Then on the primary. That's hard
for ALTER ADD COLUMN and similar, and just about impossible for renaming
things.

It's a really hard problem.

I don't think it's possible to make statement-based replication
no-fail. Physical replication is basically no-fail because it just
says, hey, go write these bytes into this page, and we can pretty much
always do that. But statement-based logical replication means
basically executing arbitrary chunks of code all over the backend, and
there is just no way to guarantee that code won't throw an error. So
the best we can do is to hope that those errors will get reported back
to the user, which is going to require some kind of distributed
transaction. Your idea to just run the replicated DDL statements on
the standby before committing on the master is one approach to that
problem, and probably the simplest one, but not the only one - one can
imagine something that resembles true clustering, for example.

By the way, the fact that you're planning to do log-based replication
of DML and trigger-based replication of DDL scares the crap out of me.
I'm not sure how that's going to work at all if the two are
interleaved in the same transaction. Also, relying on triggers for
replication is generally not awesome, because it increases
administrative complexity. Event triggers are probably better in that
regard than ordinary triggers, because they're database-wide, but I
don't think they solve the problem completely. But the thing that
scares me even more is that the DDL replication is not only
trigger-based, but statement-based. Why don't we do logical
replication by recording all of the SQL statements that run on the
master and re-executing them on the standby? Well, because we all
know that there will be plenty of important cases where that doesn't
yield the same results on both servers. There's no intrinsic reason
why that shouldn't also be a problem for DDL replication, and indeed
it is. This patch set is trying to patch around that by finding a way
to emit a revised DDL statement that is guaranteed to do exactly the
same thing on both machines, and it's probably possible to do that in
most cases, but it's probably not possible to do that in all cases.

To be clear, none of this is a reason to reject this patch set; I've
explained my reasons for being unhappy with it elsewhere, and I think
they are valid, but these are not them. These are just ruminations on
the difficulty of doing truly robust DDL replication.

--
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

#36Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#35)

On 2014-11-13 07:17:32 -0500, Robert Haas wrote:

On Wed, Nov 12, 2014 at 4:58 PM, Andres Freund <andres@2ndquadrant.com> wrote:

That's already the situation today with all the logical replication
solutions. They *constantly* break in the field. Most commonly because
of DDL differences.

Right. And that's why it's cool that logical decoding can operate
through DDL differences. The apply side might not be able to cope
with what pops out, but that's not logical decoding's fault, and
different apply-sides can adopt different policies as to how to deal
with whatever problems crop up.

I think pretty much all of the solutions just say "oops, you're on your
own". And I can't blame them for that. Once there's a schema difference
and it causes problem there's really not much that can be done.

I don't understand why you think it's likely for logical replication to
break due to this? You mean because deparse yielded a invalid statement?
In a normal single master setup there really shouldn't be scenarios
where that happens? Except bugs - but as you know we had more than in
HS/SR as well?

I don't know exactly what you mean by "a normal single master setup".
Surely the point of logical decoding is that the replica might not be
identical to the master.

I actually think that that's not primary the point if you talk about
individual objects. The majority of objects will be exactly the same on
all nodes. If you actually want to have differening objects on the nodes
you'll have to opt out/in (depending on your solution) of ddl
replication for those objects.

And if it isn't, then a command that
succeeded on the master might fail on the standby - for example,
because an object by that name already exists there, or because a type
doesn't exist there. (Even if you replicate a CREATE EXTENSION
command, there's no guarantee that the .so exists on the target.) Then
what?

Sure. There's reasons logical replication isn't always a win. But I
don't see why that's a reason not to make it as robust as possible.

Btw, the .so problem exists for wal shipping as as well.

This is basically the same problem as multi-master replication
conflicts, except with DDL. Resolving replication conflicts is not a
very easy thing to get right even if you're only concerned about the
rows in the tables. It's probably harder if you're worried about the
schema, too.

I don't think it's a sane thing to do multimaster with differing schemas
for individual relations, except maybe additional nonunique indexes.

We are thinking about extending 2PC to be usable across logical
decoding. That's a relatively simple patch. Then it's possible to do the
DDL on the primary, ship it to the standby, apply it there, and only
afterwards commit the prepared xact if that was successfull. That's
quite cool - but somewhat in the remit of the replication solution.

That would certainly make the user aware of a quite a few kinds of
errors that might otherwise go undetected.

It is (or rather would be) a generally quite cool feature imo ;)

The solution here doesn't force you to do that, does it? It's something
that can be used by more than replication solution?

In theory, yes.

What's the practical point here?

I just don't see the alternative you're proposing? I've so far not even
seen a credible *sketch* of an alternative design that also can handle
ALTER. The only current alternatives are 1) the user inserts some
events into the queue manually. If they depend on any local state you're
screwed. If they have syntax errors they're often screwed. 2). The user
does all actions on the standby first. Then on the primary. That's hard
for ALTER ADD COLUMN and similar, and just about impossible for renaming
things.

It's a really hard problem.

I don't think it's possible to make statement-based replication
no-fail.

I think generally logical replication has more failure cases than
physical ones. Which you seem to agree with.

Physical replication is basically no-fail because it just
says, hey, go write these bytes into this page, and we can pretty much
always do that. But statement-based logical replication means
basically executing arbitrary chunks of code all over the backend, and
there is just no way to guarantee that code won't throw an error. So
the best we can do is to hope that those errors will get reported back
to the user, which is going to require some kind of distributed
transaction. Your idea to just run the replicated DDL statements on
the standby before committing on the master is one approach to that
problem, and probably the simplest one, but not the only one - one can
imagine something that resembles true clustering, for example.

I think that's generally not what people need for primary/standby
cases (of subsets of tables). In practice there aren't many failures
like that besides schema differences. And there's just many usecases
that aren't doable with physical replication, so we can't advise people
doing that.

By the way, the fact that you're planning to do log-based replication
of DML and trigger-based replication of DDL scares the crap out of me.
I'm not sure how that's going to work at all if the two are
interleaved in the same transaction.

Maybe that's based on a misunderstanding. All the event trigger does is
insert a event into a (local) queue. That's then shipped to the other
side using the same replication mechanisms as used for rows. Then, when
receiving changes in that ddl queue the standby performs those actions
before continuing with the replay.
That makes the interleaving on the standby to be pretty much the same as
on the primary.

Also, relying on triggers for
replication is generally not awesome, because it increases
administrative complexity. Event triggers are probably better in that
regard than ordinary triggers, because they're database-wide, but I
don't think they solve the problem completely.

Maybe. It's not that hard to make the DDL solution record dependencies
against the event triggers to prohibit the user from dropping them or
such if that's your worry.

We can easily (additionally) define "implicit" event triggers that are
declared using a hook. Then some replication extension can just force
them to be applied.

But the thing that
scares me even more is that the DDL replication is not only
trigger-based, but statement-based. Why don't we do logical
replication by recording all of the SQL statements that run on the
master and re-executing them on the standby? Well, because we all
know that there will be plenty of important cases where that doesn't
yield the same results on both servers. There's no intrinsic reason
why that shouldn't also be a problem for DDL replication, and indeed
it is. This patch set is trying to patch around that by finding a way
to emit a revised DDL statement that is guaranteed to do exactly the
same thing on both machines, and it's probably possible to do that in
most cases, but it's probably not possible to do that in all cases.

Yes, it's not trivial. And I think there's some commands where you might
not want to try but either scream ERROR or just rereplicate the whole
relation or such.

I very strongly feel that we (as postgres devs) have a *much* higher
chance of recognizing these cases than either some random users (that
write slonik scripts or similar) or some replication solution authors
that aren't closely involved with -hackers.

These are just ruminations on the difficulty of doing truly robust DDL
replication.

Right, it's far from easy. But imo that's an argument for providing the
tools to do it as robust as we can in core.

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

#37Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#36)

On Thu, Nov 13, 2014 at 7:45 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Right. And that's why it's cool that logical decoding can operate
through DDL differences. The apply side might not be able to cope
with what pops out, but that's not logical decoding's fault, and
different apply-sides can adopt different policies as to how to deal
with whatever problems crop up.

I think pretty much all of the solutions just say "oops, you're on your
own". And I can't blame them for that. Once there's a schema difference
and it causes problem there's really not much that can be done.

Agreed.

I don't know exactly what you mean by "a normal single master setup".
Surely the point of logical decoding is that the replica might not be
identical to the master.

I actually think that that's not primary the point if you talk about
individual objects. The majority of objects will be exactly the same on
all nodes. If you actually want to have differening objects on the nodes
you'll have to opt out/in (depending on your solution) of ddl
replication for those objects.

I'm not saying it's the primary point; I'm just saying that it can happen.

And if it isn't, then a command that
succeeded on the master might fail on the standby - for example,
because an object by that name already exists there, or because a type
doesn't exist there. (Even if you replicate a CREATE EXTENSION
command, there's no guarantee that the .so exists on the target.) Then
what?

Sure. There's reasons logical replication isn't always a win. But I
don't see why that's a reason not to make it as robust as possible.

I agree.

Btw, the .so problem exists for wal shipping as as well.

Not in the same way. You might not be able to access the data on the
standby if the .so defining the datatype isn't present, but the
replication itself doesn't care.

This is basically the same problem as multi-master replication
conflicts, except with DDL. Resolving replication conflicts is not a
very easy thing to get right even if you're only concerned about the
rows in the tables. It's probably harder if you're worried about the
schema, too.

I don't think it's a sane thing to do multimaster with differing schemas
for individual relations, except maybe additional nonunique indexes.

But that's exactly what you ARE doing, isn't it? I mean, if you
replicate in only one direction, nothing keeps someone from modifying
things on the replica independently of BDR, and if you replicate in
both directions, well that's multi-master.

The solution here doesn't force you to do that, does it? It's something
that can be used by more than replication solution?

In theory, yes.

What's the practical point here?

I am quite doubtful about whether there will ever be a second, working
implementation, so I see all of this code - and the maintenance effort
associated with it - as something that will really only benefit BDR.
I understand that you don't see it that way, and I'm not saying that
you are offering anything in bad faith, but it looks to me like even
with all of this very substantial amount of infrastructure, you're
still going to need a big pile of additional code inside BDR to
actually make it work, and I don't hear anyone else offering to
develop something similar.

I think generally logical replication has more failure cases than
physical ones. Which you seem to agree with.

Yes, I agree with that.

Physical replication is basically no-fail because it just
says, hey, go write these bytes into this page, and we can pretty much
always do that. But statement-based logical replication means
basically executing arbitrary chunks of code all over the backend, and
there is just no way to guarantee that code won't throw an error. So
the best we can do is to hope that those errors will get reported back
to the user, which is going to require some kind of distributed
transaction. Your idea to just run the replicated DDL statements on
the standby before committing on the master is one approach to that
problem, and probably the simplest one, but not the only one - one can
imagine something that resembles true clustering, for example.

I think that's generally not what people need for primary/standby
cases (of subsets of tables). In practice there aren't many failures
like that besides schema differences. And there's just many usecases
that aren't doable with physical replication, so we can't advise people
doing that.

I can't really follow this, especially the first sentence.

By the way, the fact that you're planning to do log-based replication
of DML and trigger-based replication of DDL scares the crap out of me.
I'm not sure how that's going to work at all if the two are
interleaved in the same transaction.

Maybe that's based on a misunderstanding. All the event trigger does is
insert a event into a (local) queue. That's then shipped to the other
side using the same replication mechanisms as used for rows. Then, when
receiving changes in that ddl queue the standby performs those actions
before continuing with the replay.
That makes the interleaving on the standby to be pretty much the same as
on the primary.

OK. But that's also not something that I can really imagine us ever
adopting in core. If we were going to do DDL replication in core, I
have to believe we'd find a way to put all of the information in the
WAL stream, not use triggers.

Yes, it's not trivial. And I think there's some commands where you might
not want to try but either scream ERROR or just rereplicate the whole
relation or such.

I very strongly feel that we (as postgres devs) have a *much* higher
chance of recognizing these cases than either some random users (that
write slonik scripts or similar) or some replication solution authors
that aren't closely involved with -hackers.

It's clearly the job of the replication solution authors to figure
these details out. I'm not going to get into the business of passing
judgement on the relative competence of different groups of
replication developers. There's no question that 2ndQuadrant is doing
great, exciting work, but we should not tilt the playing field in a
way that crowds other people out, even new players that haven't yet
written their first line of code.

Right, it's far from easy. But imo that's an argument for providing the
tools to do it as robust as we can in core.

I agree with that on general principle, but I do not have confidence
in this patch set as the best foundation for DDL replication. You're
talking about layering a lot of other complexity on top of the
deparsing stuff in this patch to produce a usable solution, and even
then I am not really convinced that it's going to be the final
solution to this problem. Maybe it will, but I think there are enough
chinks in the armor to be really, really cautious about being certain
this is the right road to go down.

--
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

#38Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#37)

On 2014-11-14 12:38:52 -0500, Robert Haas wrote:

This is basically the same problem as multi-master replication
conflicts, except with DDL. Resolving replication conflicts is not a
very easy thing to get right even if you're only concerned about the
rows in the tables. It's probably harder if you're worried about the
schema, too.

I don't think it's a sane thing to do multimaster with differing schemas
for individual relations, except maybe additional nonunique indexes.

But that's exactly what you ARE doing, isn't it? I mean, if you
replicate in only one direction, nothing keeps someone from modifying
things on the replica independently of BDR, and if you replicate in
both directions, well that's multi-master.

Meh. By that definition any logical replication solution does multi
master replication. Either you tell your users that that's not allowed,
or you just prevent it by technical means. Absolutely the same is true
for table contents.

FWIW, in BDR we *do* prevent schemas from being modified independently
on different nodes (unless you set the 'running with scissors' guc).

I am quite doubtful about whether there will ever be a second, working
implementation, so I see all of this code - and the maintenance effort
associated with it - as something that will really only benefit BDR.
I understand that you don't see it that way, and I'm not saying that
you are offering anything in bad faith, but it looks to me like even
with all of this very substantial amount of infrastructure, you're
still going to need a big pile of additional code inside BDR to
actually make it work, and I don't hear anyone else offering to
develop something similar.

I don't know what to say about this. I don't see any other realistic way
to perform DDL replication in logical rep, and nearly all my
conversations with users have indicated that they want that.

I think it's a good idea to structure independent features in a way that
other solutions can reuse them. But I sure as hell can't force them to
use it - especially as there's unfortunately not too much development
going on in the existing logical replication solutions for postgres.

Btw, I really think there's quite legitimate use cases for this besides
logical replication solutions - e.g. schema tracking is quite a sensible
use case.

By the way, the fact that you're planning to do log-based replication
of DML and trigger-based replication of DDL scares the crap out of me.
I'm not sure how that's going to work at all if the two are
interleaved in the same transaction.

Maybe that's based on a misunderstanding. All the event trigger does is
insert a event into a (local) queue. That's then shipped to the other
side using the same replication mechanisms as used for rows. Then, when
receiving changes in that ddl queue the standby performs those actions
before continuing with the replay.
That makes the interleaving on the standby to be pretty much the same as
on the primary.

OK. But that's also not something that I can really imagine us ever
adopting in core.

Well, that bit really depends on what the user (e.g. a replication
solution, or a schema tracking feature) needs. There's certain things
that you can quite easily do as part of core (e.g. insert something into
the WAL stream), that you just can't as external code.

I don't think there's any external replication solution that won't have
some form of internal queue to manipulate its internal state. For an
external solution such a queue currently pretty much has to be some
table, but for an eventual in core solution it could be done
differently.

If we were going to do DDL replication in core, I have to believe we'd
find a way to put all of the information in the WAL stream, not use
triggers.

I agree that we might want to represent the transport to standbys
differently for something in core. That there's many different ways the
deparsing output could be used imo is a good reason why that part of the
mechanism isn't part of this submission.

I don't really understand the arguments against triggers in general
though. We're already using them quite extensively - I don't see why DDL
replication has to meet some completely different bar than say foreign
key checks or deferred uniqueness checks. We easily can add 'implicit'
event triggers, that aren't defined inside the catalog if we feel like
it. I'm just not sure we really would need/want to.

Yes, it's not trivial. And I think there's some commands where you might
not want to try but either scream ERROR or just rereplicate the whole
relation or such.

I very strongly feel that we (as postgres devs) have a *much* higher
chance of recognizing these cases than either some random users (that
write slonik scripts or similar) or some replication solution authors
that aren't closely involved with -hackers.

It's clearly the job of the replication solution authors to figure
these details out. I'm not going to get into the business of passing
judgement on the relative competence of different groups of
replication developers.

It's not a question of relative competence. It's a question of the focus
of that competence. There's just not many reasons a replication solution
developer needs to have detailed knowledge about postgres' internal
parsetrees for DDL statements. It's not like extensive documentation
about their meaning exists.

There's no question that 2ndQuadrant is doing
great, exciting work, but we should not tilt the playing field in a
way that crowds other people out, even new players that haven't yet
written their first line of code.

Huh? I'm not seing how I'm doing that in any way.

Right, it's far from easy. But imo that's an argument for providing the
tools to do it as robust as we can in core.

I agree with that on general principle, but I do not have confidence
in this patch set as the best foundation for DDL replication.

Can you please hint at some other workable design? I don't think there
really is anything else.

How should DDL be replicated in a logical replication solution but via
DDL? It's not like just replicating the catalog rows or anything like
that is something even remotely sane. As stated before, everything the
user has to do manually is *much* more error prone than what's presented
here.

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

#39Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#38)

On Fri, Nov 14, 2014 at 1:18 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I think it's a good idea to structure independent features in a way that
other solutions can reuse them. But I sure as hell can't force them to
use it - especially as there's unfortunately not too much development
going on in the existing logical replication solutions for postgres.

Btw, I really think there's quite legitimate use cases for this besides
logical replication solutions - e.g. schema tracking is quite a sensible
use case.

Well, as I already said, despite my doubts about the general utility
of this feature, I'm willing to see us take it IF we have a testing
framework that will reliably catch bugs, including bugs of omission.
Without that, I'm very confident it's going to be a maintenance
nightmare, and I believe you admitted yourself that that concern was
reasonable.

Can you please hint at some other workable design? I don't think there
really is anything else.

I think this really depends on what you mean by "anything else". Any
DDL replication solution is necessarily going to involve the following
steps:

1. Define some set of primitives such that any imaginable DDL
operation can be expressed as a series of those primitives.
2. Find a way to capture those events as they happen.
3. Serialize them into some wire format and transport that format to
the replica.
4. Apply them, possibly coordinating in some way with the master so
that the user's original request fails if the apply fails.

There are meaningful choices at every step. You're proposing that the
primitives should be "anything that can be expressed as a complete SQL
command against a single object" (I think - what are you going to emit
for an ALL IN SCHEMA op - that thing itself, or a similar operation
against each object in the schema?); that the capture mechanism should
be an event trigger that inserts into a queue table; that the
serialization format should be a JSON language designed to allow
reassembly of the corresponding SQL statement; and that the apply
coordination mechanism should be 2PC. But none of that is written in
stone.

As far as deciding what primitives to use, I think the principal
decision to be made here is as to the appropriate level of
granularity. For example, CREATE TABLE foo (a int DEFAULT 1, b int,
CHECK (b > 42)) could be emitted as a single event saying that a table
was created. But it could also be emitted as create-table (foo),
add-column (foo, a, int), add-column (foo, b, int), add-column-default
(a, 1), add-check-constraint (foo, b > 42). The appeal of a more
fine-grained set of primitives is that there might be fewer of them,
and that each of them might be simpler; one of the things that makes
physical replication so reliable is that its primitives are very
simple and thus easy to verify.

The possible event-capture mechanisms seem to be to have either (a)
event trigger or (b) a hook function in some yet-to-be-defined place
or (c) core code which will either (i) write each event to a table,
(ii) write each event directly into WAL, or perhaps (iii) write it
someplace else (file? in-memory queue? network socket?).

There are lots of possible serialization formats.

Coordinating with the master could involve 2PC, as you propose; or
trying to somehow validate that a given series of events is a valid
state transformation based on the starting state on the standby before
doing the operation on the master; or the use of a distributed
transaction coordinated by something like PG-XC's global transaction
manager; or you can skip it and hope for the best.

In addition to the decisions above, you can try to prevent failures by
restricting certain changes from happening, or you can let users
change what they like and hope for the best. Different solutions can
have different mechanisms for controlling which objects are under
replication and which changes are not; or even allowing some
individual DDL statements to opt out of replication while forcing
others to participate. Administratively, solutions can be built to
facilitate easy replication of an entire database to another node, or
more specific applications like sharding, where creating a table on a
master node creates child tables on a bunch of slave nodes, but
they're not all identical, because we're partitioning the data so that
only some of it will be on each node - thus the constraints and so on
will be different.

BDR has one set of answers to all of these questions, and despite my
worries about a few points here and there, they are not stupid
answers. But they are not the ONLY answers either.

--
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

#40Bruce Momjian
bruce@momjian.us
In reply to: Andres Freund (#19)

On Sat, Nov 8, 2014 at 05:56:00PM +0100, Andres Freund wrote:

On 2014-11-08 11:52:43 -0500, Tom Lane wrote:

Adding a similar
level of burden to support a feature with a narrow use-case seems like
a nonstarter from here.

I don't understand this statement. In my experience the lack of a usable
replication solution that allows temporary tables and major version
differences is one of the most, if not *the* most, frequent criticisms
of postgres I hear. How is this a narrow use case?

How would replicating DDL handle cases where the master and slave
servers have different major versions and the DDL is only supported by
the Postgres version on the master server? If it would fail, does this
limit the idea that logical replication allows major version-different
replication?

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ Everyone has their own god. +

--
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: Bruce Momjian (#40)

Bruce Momjian wrote:

On Sat, Nov 8, 2014 at 05:56:00PM +0100, Andres Freund wrote:

On 2014-11-08 11:52:43 -0500, Tom Lane wrote:

Adding a similar level of burden to support a feature with a
narrow use-case seems like a nonstarter from here.

I don't understand this statement. In my experience the lack of a
usable replication solution that allows temporary tables and major
version differences is one of the most, if not *the* most, frequent
criticisms of postgres I hear. How is this a narrow use case?

How would replicating DDL handle cases where the master and slave
servers have different major versions and the DDL is only supported by
the Postgres version on the master server?

Normally you would replicate between an older master and a newer
replica, so this shouldn't be an issue. I find it unlikely that we
would de-support some syntax that works in an older version: it would
break pg_dump, for one thing.

In other words I view cross-version replication as a mechanism to
upgrade, not something that you would use permanently. Once you
finish upgrading, promote the newer server and ditch the old master.

If it would fail, does this limit the idea that logical replication
allows major version-different replication?

Not in my view, at least.

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

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

#42Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#41)

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

Bruce Momjian wrote:

How would replicating DDL handle cases where the master and slave
servers have different major versions and the DDL is only supported by
the Postgres version on the master server?

Normally you would replicate between an older master and a newer
replica, so this shouldn't be an issue. I find it unlikely that we
would de-support some syntax that works in an older version: it would
break pg_dump, for one thing.

While I tend to agree with you that it's not something we're likely to
do, how would it break pg_dump? We have plenty of version-specific
logic in pg_dump and we could certainly generate a different syntax in
a newer version than we did in an older version, if the newer server was
expecting something different. We've always held that you should use
the version of pg_dump which match the server you are moving *to*, after
all.

In other words I view cross-version replication as a mechanism to
upgrade, not something that you would use permanently. Once you
finish upgrading, promote the newer server and ditch the old master.

I agree with this also.

If it would fail, does this limit the idea that logical replication
allows major version-different replication?

Not in my view, at least.

I'm all for having logical replication be a way to do major version
different replication (particularly for the purposes of upgrades), but
it shouldn't mean we can never de-support a given syntax.

As one example, we've discussed a few times removing certain table-level
privileges on the basis that they practically mean you own the table.
Perhaps that'll never actually happen, but if it does, logical
replication would need to deal with it.

Thanks,

Stephen

#43Bruce Momjian
bruce@momjian.us
In reply to: Stephen Frost (#42)

On Wed, Nov 26, 2014 at 09:01:13PM -0500, Stephen Frost wrote:

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

Bruce Momjian wrote:

How would replicating DDL handle cases where the master and slave
servers have different major versions and the DDL is only supported by
the Postgres version on the master server?

Normally you would replicate between an older master and a newer
replica, so this shouldn't be an issue. I find it unlikely that we
would de-support some syntax that works in an older version: it would
break pg_dump, for one thing.

I like the idea of older master/new replica, but what about
bidirectional replication?

Would the create/alter/drop WAL locical structure remain consistent
across major versions, or would the code have to read at least one older
version? Would we limit it to one?

While I tend to agree with you that it's not something we're likely to
do, how would it break pg_dump? We have plenty of version-specific
logic in pg_dump and we could certainly generate a different syntax in
a newer version than we did in an older version, if the newer server was
expecting something different. We've always held that you should use
the version of pg_dump which match the server you are moving *to*, after
all.

pg_upgrade avoids having to deal with major version changes by
leveraging pg_dump. Is it possible to have the new replica use the new
pg_dump to connect to the old master to get a CREATE command that it can
execute? Would that avoid having to ship CREATE syntax? What it
wouldn't help with is ALTER and DROP though. (We do have ALTER but I
think only for inheritance cases.)

In other words I view cross-version replication as a mechanism to
upgrade, not something that you would use permanently. Once you
finish upgrading, promote the newer server and ditch the old master.

I agree with this also.

If it would fail, does this limit the idea that logical replication
allows major version-different replication?

Not in my view, at least.

I'm all for having logical replication be a way to do major version
different replication (particularly for the purposes of upgrades), but
it shouldn't mean we can never de-support a given syntax.

As one example, we've discussed a few times removing certain table-level
privileges on the basis that they practically mean you own the table.
Perhaps that'll never actually happen, but if it does, logical
replication would need to deal with it.

Should we just tell the user not to modify the database schema during
this period? Should we have a server mode which disables DDL?

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ Everyone has their own god. +

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

#44Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#41)

Normally you would replicate between an older master and a newer
replica, so this shouldn't be an issue. I find it unlikely that we
would de-support some syntax that works in an older version: it would
break pg_dump, for one thing.

The most common way in which we break forward-compatibility is by
reserving additional keywords. Logical replication can deal with this
by quoting all identifiers, so it's not a big issue.

It's not the only possible issue; it is certainly conceivable that we
could change something in the server and then try to change pg_dump to
compensate. I think we wouldn't do that with anything very big, but
suppose spgist were supplanted by a new and better access method
tsigps. Well, we might figure that there are few enough people
accustomed to the current syntax that we can get away with hacking the
pg_dump output, and yes, anyone with an existing dump from an older
system will have problems, but there won't be many of them, and they
can always adjust the dump by hand. If we ever decided to do such a
thing, whatever syntax transformation we did in pg_dump would have to
be mimicked by any logical replication solution.

I think it's legitimate to say that this could be a problem, but I
think it probably won't be a problem very often, and I think when it
is a problem it probably won't be a very big problem, because we
already have good reasons not to break the ability to restore older
dumps on newer servers more often than absolutely necessary.

One of the somewhat disappointing things about this is that we're
talking about adding a lot of new deparsing code to the server that
probably, in some sense, duplicates pg_dump. Perhaps in an ideal
world there would be a way to avoid that, but in the world we really
live in, there probably isn't.

--
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

#45Michael Paquier
michael.paquier@gmail.com
In reply to: Bruce Momjian (#40)

On Thu, Nov 27, 2014 at 10:16 AM, Bruce Momjian <bruce@momjian.us> wrote:

On Sat, Nov 8, 2014 at 05:56:00PM +0100, Andres Freund wrote:

On 2014-11-08 11:52:43 -0500, Tom Lane wrote:

Adding a similar
level of burden to support a feature with a narrow use-case seems like
a nonstarter from here.

I don't understand this statement. In my experience the lack of a usable
replication solution that allows temporary tables and major version
differences is one of the most, if not *the* most, frequent criticisms
of postgres I hear. How is this a narrow use case?

How would replicating DDL handle cases where the master and slave
servers have different major versions and the DDL is only supported by
the Postgres version on the master server? If it would fail, does this
limit the idea that logical replication allows major version-different
replication?

Marking this patch as "Returned with feedback". Even with the
more-fundamental arguments of putting such replication solution
in-core or not (I am skeptical as well btw), on a
code-base-discussion-only I don't think that this patch is acceptable
as-is without more structure of jsonb to do on-memory manipulation of
containers (aka remove ObjTree stuff).
Regards,
--
Michael

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