0001-Deparser-for-Table-DDL-commands-and-exten-2023_03_30.patch

application/octet-stream

Filename: 0001-Deparser-for-Table-DDL-commands-and-exten-2023_03_30.patch
Type: application/octet-stream
Part: 1
Message: RE: Support logical replication of DDLs

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch 0001
Subject: Deparser for Table DDL commands and extending event triggers
File+
src/backend/commands/createas.c 10 0
src/backend/commands/ddl_deparse.c 3366 0
src/backend/commands/ddl_json.c 780 0
src/backend/commands/event_trigger.c 304 46
src/backend/commands/Makefile 2 0
src/backend/commands/meson.build 2 0
src/backend/commands/sequence.c 43 0
src/backend/commands/tablecmds.c 8 2
src/backend/parser/parse_utilcmd.c 1 0
src/backend/tcop/utility.c 109 0
src/backend/utils/adt/format_type.c 1 3
src/backend/utils/adt/ruleutils.c 21 10
src/backend/utils/cache/evtcache.c 2 0
src/include/catalog/pg_proc.dat 7 0
src/include/commands/event_trigger.h 49 1
src/include/commands/sequence.h 9 0
src/include/nodes/parsenodes.h 1 0
src/include/tcop/ddl_deparse.h 22 0
src/include/tcop/deparse_utility.h 11 1
src/include/tcop/utility.h 2 0
src/include/utils/builtins.h 1 0
src/include/utils/evtcache.h 2 1
src/include/utils/ruleutils.h 9 0
src/tools/pgindent/typedefs.list 4 0
From 32b2978ab82b4d0a0d016691253b6c23d2f30675 Mon Sep 17 00:00:00 2001
From: Wang Wei <wangw.fnst@fujitsu.com>
Date: Thu, 30 Mar 2023 13:50:22 +0800
Subject: [PATCH 1/5] Deparser for Table DDL commands and extending event
 triggers

This patch constructs 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.

This provides a base for logical replication of DDL statements. Currently,
the patch has support for:

CREATE/ALTER/DROP TABLE -- Note #1, Note #2

Note: For ATTACH/DETACH PARTITION, we haven't added extra logic on
the subscriber to handle the case where the table on the publisher is a PARTITIONED
TABLE while the target table on the subscriber side is a NORMAL table. We will
research this more and improve it later.
---
 src/backend/commands/Makefile        |    2 +
 src/backend/commands/createas.c      |   10 +
 src/backend/commands/ddl_deparse.c   | 3366 ++++++++++++++++++++++++++
 src/backend/commands/ddl_json.c      |  780 ++++++
 src/backend/commands/event_trigger.c |  350 ++-
 src/backend/commands/meson.build     |    2 +
 src/backend/commands/sequence.c      |   43 +
 src/backend/commands/tablecmds.c     |   10 +-
 src/backend/parser/parse_utilcmd.c   |    1 +
 src/backend/tcop/utility.c           |  109 +
 src/backend/utils/adt/format_type.c  |    4 +-
 src/backend/utils/adt/ruleutils.c    |   31 +-
 src/backend/utils/cache/evtcache.c   |    2 +
 src/include/catalog/pg_proc.dat      |    7 +
 src/include/commands/event_trigger.h |   50 +-
 src/include/commands/sequence.h      |    9 +
 src/include/nodes/parsenodes.h       |    1 +
 src/include/tcop/ddl_deparse.h       |   22 +
 src/include/tcop/deparse_utility.h   |   12 +-
 src/include/tcop/utility.h           |    2 +
 src/include/utils/builtins.h         |    1 +
 src/include/utils/evtcache.h         |    3 +-
 src/include/utils/ruleutils.h        |    9 +
 src/tools/pgindent/typedefs.list     |    4 +
 24 files changed, 4766 insertions(+), 64 deletions(-)
 create mode 100644 src/backend/commands/ddl_deparse.c
 create mode 100644 src/backend/commands/ddl_json.c
 create mode 100644 src/include/tcop/ddl_deparse.h

diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..171dfb2800 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -29,6 +29,8 @@ OBJS = \
 	copyto.o \
 	createas.o \
 	dbcommands.o \
+	ddl_deparse.o \
+	ddl_json.o \
 	define.o \
 	discard.o \
 	dropcmds.o \
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index e91920ca14..a7b22cb5db 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -34,6 +34,7 @@
 #include "catalog/namespace.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
+#include "commands/event_trigger.h"
 #include "commands/matview.h"
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
@@ -143,6 +144,15 @@ create_ctas_internal(List *attrList, IntoClause *into)
 		StoreViewQuery(intoRelationAddr.objectId, query, false);
 		CommandCounterIncrement();
 	}
+	else
+	{
+		/*
+		 * Fire the trigger for table_init_write after creating the table so
+		 * that we can access the catalog info about the newly created table
+		 * in the trigger function.
+		 */
+		EventTriggerTableInitWrite((Node *) create, intoRelationAddr);
+	}
 
 	return intoRelationAddr;
 }
diff --git a/src/backend/commands/ddl_deparse.c b/src/backend/commands/ddl_deparse.c
new file mode 100644
index 0000000000..9098220a9c
--- /dev/null
+++ b/src/backend/commands/ddl_deparse.c
@@ -0,0 +1,3366 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_deparse.c
+ *	  Functions to convert utility commands to machine-parseable
+ *	  representation
+ *
+ * Portions Copyright (c) 1996-2023, 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.
+ *
+ * Deparse object tree is created by using:
+ * 	a) new_objtree("know contents") where the complete tree content is known or
+ *     the initial tree content is known.
+ * 	b) new_objtree("") for the syntax where the object tree will be derived
+ *     based on some conditional checks.
+ * 	c) new_objtree_VA where the complete tree can be derived using some fixed
+ *     content or by using the initial tree content along with some variable
+ *     arguments.
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/ddl_deparse.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/relation.h"
+#include "access/table.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_conversion.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_extension.h"
+#include "catalog/pg_foreign_data_wrapper.h"
+#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_language.h"
+#include "catalog/pg_largeobject.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_policy.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_range.h"
+#include "catalog/pg_rewrite.h"
+#include "catalog/pg_sequence.h"
+#include "catalog/pg_statistic_ext.h"
+#include "catalog/pg_transform.h"
+#include "catalog/pg_ts_config.h"
+#include "catalog/pg_ts_dict.h"
+#include "catalog/pg_ts_parser.h"
+#include "catalog/pg_ts_template.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "commands/tablespace.h"
+#include "foreign/foreign.h"
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/optimizer.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/ddl_deparse.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/jsonb.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/* Estimated length of the generated jsonb string */
+#define JSONB_ESTIMATED_LEN 128
+
+/*
+ * Before they are turned into JSONB representation, each command is
+ * represented as an object tree, using the structs below.
+ */
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeInteger,
+	ObjTypeFloat,
+	ObjTypeObject
+} ObjType;
+
+/*
+ * Represent the command as an object tree.
+ */
+typedef struct ObjTree
+{
+	slist_head	params;			/* Object tree parameters */
+	int			numParams;		/* Number of parameters in the object tree */
+	StringInfo	fmtinfo;		/* Format string of the ObjTree */
+	bool		present;		/* Indicates if boolean value should be stored */
+} ObjTree;
+
+/*
+ * An element of an object tree (ObjTree).
+ */
+typedef struct ObjElem
+{
+	char	   *name;			/* Name of object element */
+	ObjType		objtype;		/* Object type */
+
+	union
+	{
+		bool		boolean;
+		char	   *string;
+		int64		integer;
+		float8		flt;
+		ObjTree    *object;
+		List	   *array;
+	}			value;			/* Store the object value based on the object
+								 * type */
+	slist_node	node;			/* Used in converting back to ObjElem
+								 * structure */
+} ObjElem;
+
+/*
+ * Reduce some unnecessary strings from the output json when verbose
+ * and "present" member is false. This means these strings won't be merged into
+ * the last DDL command.
+ */
+bool		verbose = true;
+
+static void append_array_object(ObjTree *tree, char *sub_fmt, List *array);
+static void append_bool_object(ObjTree *tree, char *sub_fmt, bool value);
+static void append_null_object(ObjTree *tree, char *sub_fmt);
+static void append_object_object(ObjTree *tree, char *sub_fmt, ObjTree *value);
+static char *append_object_to_format_string(ObjTree *tree, char *sub_fmt);
+static void append_premade_object(ObjTree *tree, ObjElem *elem);
+static void append_string_object(ObjTree *tree, char *sub_fmt, char *name,
+								 char *value);
+static void format_type_detailed(Oid type_oid, int32 typemod,
+								 Oid *nspid, char **typname, char **typemodstr,
+								 bool *typarray);
+static ObjElem *new_object(ObjType type, char *name);
+static ObjTree *new_objtree_for_qualname_id(Oid classId, Oid objectId);
+static ObjElem *new_object_object(ObjTree *value);
+static ObjTree *new_objtree_VA(char *fmt, int numobjs,...);
+static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+static char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
+									  List *dpcontext, List **exprs);
+
+static ObjTree *deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
+								  ColumnDef *coldef, bool is_alter, List **exprs);
+static ObjTree *deparse_ColumnIdentity(Oid seqrelid, char identity, bool alter_table);
+static ObjTree *deparse_ColumnSetOptions(AlterTableCmd *subcmd);
+
+static ObjTree *deparse_DefElem(DefElem *elem, bool is_reset);
+static ObjTree *deparse_OnCommitClause(OnCommitAction option);
+static ObjTree *deparse_RelSetOptions(AlterTableCmd *subcmd);
+
+static inline ObjElem *deparse_Seq_Cache(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_Cycle(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_IncrementBy(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_Minvalue(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_Maxvalue(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_Restart(int64 last_value);
+static inline ObjElem *deparse_Seq_Startwith(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_As(Form_pg_sequence seqdata);
+static inline ObjElem *deparse_Type_Storage(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Receive(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Send(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Typmod_In(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Typmod_Out(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Analyze(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Subscript(Form_pg_type typForm);
+
+static List *deparse_InhRelations(Oid objectId);
+static List *deparse_TableElements(Relation relation, List *tableElements, List *dpcontext,
+								   bool typed, bool composite);
+
+/*
+ * Append present as false to a tree.
+ */
+static void
+append_not_present(ObjTree *tree)
+{
+	append_bool_object(tree, "present", false);
+}
+
+/*
+ * Append an array parameter to a tree.
+ */
+static void
+append_array_object(ObjTree *tree, char *sub_fmt, List *array)
+{
+	ObjElem    *param;
+	char	   *object_name;
+
+	Assert(sub_fmt);
+
+	if (list_length(array) == 0)
+		return;
+
+	if (!verbose)
+	{
+		ListCell   *lc;
+
+		/* Remove elements where present flag is false */
+		foreach(lc, array)
+		{
+			ObjElem    *elem = (ObjElem *) lfirst(lc);
+
+			Assert(elem->objtype == ObjTypeObject ||
+				   elem->objtype == ObjTypeString);
+
+			if (!elem->value.object->present &&
+				elem->objtype == ObjTypeObject)
+				array = foreach_delete_current(array, lc);
+		}
+
+	}
+
+	/* Check for empty list after removing elements */
+	if (list_length(array) == 0)
+		return;
+
+	object_name = append_object_to_format_string(tree, sub_fmt);
+
+	param = new_object(ObjTypeArray, object_name);
+	param->value.array = array;
+	append_premade_object(tree, param);
+}
+
+/*
+ * Append a boolean parameter to a tree.
+ */
+static void
+append_bool_object(ObjTree *tree, char *sub_fmt, bool value)
+{
+	ObjElem    *param;
+	char	   *object_name = sub_fmt;
+	bool		is_present_flag = false;
+
+	Assert(sub_fmt);
+
+	/*
+	 * Check if the format string is 'present' and if yes, store the boolean
+	 * value
+	 */
+	if (strcmp(sub_fmt, "present") == 0)
+	{
+		is_present_flag = true;
+		tree->present = value;
+	}
+
+	if (!is_present_flag)
+		object_name = append_object_to_format_string(tree, sub_fmt);
+
+	param = new_object(ObjTypeBool, object_name);
+	param->value.boolean = value;
+	append_premade_object(tree, param);
+}
+
+/*
+ * Append the input format string to the ObjTree.
+ */
+static void
+append_format_string(ObjTree *tree, char *sub_fmt)
+{
+	int			len;
+	char	   *fmt;
+
+	if (tree->fmtinfo == NULL)
+		return;
+
+	fmt = tree->fmtinfo->data;
+	len = tree->fmtinfo->len;
+
+	/* Add a separator if necessary */
+	if (len > 0 && fmt[len - 1] != ' ')
+		appendStringInfoSpaces(tree->fmtinfo, 1);
+
+	appendStringInfoString(tree->fmtinfo, sub_fmt);
+}
+
+/*
+ * Append a NULL object to a tree.
+ */
+static void
+append_null_object(ObjTree *tree, char *sub_fmt)
+{
+	char	   *object_name;
+
+	Assert(sub_fmt);
+
+	if (!verbose)
+		return;
+
+	object_name = append_object_to_format_string(tree, sub_fmt);
+
+	append_premade_object(tree, new_object(ObjTypeNull, object_name));
+}
+
+/*
+ * Append an object parameter to a tree.
+ */
+static void
+append_object_object(ObjTree *tree, char *sub_fmt, ObjTree *value)
+{
+	ObjElem    *param;
+	char	   *object_name;
+
+	Assert(sub_fmt);
+
+	if (!verbose && !value->present)
+		return;
+
+	object_name = append_object_to_format_string(tree, sub_fmt);
+
+	param = new_object(ObjTypeObject, object_name);
+	param->value.object = value;
+	append_premade_object(tree, param);
+}
+
+/*
+ * Return the object name which is extracted from the input "*%{name[:.]}*"
+ * style string. And append the input format string to the ObjTree.
+ */
+static char *
+append_object_to_format_string(ObjTree *tree, char *sub_fmt)
+{
+	StringInfoData object_name;
+	const char *end_ptr, *start_ptr;
+	int         length;
+	char        *tmp_str;
+
+	if (sub_fmt == NULL || tree->fmtinfo == NULL)
+		return sub_fmt;
+
+	initStringInfo(&object_name);
+
+	start_ptr = strchr(sub_fmt, '{');
+	end_ptr = strchr(sub_fmt, ':');
+	if (end_ptr == NULL)
+		end_ptr = strchr(sub_fmt, '}');
+
+	if (start_ptr != NULL && end_ptr != NULL)
+	{
+		length = end_ptr - start_ptr - 1;
+		tmp_str = (char *) palloc(length + 1);
+		strncpy(tmp_str, start_ptr + 1, length);
+		tmp_str[length] = '\0';
+		appendStringInfoString(&object_name, tmp_str);
+		pfree(tmp_str);
+	}
+
+	if (object_name.len == 0)
+		elog(ERROR, "object name not found");
+
+	append_format_string(tree, sub_fmt);
+
+	return object_name.data;
+
+}
+
+/*
+ * 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++;
+}
+
+/*
+ * Append a string parameter to a tree.
+ */
+static void
+append_string_object(ObjTree *tree, char *sub_fmt, char * object_name,
+					 char *value)
+{
+	ObjElem    *param;
+
+	Assert(sub_fmt);
+
+	if (!verbose && (value == NULL || value[0] == '\0'))
+		return;
+
+	append_format_string(tree, sub_fmt);
+	param = new_object(ObjTypeString, object_name);
+	param->value.string = value;
+	append_premade_object(tree, param);
+}
+
+/*
+ * Similar to format_type_extended, 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; the caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name in this case.
+ *
+ * - typname is set to the type name, without quotes
+ *
+ * - typemodstr is set to the typemod, if any, as a string with parentheses
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+static void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typename, char **typemodstr,
+					 bool *typearray)
+{
+	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 with OID %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Check if it's a regular (variable length) array type.  As above,
+	 * fixed-length array types such as "name" shouldn't get deconstructed.
+	 */
+	array_base_type = typeform->typelem;
+
+	*typearray = (IsTrueArrayType(typeform) && typeform->typstorage != TYPSTORAGE_PLAIN);
+
+	if (*typearray)
+	{
+		/* 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 with OID %u", type_oid);
+
+		typeform = (Form_pg_type) GETSTRUCT(tuple);
+		type_oid = array_base_type;
+	}
+
+	/*
+	 * Special-case crock for types with strange typmod rules where we put
+	 * typemod at the middle of name (e.g. TIME(6) with time zone). We cannot
+	 * schema-qualify nor add quotes to the type name in these cases.
+	 */
+	*nspid = InvalidOid;
+
+	switch (type_oid)
+	{
+		case INTERVALOID:
+			*typename = pstrdup("INTERVAL");
+			break;
+		case TIMESTAMPTZOID:
+			if (typemod < 0)
+				*typename = pstrdup("TIMESTAMP WITH TIME ZONE");
+			else
+				/* otherwise, WITH TZ is added by typmod. */
+				*typename = pstrdup("TIMESTAMP");
+			break;
+		case TIMESTAMPOID:
+			*typename = pstrdup("TIMESTAMP");
+			break;
+		case TIMETZOID:
+			if (typemod < 0)
+				*typename = pstrdup("TIME WITH TIME ZONE");
+			else
+				/* otherwise, WITH TZ is added by typmod. */
+				*typename = pstrdup("TIME");
+			break;
+		case TIMEOID:
+			*typename = pstrdup("TIME");
+			break;
+		default:
+
+			/*
+			 * No additional processing is required for other types, so get
+			 * the type name and schema directly from the catalog.
+			 */
+			*nspid = typeform->typnamespace;
+			*typename = pstrdup(NameStr(typeform->typname));
+	}
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod("", typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
+/*
+ * Return the string representation of the given RELPERSISTENCE value.
+ */
+static char *
+get_persistence_str(char persistence)
+{
+	switch (persistence)
+	{
+		case RELPERSISTENCE_TEMP:
+			return "TEMPORARY";
+		case RELPERSISTENCE_UNLOGGED:
+			return "UNLOGGED";
+		case RELPERSISTENCE_PERMANENT:
+			return "";
+		default:
+			elog(ERROR, "unexpected persistence marking %c", persistence);
+			return "";			/* make compiler happy */
+	}
+}
+
+/*
+ * Return the string representation of the given storagetype value.
+ */
+static inline char *
+get_type_storage(char storagetype)
+{
+	switch (storagetype)
+	{
+		case 'p':
+			return "plain";
+		case 'e':
+			return "external";
+		case 'x':
+			return "extended";
+		case 'm':
+			return "main";
+		default:
+			elog(ERROR, "invalid storage specifier %c", storagetype);
+	}
+}
+
+/*
+ * Allocate a new parameter.
+ */
+static ObjElem *
+new_object(ObjType type, char *name)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+	param->name = name;
+	param->objtype = type;
+
+	return param;
+}
+
+/*
+ * Allocate a new object parameter.
+ */
+static ObjElem *
+new_object_object(ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = new_object(ObjTypeObject, NULL);
+	param->value.object = value;
+
+	return param;
+}
+
+/*
+ * Allocate a new object tree to store parameter values.
+ */
+static ObjTree *
+new_objtree(char *fmt)
+{
+	ObjTree    *params;
+
+	params = palloc0(sizeof(ObjTree));
+	params->present = true;
+	slist_init(&params->params);
+
+	if (fmt)
+	{
+		params->fmtinfo = makeStringInfo();
+		appendStringInfoString(params->fmtinfo, fmt);
+	}
+
+	return params;
+}
+
+/*
+ * A helper routine to set up %{}D and %{}O elements.
+ *
+ * Elements "schema_name" and "obj_name" 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 obj_name 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;
+
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+
+	qualified = new_objtree_VA(NULL, 2,
+							   "schemaname", ObjTypeString, namespace,
+							   "objname", ObjTypeString, pstrdup(name));
+
+	return qualified;
+}
+
+/*
+ * A helper routine to set up %{}D and %{}O elements, with the object specified
+ * by classId/objId.
+ */
+static ObjTree *
+new_objtree_for_qualname_id(Oid classId, Oid objectId)
+{
+	ObjTree    *qualified;
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		obj_nsp;
+	Datum		obj_name;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	AttrNumber	Anum_oid = get_object_attnum_oid(classId);
+	bool		isnull;
+
+	catalog = table_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, Anum_oid, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object with OID %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	obj_nsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "null namespace for object %u", objectId);
+
+	obj_name = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "null attribute name for object %u", objectId);
+
+	qualified = new_objtree_for_qualname(DatumGetObjectId(obj_nsp),
+										 NameStr(*DatumGetName(obj_name)));
+	table_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{
+	Oid			typnspid;
+	char	   *type_nsp;
+	char	   *type_name = NULL;
+	char	   *typmodstr;
+	bool		type_array;
+
+	format_type_detailed(typeId, typmod,
+						 &typnspid, &type_name, &typmodstr, &type_array);
+
+	if (OidIsValid(typnspid))
+		type_nsp = get_namespace_name_or_temp(typnspid);
+	else
+		type_nsp = pstrdup("");
+
+	return new_objtree_VA(NULL, 4,
+						  "schemaname", ObjTypeString, type_nsp,
+						  "typename", ObjTypeString, type_name,
+						  "typmod", ObjTypeString, typmodstr,
+						  "typarray", ObjTypeBool, type_array);
+}
+
+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one, a
+ * name (string), type (from the ObjType enum) and value must be supplied.  The
+ * value must match the type given; for instance, ObjTypeInteger requires an
+ * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of
+ * ObjElem), ObjTypeObject requires an ObjTree, and so on.  Each element type *
+ * must match the conversion specifier given in the format string, as described
+ * in ddl_deparse_expand_command, q.v.
+ *
+ * Note we don't have the luxury of sprintf-like compiler warnings for
+ * malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree(fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		char	   *name;
+		ObjType		type;
+		ObjElem    *elem;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+		elem = new_object(type, NULL);
+
+		/*
+		 * For all param types other than ObjTypeNull, there must be a value in
+		 * the varargs. Fetch it and add the fully formed subobject into the
+		 * main object.
+		 */
+		switch (type)
+		{
+			case ObjTypeNull:
+				/* Null params don't have a value (obviously) */
+				break;
+			case ObjTypeBool:
+				elem->value.boolean = va_arg(args, int);
+				break;
+			case ObjTypeString:
+				elem->value.string = va_arg(args, char *);
+				break;
+			case ObjTypeArray:
+				elem->value.array = va_arg(args, List *);
+				break;
+			case ObjTypeInteger:
+				elem->value.integer = va_arg(args, int);
+				break;
+			case ObjTypeFloat:
+				elem->value.flt = va_arg(args, double);
+				break;
+			case ObjTypeObject:
+				elem->value.object = va_arg(args, ObjTree *);
+				break;
+			default:
+				elog(ERROR, "invalid ObjTree element type %d", type);
+		}
+
+		elem->name = name;
+		append_premade_object(tree, elem);
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/*
+ * Process the pre-built format string from the ObjTree into the output parse
+ * state.
+ */
+static void
+objtree_fmt_to_jsonb_element(JsonbParseState *state, ObjTree *tree)
+{
+	JsonbValue	key;
+	JsonbValue	val;
+
+	if (tree->fmtinfo == NULL)
+		return;
+
+	/* Push the key first */
+	key.type = jbvString;
+	key.val.string.val = "fmt";
+	key.val.string.len = strlen(key.val.string.val);
+	pushJsonbValue(&state, WJB_KEY, &key);
+
+	/* Then process the pre-built format string */
+	val.type = jbvString;
+	val.val.string.len = tree->fmtinfo->len;
+	val.val.string.val = tree->fmtinfo->data;
+	pushJsonbValue(&state, WJB_VALUE, &val);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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)
+{
+	JsonbValue	val;
+
+	switch (object->objtype)
+	{
+		case ObjTypeNull:
+			val.type = jbvNull;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeString:
+			val.type = jbvString;
+			val.val.string.len = strlen(object->value.string);
+			val.val.string.val = object->value.string;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeInteger:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeFloat:
+			val.type = jbvNumeric;
+			val.val.numeric = (Numeric)
+				DatumGetNumeric(DirectFunctionCall1(float8_numeric,
+													object->value.integer));
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeBool:
+			val.type = jbvBool;
+			val.val.boolean = object->value.boolean;
+			pushJsonbValue(&state, elem_token, &val);
+			break;
+
+		case ObjTypeObject:
+			/* Recursively add the object into the existing parse state */
+			objtree_to_jsonb_rec(object->value.object, state);
+			break;
+
+		case ObjTypeArray:
+			{
+				ListCell   *cell;
+
+				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);
+
+	objtree_fmt_to_jsonb_element(state, tree);
+
+	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);
+}
+
+/*
+ * 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    *constr;
+	Oid			relid;
+
+	/* Only one may be valid */
+	Assert(OidIsValid(relationId) ^ OidIsValid(domainId));
+
+	relid = OidIsValid(relationId) ? ConstraintRelidTypidNameIndexId :
+			ConstraintTypidIndexId;
+
+	/*
+	 * Scan pg_constraint to fetch all constraints linked to the given
+	 * relation.
+	 */
+	conRel = table_open(ConstraintRelationId, AccessShareLock);
+	ScanKeyInit(&key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(relationId));
+	scan = systable_beginscan(conRel, relid, 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.
+		 */
+		constr = 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(constrForm->oid));
+		elements = lappend(elements, new_object_object(constr));
+	}
+
+	systable_endscan(scan);
+	table_close(conRel, AccessShareLock);
+
+	return elements;
+}
+
+/*
+ * Obtain the deparsed default value for the given column of the given table.
+ *
+ * Caller must have set a correct deparse context.
+ */
+static char *
+RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext,
+						 List **exprs)
+{
+	Node	   *defval;
+	char	   *defstr;
+
+	defval = build_column_default(rel, attno);
+	defstr = deparse_expression(defval, dpcontext, false, false);
+
+	/* Collect the expression for later replication safety checks */
+	if (exprs)
+		*exprs = lappend(*exprs, defval);
+
+	return defstr;
+}
+
+/*
+ * Obtain the deparsed partition bound expression for the given table.
+ */
+static char *
+RelationGetPartitionBound(Oid relid)
+{
+	Datum		deparsed;
+	Datum		boundDatum;
+	bool		isnull;
+	HeapTuple	tuple;
+
+	tuple = SearchSysCache1(RELOID, relid);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation with OID %u", relid);
+
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+
+	deparsed = DirectFunctionCall2(pg_get_expr,
+								   CStringGetTextDatum(TextDatumGetCString(boundDatum)),
+								   relid);
+
+	ReleaseSysCache(tuple);
+
+	return TextDatumGetCString(deparsed);
+}
+
+/*
+ * 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).
+ *
+ * Verbose syntax
+ * %{name}I %{coltype}T %{compression}s %{default}s %{not_null}s %{collation}s
+ */
+static ObjTree *
+deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
+				  ColumnDef *coldef, bool is_alter, List **exprs)
+{
+	ObjTree    *ret;
+	ObjTree    *tmp_obj;
+	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);
+
+	ret = new_objtree_VA("%{name}I %{coltype}T", 3,
+						 "type", ObjTypeString, "column",
+						 "name", ObjTypeString, coldef->colname,
+						 "coltype", ObjTypeObject,
+						 new_objtree_for_type(typid, typmod));
+
+	if (!composite)
+		append_string_object(ret, "STORAGE %{colstorage}s", "colstorage",
+							 get_type_storage(attrForm->attstorage));
+
+	/* USING clause */
+	tmp_obj = new_objtree("COMPRESSION");
+	if (coldef->compression)
+		append_string_object(tmp_obj, "%{compression_method}I",
+							 "compression_method", coldef->compression);
+	else
+	{
+		append_null_object(tmp_obj, "%{compression_method}I");
+		append_not_present(tmp_obj);
+	}
+	append_object_object(ret, "%{compression}s", tmp_obj);
+
+	tmp_obj = new_objtree("COLLATE");
+	if (OidIsValid(typcollation))
+		append_object_object(tmp_obj, "%{name}D",
+							 new_objtree_for_qualname_id(CollationRelationId,
+														 typcollation));
+	else
+		append_not_present(tmp_obj);
+	append_object_object(ret, "%{collation}s", tmp_obj);
+
+	if (!composite)
+	{
+		Oid			seqrelid = InvalidOid;
+
+		/*
+		 * 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; we must not emit a NOT NULL
+		 * constraint in that case, unless explicitly specified.  Therefore,
+		 * we scan the list of constraints attached to this column to
+		 * determine whether we need to emit anything. (Fortunately, NOT NULL
+		 * constraints cannot be table constraints.)
+		 *
+		 * In the ALTER TABLE cases, we also add a NOT NULL if the colDef is
+		 * marked is_not_null.
+		 */
+		saw_notnull = false;
+		foreach(cell, coldef->constraints)
+		{
+			Constraint *constr = (Constraint *) lfirst(cell);
+
+			if (constr->contype == CONSTR_NOTNULL)
+			{
+				saw_notnull = true;
+				break;
+			}
+		}
+
+		if (is_alter && coldef->is_not_null)
+			saw_notnull = true;
+
+		append_string_object(ret, "%{not_null}s", "not_null",
+							 saw_notnull ? "NOT NULL" : "");
+
+		tmp_obj = new_objtree("DEFAULT");
+		if (attrForm->atthasdef &&
+			coldef->generated != ATTRIBUTE_GENERATED_STORED)
+		{
+			char	   *defstr;
+
+			defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+											  dpcontext, exprs);
+
+			append_string_object(tmp_obj, "%{default}s", "default", defstr);
+		}
+		else
+			append_not_present(tmp_obj);
+		append_object_object(ret, "%{default}s", tmp_obj);
+
+		/* IDENTITY COLUMN */
+		if (coldef->identity)
+		{
+			Oid			attno = get_attnum(relid, coldef->colname);
+
+			seqrelid = getIdentitySequence(relid, attno, true);
+			if (OidIsValid(seqrelid) && coldef->identitySequence)
+				seqrelid = RangeVarGetRelid(coldef->identitySequence, NoLock, false);
+		}
+
+		if (OidIsValid(seqrelid))
+		{
+			tmp_obj = deparse_ColumnIdentity(seqrelid, coldef->identity, is_alter);
+			append_object_object(ret, "%{identity_column}s", tmp_obj);
+		}
+
+		/* GENERATED COLUMN EXPRESSION */
+		tmp_obj = new_objtree("GENERATED ALWAYS AS");
+		if (coldef->generated == ATTRIBUTE_GENERATED_STORED)
+		{
+			char	   *defstr;
+
+			defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+											  dpcontext, exprs);
+			append_string_object(tmp_obj, "(%{generation_expr}s) STORED",
+								 "generation_expr", defstr);
+		}
+		else
+			append_not_present(tmp_obj);
+
+		append_object_object(ret, "%{generated_column}s", tmp_obj);
+	}
+
+	ReleaseSysCache(attrTup);
+
+	return ret;
+}
+
+/*
+ * 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.
+ *
+ * Verbose syntax
+ * %{name}I WITH OPTIONS %{not_null}s %{default}s.
+ */
+static ObjTree *
+deparse_ColumnDef_typed(Relation relation, List *dpcontext, ColumnDef *coldef)
+{
+	ObjTree    *ret = NULL;
+	ObjTree    *tmp_obj;
+	Oid			relid = RelationGetRelid(relation);
+	HeapTuple	attrTup;
+	Form_pg_attribute attrForm;
+	Oid			typid;
+	int32		typmod;
+	Oid			typcollation;
+	bool		saw_notnull;
+	ListCell   *cell;
+
+	attrTup = SearchSysCacheAttName(relid, coldef->colname);
+	if (!HeapTupleIsValid(attrTup))
+		elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
+			 coldef->colname, relid);
+	attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
+
+	get_atttypetypmodcoll(relid, attrForm->attnum,
+						  &typid, &typmod, &typcollation);
+
+	/*
+	 * Search for a NOT NULL declaration. As in deparse_ColumnDef, we rely on
+	 * finding a constraint on the column rather than coldef->is_not_null.
+	 * (This routine is never used for ALTER cases.)
+	 */
+	saw_notnull = false;
+	foreach(cell, coldef->constraints)
+	{
+		Constraint *constr = (Constraint *) lfirst(cell);
+
+		if (constr->contype == CONSTR_NOTNULL)
+		{
+			saw_notnull = true;
+			break;
+		}
+	}
+
+	if (!saw_notnull && !attrForm->atthasdef)
+	{
+		ReleaseSysCache(attrTup);
+		return NULL;
+	}
+
+	tmp_obj = new_objtree("DEFAULT");
+	if (attrForm->atthasdef)
+	{
+		char	   *defstr;
+
+		defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+										  dpcontext, NULL);
+
+		append_string_object(tmp_obj, "%{default}s", "default", defstr);
+	}
+	else
+		append_not_present(tmp_obj);
+
+	ret = new_objtree_VA("%{name}I WITH OPTIONS %{not_null}s %{default}s", 4,
+						 "type", ObjTypeString, "column",
+						 "name", ObjTypeString, coldef->colname,
+						 "not_null", ObjTypeString,
+						 saw_notnull ? "NOT NULL" : "",
+						 "default", ObjTypeObject, tmp_obj);
+
+	/* Generated columns are not supported on typed tables, so we are done */
+
+	ReleaseSysCache(attrTup);
+
+	return ret;
+}
+
+/*
+ * Deparse the definition of column identity.
+ *
+ * Verbose syntax
+ * SET GENERATED %{option}s %{identity_type}s %{seq_definition: }s
+ * 	OR
+ * GENERATED %{option}s AS IDENTITY %{identity_type}s ( %{seq_definition: }s )
+ */
+static ObjTree *
+deparse_ColumnIdentity(Oid seqrelid, char identity, bool alter_table)
+{
+	ObjTree    *ret;
+	ObjTree    *ident_obj;
+	List	   *elems = NIL;
+	Form_pg_sequence seqform;
+	Sequence_values *seqvalues;
+	char	   *identfmt;
+	char	   *objfmt;
+
+	if (alter_table)
+	{
+		identfmt = "SET GENERATED ";
+		objfmt = "%{option}s";
+	}
+	else
+	{
+		identfmt = "GENERATED ";
+		objfmt = "%{option}s AS IDENTITY";
+	}
+
+	ident_obj = new_objtree(identfmt);
+
+	if (identity == ATTRIBUTE_IDENTITY_ALWAYS)
+		append_string_object(ident_obj, objfmt, "option", "ALWAYS");
+	else if (identity == ATTRIBUTE_IDENTITY_BY_DEFAULT)
+		append_string_object(ident_obj, objfmt, "option", "BY DEFAULT");
+	else
+		append_not_present(ident_obj);
+
+	ret = new_objtree_VA("%{identity_type}s", 1,
+						 "identity_type", ObjTypeObject, ident_obj);
+
+	seqvalues = get_sequence_values(seqrelid);
+	seqform = seqvalues->seqform;
+
+	/* Definition elements */
+	elems = lappend(elems, deparse_Seq_Cache(seqform, alter_table));
+	elems = lappend(elems, deparse_Seq_Cycle(seqform, alter_table));
+	elems = lappend(elems, deparse_Seq_IncrementBy(seqform, alter_table));
+	elems = lappend(elems, deparse_Seq_Minvalue(seqform, alter_table));
+	elems = lappend(elems, deparse_Seq_Maxvalue(seqform, alter_table));
+	elems = lappend(elems, deparse_Seq_Startwith(seqform, alter_table));
+	elems = lappend(elems, deparse_Seq_Restart(seqvalues->last_value));
+	/* We purposefully do not emit OWNED BY here */
+
+	if (alter_table)
+		append_array_object(ret, "%{seq_definition: }s", elems);
+	else
+		append_array_object(ret, "( %{seq_definition: }s )", elems);
+
+	return ret;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ *
+ * Verbose syntax
+ * ALTER COLUMN %{column}I RESET|SET (%{options:, }s)
+ */
+static ObjTree *
+deparse_ColumnSetOptions(AlterTableCmd *subcmd)
+{
+	List	   *sets = NIL;
+	ListCell   *cell;
+	ObjTree    *ret;
+	bool		is_reset = subcmd->subtype == AT_ResetOptions;
+
+	ret = new_objtree_VA("ALTER COLUMN %{column}I %{option}s", 2,
+						 "column", ObjTypeString, subcmd->name,
+						 "option", ObjTypeString, is_reset ? "RESET" : "SET");
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem    *elem;
+		ObjTree    *set;
+
+		elem = (DefElem *) lfirst(cell);
+		set = deparse_DefElem(elem, is_reset);
+		sets = lappend(sets, new_object_object(set));
+	}
+
+	Assert(sets);
+	append_array_object(ret, "(%{options:, }s)", sets);
+
+	return ret;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ *
+ * Verbose syntax
+ * RESET|SET (%{options:, }s)
+ */
+static ObjTree *
+deparse_RelSetOptions(AlterTableCmd *subcmd)
+{
+	List	   *sets = NIL;
+	ListCell   *cell;
+	bool		is_reset = subcmd->subtype == AT_ResetRelOptions;
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem    *elem;
+		ObjTree    *set;
+
+		elem = (DefElem *) lfirst(cell);
+		set = deparse_DefElem(elem, is_reset);
+		sets = lappend(sets, new_object_object(set));
+	}
+
+	Assert(sets);
+
+	return new_objtree_VA("%{set_reset}s (%{options:, }s)", 2,
+						  "set_reset", ObjTypeString, is_reset ? "RESET" : "SET",
+						  "options", ObjTypeArray, sets);
+}
+
+/*
+ * Deparse DefElems, as used e.g. by ALTER COLUMN ... SET, into a list of SET
+ * (...)  or RESET (...) contents.
+ *
+ * Verbose syntax
+ * %{label}s = %{value}L
+ */
+static ObjTree *
+deparse_DefElem(DefElem *elem, bool is_reset)
+{
+	ObjTree    *ret;
+	ObjTree    *optname = new_objtree("");
+
+	if (elem->defnamespace != NULL)
+		append_string_object(optname, "%{schema}I.", "schema",
+							 elem->defnamespace);
+
+	append_string_object(optname, "%{label}I", "label", elem->defname);
+
+	ret = new_objtree_VA("%{label}s", 1,
+						 "label", ObjTypeObject, optname);
+
+	if (!is_reset)
+		append_string_object(ret, "= %{value}L", "value",
+							 elem->arg ? defGetString(elem) :
+							 defGetBoolean(elem) ? "TRUE" : "FALSE");
+
+	return ret;
+}
+
+/*
+ * Deparse the INHERITS relations.
+ *
+ * Given a table OID, return a schema-qualified table list representing
+ * the parent tables.
+ */
+static List *
+deparse_InhRelations(Oid objectId)
+{
+	List	   *parents = NIL;
+	Relation	inhRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple;
+
+	inhRel = table_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);
+	table_close(inhRel, RowExclusiveLock);
+
+	return parents;
+}
+
+/*
+ * Deparse the ON COMMIT ... clause for CREATE ... TEMPORARY ...
+ *
+ * Verbose syntax
+ * ON COMMIT %{on_commit_value}s
+ */
+static ObjTree *
+deparse_OnCommitClause(OnCommitAction option)
+{
+	ObjTree    *ret  = new_objtree("ON COMMIT");
+	switch (option)
+	{
+		case ONCOMMIT_DROP:
+			append_string_object(ret, "%{on_commit_value}s",
+								 "on_commit_value", "DROP");
+			break;
+
+		case ONCOMMIT_DELETE_ROWS:
+			append_string_object(ret, "%{on_commit_value}s",
+								 "on_commit_value", "DELETE ROWS");
+			break;
+
+		case ONCOMMIT_PRESERVE_ROWS:
+			append_string_object(ret, "%{on_commit_value}s",
+								 "on_commit_value", "PRESERVE ROWS");
+			break;
+
+		case ONCOMMIT_NOOP:
+			append_null_object(ret, "%{on_commit_value}s");
+			append_not_present(ret);
+			break;
+	}
+
+	return ret;
+}
+
+/*
+ * Deparse the sequence CACHE option.
+ *
+ * Verbose syntax
+ * SET CACHE %{value}s
+ * OR
+ * CACHE %{value}
+ */
+static inline ObjElem *
+deparse_Seq_Cache(Form_pg_sequence seqdata, bool alter_table)
+{
+	ObjTree    *ret;
+	char	   *fmt;
+
+	fmt = alter_table ? "SET CACHE %{value}s" : "CACHE %{value}s";
+
+	ret = new_objtree_VA(fmt, 2,
+						 "clause", ObjTypeString, "cache",
+						 "value", ObjTypeString,
+						 psprintf(INT64_FORMAT, seqdata->seqcache));
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence CYCLE option.
+ *
+ * Verbose syntax
+ * SET %{no}s CYCLE
+ * OR
+ * %{no}s CYCLE
+ */
+static inline ObjElem *
+deparse_Seq_Cycle(Form_pg_sequence seqdata, bool alter_table)
+{
+	ObjTree    *ret;
+	char	   *fmt;
+
+	fmt = alter_table ? "SET %{no}s CYCLE" : "%{no}s CYCLE";
+
+	ret = new_objtree_VA(fmt, 2,
+						 "clause", ObjTypeString, "cycle",
+						 "no", ObjTypeString,
+						 seqdata->seqcycle ? "" : "NO");
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence INCREMENT BY option.
+ *
+ * Verbose syntax
+ * SET INCREMENT BY %{value}s
+ * OR
+ * INCREMENT BY %{value}s
+ */
+static inline ObjElem *
+deparse_Seq_IncrementBy(Form_pg_sequence seqdata, bool alter_table)
+{
+	ObjTree    *ret;
+	char	   *fmt;
+
+	fmt = alter_table ? "SET INCREMENT BY %{value}s" : "INCREMENT BY %{value}s";
+
+	ret = new_objtree_VA(fmt, 2,
+						 "clause", ObjTypeString, "seqincrement",
+						 "value", ObjTypeString,
+						 psprintf(INT64_FORMAT, seqdata->seqincrement));
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence MAXVALUE option.
+ *
+ * Verbose syntax
+ * SET MAXVALUE %{value}s
+ * OR
+ * MAXVALUE %{value}s
+ */
+static inline ObjElem *
+deparse_Seq_Maxvalue(Form_pg_sequence seqdata, bool alter_table)
+{
+	ObjTree    *ret;
+	char	   *fmt;
+
+	fmt = alter_table ? "SET MAXVALUE %{value}s" : "MAXVALUE %{value}s";
+
+	ret = new_objtree_VA(fmt, 2,
+						 "clause", ObjTypeString, "maxvalue",
+						 "value", ObjTypeString,
+						 psprintf(INT64_FORMAT, seqdata->seqmax));
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence MINVALUE option.
+ *
+ * Verbose syntax
+ * SET MINVALUE %{value}s
+ * OR
+ * MINVALUE %{value}s
+ */
+static inline ObjElem *
+deparse_Seq_Minvalue(Form_pg_sequence seqdata, bool alter_table)
+{
+	ObjTree    *ret;
+	char	   *fmt;
+
+	fmt = alter_table ? "SET MINVALUE %{value}s" : "MINVALUE %{value}s";
+
+	ret = new_objtree_VA(fmt, 2,
+						 "clause", ObjTypeString, "minvalue",
+						 "value", ObjTypeString,
+						 psprintf(INT64_FORMAT, seqdata->seqmin));
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence RESTART option.
+ *
+ * Verbose syntax
+ * RESTART %{value}s
+ */
+static inline ObjElem *
+deparse_Seq_Restart(int64 last_value)
+{
+	ObjTree    *ret;
+
+	ret = new_objtree_VA("RESTART %{value}s", 2,
+						 "clause", ObjTypeString, "restart",
+						 "value", ObjTypeString,
+						 psprintf(INT64_FORMAT, last_value));
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence AS option.
+ *
+ * Verbose syntax
+ * AS %{seqtype}T
+ */
+static inline ObjElem *
+deparse_Seq_As(Form_pg_sequence seqdata)
+{
+	ObjTree    *ret;
+
+	ret = new_objtree("AS");
+	if (OidIsValid(seqdata->seqtypid))
+		append_object_object(ret, "%{seqtype}T",
+							 new_objtree_for_type(seqdata->seqtypid, -1));
+	else
+		append_not_present(ret);
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence START WITH option.
+ *
+ * Verbose syntax
+ * SET START WITH %{value}s
+ * OR
+ * START WITH %{value}s
+ */
+static inline ObjElem *
+deparse_Seq_Startwith(Form_pg_sequence seqdata, bool alter_table)
+{
+	ObjTree    *ret;
+	char	   *fmt;
+
+	fmt = alter_table ? "SET START WITH %{value}s" : "START WITH %{value}s";
+
+	ret = new_objtree_VA(fmt, 2,
+						 "clause", ObjTypeString, "start",
+						 "value", ObjTypeString,
+						 psprintf(INT64_FORMAT, seqdata->seqstart));
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the type STORAGE option.
+ *
+ * Verbose syntax
+ * STORAGE=%{value}s
+ */
+static inline ObjElem *
+deparse_Type_Storage(Form_pg_type typForm)
+{
+	ObjTree    *ret;
+	ret = new_objtree_VA("STORAGE = %{value}s", 2,
+						 "clause", ObjTypeString, "storage",
+						 "value", ObjTypeString, get_type_storage(typForm->typstorage));
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the type RECEIVE option.
+ *
+ * Verbose syntax
+ * RECEIVE=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Receive(Form_pg_type typForm)
+{
+	ObjTree    *ret;
+
+	ret = new_objtree_VA("RECEIVE=", 1,
+						 "clause", ObjTypeString, "receive");
+	if (OidIsValid(typForm->typreceive))
+		append_object_object(ret, "%{procedure}D",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typreceive));
+	else
+		append_not_present(ret);
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the type SEND option.
+ *
+ * Verbose syntax
+ * SEND=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Send(Form_pg_type typForm)
+{
+	ObjTree    *ret;
+
+	ret = new_objtree_VA("SEND=", 1,
+						 "clause", ObjTypeString, "send");
+	if (OidIsValid(typForm->typsend))
+		append_object_object(ret, "%{procedure}D",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typsend));
+	else
+		append_not_present(ret);
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the type typmod_in option.
+ *
+ * Verbose syntax
+ * TYPMOD_IN=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Typmod_In(Form_pg_type typForm)
+{
+	ObjTree    *ret;
+
+	ret = new_objtree_VA("TYPMOD_IN=", 1,
+						 "clause", ObjTypeString, "typmod_in");
+	if (OidIsValid(typForm->typmodin))
+		append_object_object(ret, "%{procedure}D",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typmodin));
+	else
+		append_not_present(ret);
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the type typmod_out option.
+ *
+ * Verbose syntax
+ * TYPMOD_OUT=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Typmod_Out(Form_pg_type typForm)
+{
+	ObjTree    *ret;
+
+	ret = new_objtree_VA("TYPMOD_OUT=", 1,
+						 "clause", ObjTypeString, "typmod_out");
+	if (OidIsValid(typForm->typmodout))
+		append_object_object(ret, "%{procedure}D",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typmodout));
+	else
+		append_not_present(ret);
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the type analyze option.
+ *
+ * Verbose syntax
+ * ANALYZE=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Analyze(Form_pg_type typForm)
+{
+	ObjTree    *ret;
+
+	ret = new_objtree_VA("ANALYZE=", 1,
+						 "clause", ObjTypeString, "analyze");
+	if (OidIsValid(typForm->typanalyze))
+		append_object_object(ret, "%{procedure}D",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typanalyze));
+	else
+		append_not_present(ret);
+
+	return new_object_object(ret);
+}
+
+/*
+ * Deparse the type subscript option.
+ *
+ * Verbose syntax
+ * SUBSCRIPT=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Subscript(Form_pg_type typForm)
+{
+	ObjTree    *ret;
+
+	ret = new_objtree_VA("SUBSCRIPT=", 1,
+						 "clause", ObjTypeString, "subscript");
+	if (OidIsValid(typForm->typsubscript))
+		append_object_object(ret, "%{procedure}D",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typsubscript));
+	else
+		append_not_present(ret);
+
+	return new_object_object(ret);
+}
+
+/*
+ * 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 *
+deparse_TableElements(Relation relation, List *tableElements, List *dpcontext,
+					  bool typed, bool composite)
+{
+	List	   *elements = NIL;
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					ObjTree    *tree;
+
+					tree = typed ?
+						deparse_ColumnDef_typed(relation, dpcontext,
+												(ColumnDef *) elt) :
+						deparse_ColumnDef(relation, dpcontext,
+										  composite, (ColumnDef *) elt,
+										  false, NULL);
+					if (tree != NULL)
+						elements = lappend(elements, new_object_object(tree));
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+
+	return elements;
+}
+
+/*
+ * Deparse a CreateStmt (CREATE TABLE).
+ *
+ * Given a table OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D [OF
+ * %{of_type}T | PARTITION OF %{parent_identity}D] %{table_elements}s
+ * %{inherits}s %{partition_by}s %{access_method}s %{with_clause}s
+ * %{on_commit}s %{tablespace}s
+ */
+static ObjTree *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *ret;
+	ObjTree    *tmp_obj;
+	List	   *list = NIL;
+	ListCell   *cell;
+
+	ret = new_objtree_VA("CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D", 3,
+						 "persistence", ObjTypeString,
+						 get_persistence_str(relation->rd_rel->relpersistence),
+						 "if_not_exists", ObjTypeString,
+						 node->if_not_exists ? "IF NOT EXISTS" : "",
+						 "identity", ObjTypeObject,
+						 new_objtree_for_qualname(relation->rd_rel->relnamespace,
+												  RelationGetRelationName(relation)));
+
+	dpcontext = deparse_context_for(RelationGetRelationName(relation),
+									objectId);
+
+	/*
+	 * Typed tables and partitions 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 parentheses must not be emitted;
+	 * and also, typed tables do not allow for inheritance.
+	 */
+	if (node->ofTypename || node->partbound)
+	{
+		List	   *tableelts = NIL;
+
+		/*
+		 * We can't put table elements directly in the fmt string as an array
+		 * surrounded by parentheses 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.
+		 */
+		if (node->ofTypename)
+		{
+			tmp_obj = new_objtree_for_type(relation->rd_rel->reloftype, -1);
+			append_object_object(ret, "OF %{of_type}T", tmp_obj);
+		}
+		else
+		{
+			List	   *parents;
+			ObjElem    *elem;
+
+			parents = deparse_InhRelations(objectId);
+			elem = (ObjElem *) linitial(parents);
+
+			Assert(list_length(parents) == 1);
+
+			append_format_string(ret, "PARTITION OF");
+
+			append_object_object(ret, "%{parent_identity}D",
+								 elem->value.object);
+		}
+
+		tableelts = deparse_TableElements(relation, node->tableElts, dpcontext,
+										  true, /* typed table */
+										  false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+		tmp_obj = new_objtree("");
+		if (tableelts)
+			append_array_object(tmp_obj, "(%{elements:, }s)", tableelts);
+		else
+			append_not_present(tmp_obj);
+
+		append_object_object(ret, "%{table_elements}s", tmp_obj);
+	}
+	else
+	{
+		List	   *tableelts = NIL;
+
+		/*
+		 * There is no need to process LIKE clauses separately; they have
+		 * already been transformed into columns and constraints.
+		 */
+
+		/*
+		 * 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 = deparse_TableElements(relation, node->tableElts, dpcontext,
+										  false,	/* not typed table */
+										  false);	/* not composite */
+		tableelts = obtainConstraints(tableelts, objectId, InvalidOid);
+
+		if (tableelts)
+			append_array_object(ret, "(%{table_elements:, }s)", tableelts);
+		else
+			append_format_string(ret, "()");
+
+		/*
+		 * 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_obj = new_objtree("INHERITS");
+		if (node->inhRelations != NIL)
+			append_array_object(tmp_obj, "(%{parents:, }D)", deparse_InhRelations(objectId));
+		else
+		{
+			append_null_object(tmp_obj, "(%{parents:, }D)");
+			append_not_present(tmp_obj);
+		}
+		append_object_object(ret, "%{inherits}s", tmp_obj);
+	}
+
+	/* FOR VALUES clause */
+	if (node->partbound)
+	{
+		/*
+		 * Get pg_class.relpartbound. We cannot use partbound in the parsetree
+		 * directly as it's the original partbound expression which haven't
+		 * been transformed.
+		 */
+		append_string_object(ret, "%{partition_bound}s", "partition_bound",
+							 RelationGetPartitionBound(objectId));
+	}
+
+	/* PARTITION BY clause */
+	tmp_obj = new_objtree("PARTITION BY");
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		append_string_object(tmp_obj, "%{definition}s", "definition",
+							 pg_get_partkeydef_string(objectId));
+	else
+	{
+		append_null_object(tmp_obj, "%{definition}s");
+		append_not_present(tmp_obj);
+	}
+	append_object_object(ret, "%{partition_by}s", tmp_obj);
+
+	/* USING clause */
+	tmp_obj = new_objtree("USING");
+	if (node->accessMethod)
+		append_string_object(tmp_obj, "%{access_method}I", "access_method",
+							 node->accessMethod);
+	else
+	{
+		append_null_object(tmp_obj, "%{access_method}I");
+		append_not_present(tmp_obj);
+	}
+	append_object_object(ret, "%{access_method}s", tmp_obj);
+
+	/* WITH clause */
+	tmp_obj = new_objtree("WITH");
+
+	foreach(cell, node->options)
+	{
+		ObjTree    *tmp_obj2;
+		DefElem    *opt = (DefElem *) lfirst(cell);
+
+		tmp_obj2 = deparse_DefElem(opt, false);
+		list = lappend(list, new_object_object(tmp_obj2));
+	}
+
+	if (list)
+		append_array_object(tmp_obj, "(%{with:, }s)", list);
+	else
+		append_not_present(tmp_obj);
+
+	append_object_object(ret, "%{with_clause}s", tmp_obj);
+
+	append_object_object(ret, "%{on_commit}s",
+						 deparse_OnCommitClause(node->oncommit));
+
+	tmp_obj = new_objtree("TABLESPACE");
+	if (node->tablespacename)
+		append_string_object(tmp_obj, "%{tablespace}I", "tablespace",
+							 node->tablespacename);
+	else
+	{
+		append_null_object(tmp_obj, "%{tablespace}I");
+		append_not_present(tmp_obj);
+	}
+	append_object_object(ret, "%{tablespace}s", tmp_obj);
+
+	relation_close(relation, AccessShareLock);
+
+	return ret;
+}
+
+/*
+ * Deparse CREATE TABLE AS command.
+ *
+ * deparse_CreateStmt do the actual work as we deparse the final CreateStmt for
+ * CREATE TABLE AS command.
+ */
+static ObjTree *
+deparse_CreateTableAsStmt(CollectedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+
+	Assert(cmd->type == SCT_CreateTableAs);
+
+	parsetree = cmd->d.ctas.real_create;
+	objectId = cmd->d.ctas.address.objectId;
+
+	return deparse_CreateStmt(objectId, parsetree);
+}
+
+/*
+ * Deparse all the collected subcommands and return an ObjTree representing the
+ * alter command.
+ *
+ * Verbose syntax
+ * ALTER reltype %{only}s %{identity}D %{subcmds:, }s
+ */
+static ObjTree *
+deparse_AlterRelation(CollectedCommand *cmd)
+{
+	ObjTree    *ret;
+	ObjTree    *tmp_obj;
+	ObjTree    *tmp_obj2;
+	List	   *dpcontext;
+	Relation	rel;
+	List	   *subcmds = NIL;
+	ListCell   *cell;
+	const char *reltype;
+	bool		istype = false;
+	List	   *exprs = NIL;
+	Oid			relId = cmd->d.alterTable.objectId;
+	AlterTableStmt *stmt = NULL;
+
+	Assert(cmd->type == SCT_AlterTable);
+	stmt = (AlterTableStmt *) cmd->parsetree;
+	Assert(IsA(stmt, AlterTableStmt));
+
+	/*
+	 * ALTER TABLE subcommands generated for TableLikeClause is processed in
+	 * the top level CREATE TABLE command; return empty here.
+	 */
+	if (stmt->table_like)
+		return NULL;
+
+	rel = relation_open(relId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									relId);
+
+	switch (rel->rd_rel->relkind)
+	{
+		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
+			reltype = "TABLE";
+			break;
+		case RELKIND_INDEX:
+		case RELKIND_PARTITIONED_INDEX:
+			reltype = "INDEX";
+			break;
+		case RELKIND_VIEW:
+			reltype = "VIEW";
+			break;
+		case RELKIND_COMPOSITE_TYPE:
+			reltype = "TYPE";
+			istype = true;
+			break;
+		case RELKIND_FOREIGN_TABLE:
+			reltype = "FOREIGN TABLE";
+			break;
+		case RELKIND_MATVIEW:
+			reltype = "MATERIALIZED VIEW";
+			break;
+
+			/* TODO support for partitioned table */
+
+		default:
+			elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind);
+	}
+
+	ret = new_objtree_VA("ALTER %{objtype}s %{identity}D", 2,
+						 "objtype", ObjTypeString, reltype,
+						 "identity", ObjTypeObject,
+						 new_objtree_for_qualname(rel->rd_rel->relnamespace,
+												  RelationGetRelationName(rel)));
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
+		AlterTableCmd *subcmd = (AlterTableCmd *) sub->parsetree;
+		ObjTree    *tree;
+
+		Assert(IsA(subcmd, AlterTableCmd));
+
+	   /*
+		* If the ALTER TABLE command for the parent table includes subcommands
+		* for child table(s), do not deparse the subcommand for child
+		* table(s).
+		*/
+		if (sub->address.objectId != relId &&
+			has_superclass(sub->address.objectId))
+			continue;
+
+		switch (subcmd->subtype)
+		{
+			case AT_AddColumn:
+				/* XXX need to set the "recurse" bit somewhere? */
+				Assert(IsA(subcmd->def, ColumnDef));
+				tree = deparse_ColumnDef(rel, dpcontext, false,
+										 (ColumnDef *) subcmd->def, true, &exprs);
+				tmp_obj = new_objtree_VA("ADD %{objtype}s %{if_not_exists}s %{definition}s", 4,
+										"objtype", ObjTypeString,
+										istype ? "ATTRIBUTE" : "COLUMN",
+										"type", ObjTypeString, "add column",
+										"if_not_exists", ObjTypeString,
+										subcmd->missing_ok ? "IF NOT EXISTS" : "",
+										"definition", ObjTypeObject, tree);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_AddIndexConstraint:
+				{
+					IndexStmt  *istmt;
+					Relation	idx;
+					Oid			constrOid = sub->address.objectId;
+
+					Assert(IsA(subcmd->def, IndexStmt));
+					istmt = (IndexStmt *) subcmd->def;
+
+					Assert(istmt->isconstraint && istmt->unique);
+
+					idx = relation_open(istmt->indexOid, AccessShareLock);
+
+					/*
+					 * Verbose syntax
+					 *
+					 * ADD CONSTRAINT %{name}I %{constraint_type}s USING INDEX
+					 * %index_name}I %{deferrable}s %{init_deferred}s
+					 */
+					tmp_obj = new_objtree_VA("ADD CONSTRAINT %{name}I %{constraint_type}s USING INDEX %{index_name}I %{deferrable}s %{init_deferred}s", 6,
+											"type", ObjTypeString, "add constraint using index",
+											"name", ObjTypeString, get_constraint_name(constrOid),
+											"constraint_type", ObjTypeString,
+											istmt->primary ? "PRIMARY KEY" : "UNIQUE",
+											"index_name", ObjTypeString,
+											RelationGetRelationName(idx),
+											"deferrable", ObjTypeString,
+											istmt->deferrable ? "DEFERRABLE" : "NOT DEFERRABLE",
+											"init_deferred", ObjTypeString,
+											istmt->initdeferred ? "INITIALLY DEFERRED" : "INITIALLY IMMEDIATE");
+
+					subcmds = lappend(subcmds, new_object_object(tmp_obj));
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;
+
+			case AT_ReAddIndex:
+			case AT_ReAddConstraint:
+			case AT_ReAddDomainConstraint:
+			case AT_ReAddComment:
+			case AT_ReplaceRelOptions:
+			case AT_CheckNotNull:
+			case AT_ReAddStatistics:
+				/* Subtypes used for internal operations; nothing to do here */
+				break;
+
+			case AT_CookedColumnDefault:
+				{
+					Relation	attrrel;
+					HeapTuple	atttup;
+					Form_pg_attribute attStruct;
+
+					attrrel = table_open(AttributeRelationId, RowExclusiveLock);
+					atttup = SearchSysCacheCopy2(ATTNUM,
+												 ObjectIdGetDatum(RelationGetRelid(rel)),
+												 Int16GetDatum(subcmd->num));
+					if (!HeapTupleIsValid(atttup))
+						elog(ERROR, "cache lookup failed for attribute %d of relation with OID %u",
+							 subcmd->num, RelationGetRelid(rel));
+					attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
+
+					/*
+					 * Both default and generation expression not supported
+					 * together.
+					 */
+					if (!attStruct->attgenerated)
+						elog(WARNING, "unsupported alter table subtype %d",
+							 subcmd->subtype);
+
+					heap_freetuple(atttup);
+					table_close(attrrel, RowExclusiveLock);
+					break;
+				}
+
+			case AT_AddColumnToView:
+				/* CREATE OR REPLACE VIEW -- nothing to do here */
+				break;
+
+			case AT_ColumnDefault:
+				if (subcmd->def == NULL)
+					tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I DROP DEFAULT", 2,
+											"type", ObjTypeString, "drop default",
+											"column", ObjTypeString, subcmd->name);
+				else
+				{
+					List	   *dpcontext_rel;
+					HeapTuple	attrtup;
+					AttrNumber	attno;
+
+					tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET DEFAULT", 2,
+											"type", ObjTypeString, "set default",
+											"column", ObjTypeString, subcmd->name);
+
+					dpcontext_rel = deparse_context_for(RelationGetRelationName(rel),
+														RelationGetRelid(rel));
+					attrtup = SearchSysCacheAttName(RelationGetRelid(rel), subcmd->name);
+					attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum;
+					append_string_object(tmp_obj, "%{definition}s", "definition",
+										 RelationGetColumnDefault(rel, attno,
+																  dpcontext_rel,
+																  NULL));
+					ReleaseSysCache(attrtup);
+				}
+
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DropNotNull:
+				tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I DROP NOT NULL", 2,
+										"type", ObjTypeString, "drop not null",
+										"column", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_ForceRowSecurity:
+				tmp_obj = new_objtree("FORCE ROW LEVEL SECURITY");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_NoForceRowSecurity:
+				tmp_obj = new_objtree("NO FORCE ROW LEVEL SECURITY");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_SetNotNull:
+				tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET NOT NULL", 2,
+										"type", ObjTypeString, "set not null",
+										"column", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DropExpression:
+				tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I DROP EXPRESSION %{if_exists}s", 3,
+										"type", ObjTypeString, "drop expression",
+										"column", ObjTypeString, subcmd->name,
+										"if_exists", ObjTypeString,
+										subcmd->missing_ok ? "IF EXISTS" : "");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_SetStatistics:
+				{
+					Assert(IsA(subcmd->def, Integer));
+					if (subcmd->name)
+						tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n", 3,
+												"type", ObjTypeString, "set statistics",
+												"column", ObjTypeString, subcmd->name,
+												"statistics", ObjTypeInteger,
+												intVal((Integer *) subcmd->def));
+					else
+						tmp_obj = new_objtree_VA("ALTER COLUMN %{column}n SET STATISTICS %{statistics}n", 3,
+												"type", ObjTypeString, "set statistics",
+												"column", ObjTypeInteger, subcmd->num,
+												"statistics", ObjTypeInteger,
+												intVal((Integer *) subcmd->def));
+					subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				}
+				break;
+
+			case AT_SetOptions:
+			case AT_ResetOptions:
+				subcmds = lappend(subcmds, new_object_object(
+															 deparse_ColumnSetOptions(subcmd)));
+				break;
+
+			case AT_SetStorage:
+				Assert(IsA(subcmd->def, String));
+				tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET STORAGE %{storage}s", 3,
+										"type", ObjTypeString, "set storage",
+										"column", ObjTypeString, subcmd->name,
+										"storage", ObjTypeString,
+										strVal((String *) subcmd->def));
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_SetCompression:
+				Assert(IsA(subcmd->def, String));
+				tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET COMPRESSION %{compression_method}s", 3,
+										"type", ObjTypeString, "set compression",
+										"column", ObjTypeString, subcmd->name,
+										"compression_method", ObjTypeString,
+										strVal((String *) subcmd->def));
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DropColumn:
+				tmp_obj = new_objtree_VA("DROP %{objtype}s %{if_exists}s %{column}I", 4,
+										"objtype", ObjTypeString,
+										istype ? "ATTRIBUTE" : "COLUMN",
+										"type", ObjTypeString, "drop column",
+										"if_exists", ObjTypeString,
+										subcmd->missing_ok ? "IF EXISTS" : "",
+										"column", ObjTypeString, subcmd->name);
+				tmp_obj2 = new_objtree_VA("CASCADE", 1,
+										 "present", ObjTypeBool, subcmd->behavior);
+				append_object_object(tmp_obj, "%{cascade}s", tmp_obj2);
+
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_AddIndex:
+				{
+					Oid			idxOid = sub->address.objectId;
+					IndexStmt  *istmt;
+					Relation	idx;
+					const char *idxname;
+					Oid			constrOid;
+
+					Assert(IsA(subcmd->def, IndexStmt));
+					istmt = (IndexStmt *) subcmd->def;
+
+					if (!istmt->isconstraint)
+						break;
+
+					idx = relation_open(idxOid, AccessShareLock);
+					idxname = RelationGetRelationName(idx);
+
+					constrOid = get_relation_constraint_oid(
+															cmd->d.alterTable.objectId, idxname, false);
+
+					tmp_obj = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", 3,
+											"type", ObjTypeString, "add constraint",
+											"name", ObjTypeString, idxname,
+											"definition", ObjTypeString,
+											pg_get_constraintdef_string(constrOid));
+					subcmds = lappend(subcmds, new_object_object(tmp_obj));
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;
+
+			case AT_AddConstraint:
+				{
+					/* XXX need to set the "recurse" bit somewhere? */
+					Oid			constrOid = sub->address.objectId;
+					bool		isnull;
+					HeapTuple	tup;
+					Datum		val;
+					Constraint *constr;
+
+					/* Skip adding constraint for inherits table sub command */
+					if (!constrOid)
+						continue;
+
+					Assert(IsA(subcmd->def, Constraint));
+					constr = castNode(Constraint, subcmd->def);
+
+					if (!constr->skip_validation)
+					{
+						tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
+
+						if (HeapTupleIsValid(tup))
+						{
+							char	   *conbin;
+
+							/* Fetch constraint expression in parsetree form */
+							val = SysCacheGetAttr(CONSTROID, tup,
+												  Anum_pg_constraint_conbin, &isnull);
+
+							if (!isnull)
+							{
+								conbin = TextDatumGetCString(val);
+								exprs = lappend(exprs, stringToNode(conbin));
+							}
+
+							ReleaseSysCache(tup);
+						}
+					}
+
+					tmp_obj = 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));
+					subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				}
+				break;
+
+			case AT_AlterConstraint:
+				{
+					Oid			constrOid = sub->address.objectId;
+					Constraint *c = (Constraint *) subcmd->def;
+
+					/* If no constraint was altered, silently skip it */
+					if (!OidIsValid(constrOid))
+						break;
+
+					Assert(IsA(c, Constraint));
+					tmp_obj = new_objtree_VA("ALTER CONSTRAINT %{name}I %{deferrable}s %{init_deferred}s", 4,
+											"type", ObjTypeString, "alter constraint",
+											"name", ObjTypeString, get_constraint_name(constrOid),
+											"deferrable", ObjTypeString,
+											c->deferrable ? "DEFERRABLE" : "NOT DEFERRABLE",
+											"init_deferred", ObjTypeString,
+											c->initdeferred ? "INITIALLY DEFERRED" : "INITIALLY IMMEDIATE");
+
+					subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				}
+				break;
+
+			case AT_ValidateConstraint:
+				tmp_obj = new_objtree_VA("VALIDATE CONSTRAINT %{constraint}I", 2,
+										"type", ObjTypeString, "validate constraint",
+										"constraint", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DropConstraint:
+				tmp_obj = new_objtree_VA("DROP CONSTRAINT %{if_exists}s %{constraint}I %{cascade}s", 4,
+										"type", ObjTypeString, "drop constraint",
+										"if_exists", ObjTypeString,
+										subcmd->missing_ok ? "IF EXISTS" : "",
+										"constraint", ObjTypeString, subcmd->name,
+										"cascade", ObjTypeString,
+										subcmd->behavior == DROP_CASCADE ? "CASCADE" : "");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_AlterColumnType:
+				{
+					TupleDesc	tupdesc = RelationGetDescr(rel);
+					Form_pg_attribute att;
+					ColumnDef  *def;
+
+					att = &(tupdesc->attrs[sub->address.objectSubId - 1]);
+					def = (ColumnDef *) subcmd->def;
+					Assert(IsA(def, ColumnDef));
+
+					/*
+					 * Verbose syntax
+					 *
+					 * Composite types: ALTER reltype %{column}I SET DATA TYPE
+					 * %{datatype}T %{collation}s ATTRIBUTE %{cascade}s
+					 *
+					 * Normal types: ALTER reltype %{column}I SET DATA TYPE
+					 * %{datatype}T %{collation}s COLUMN %{using}s
+					 */
+					tmp_obj = new_objtree_VA("ALTER %{objtype}s %{column}I SET DATA TYPE %{datatype}T", 4,
+											"objtype", ObjTypeString,
+											istype ? "ATTRIBUTE" : "COLUMN",
+											"type", ObjTypeString, "alter column type",
+											"column", ObjTypeString, subcmd->name,
+											"datatype", ObjTypeObject,
+											new_objtree_for_type(att->atttypid,
+																 att->atttypmod));
+
+					/* Add a COLLATE clause, if needed */
+					tmp_obj2 = new_objtree("COLLATE");
+					if (OidIsValid(att->attcollation))
+					{
+						ObjTree    *collname;
+
+						collname = new_objtree_for_qualname_id(CollationRelationId,
+															   att->attcollation);
+						append_object_object(tmp_obj2, "%{name}D", collname);
+					}
+					else
+						append_not_present(tmp_obj2);
+					append_object_object(tmp_obj, "%{collation}s", tmp_obj2);
+
+					/* If not a composite type, add the USING clause */
+					if (!istype)
+					{
+						/*
+						 * If there's a USING clause, transformAlterTableStmt
+						 * ran it through transformExpr and stored the
+						 * resulting node in cooked_default, which we can use
+						 * here.
+						 */
+						tmp_obj2 = new_objtree("USING");
+						if (def->raw_default)
+							append_string_object(tmp_obj2, "%{expression}s",
+												 "expression",
+												 sub->usingexpr);
+						else
+							append_not_present(tmp_obj2);
+						append_object_object(tmp_obj, "%{using}s", tmp_obj2);
+					}
+
+					/* If it's a composite type, add the CASCADE clause */
+					if (istype)
+					{
+						tmp_obj2 = new_objtree("CASCADE");
+						if (subcmd->behavior != DROP_CASCADE)
+							append_not_present(tmp_obj2);
+						append_object_object(tmp_obj, "%{cascade}s", tmp_obj2);
+					}
+
+					subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				}
+				break;
+
+#ifdef TODOLIST
+			case AT_AlterColumnGenericOptions:
+				tmp_obj = deparse_FdwOptions((List *) subcmd->def,
+											subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+#endif
+			case AT_ChangeOwner:
+				tmp_obj = new_objtree_VA("OWNER TO %{owner}I", 2,
+										"type", ObjTypeString, "change owner",
+										"owner", ObjTypeString,
+										get_rolespec_name(subcmd->newowner));
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_ClusterOn:
+				tmp_obj = new_objtree_VA("CLUSTER ON %{index}I", 2,
+										"type", ObjTypeString, "cluster on",
+										"index", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DropCluster:
+				tmp_obj = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+										"type", ObjTypeString, "set without cluster");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_SetLogged:
+				tmp_obj = new_objtree_VA("SET LOGGED", 1,
+										"type", ObjTypeString, "set logged");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_SetUnLogged:
+				tmp_obj = new_objtree_VA("SET UNLOGGED", 1,
+										"type", ObjTypeString, "set unlogged");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DropOids:
+				tmp_obj = new_objtree_VA("SET WITHOUT OIDS", 1,
+										"type", ObjTypeString, "set without oids");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+			case AT_SetAccessMethod:
+				tmp_obj = new_objtree_VA("SET ACCESS METHOD %{access_method}I", 2,
+										"type", ObjTypeString, "set access method",
+										"access_method", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+			case AT_SetTableSpace:
+				tmp_obj = new_objtree_VA("SET TABLESPACE %{tablespace}I", 2,
+										"type", ObjTypeString, "set tablespace",
+										"tablespace", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_SetRelOptions:
+			case AT_ResetRelOptions:
+				subcmds = lappend(subcmds, new_object_object(
+															 deparse_RelSetOptions(subcmd)));
+				break;
+
+			case AT_EnableTrig:
+				tmp_obj = new_objtree_VA("ENABLE TRIGGER %{trigger}I", 2,
+										"type", ObjTypeString, "enable trigger",
+										"trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_EnableAlwaysTrig:
+				tmp_obj = 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_obj));
+				break;
+
+			case AT_EnableReplicaTrig:
+				tmp_obj = 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_obj));
+				break;
+
+			case AT_DisableTrig:
+				tmp_obj = new_objtree_VA("DISABLE TRIGGER %{trigger}I", 2,
+										"type", ObjTypeString, "disable trigger",
+										"trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_EnableTrigAll:
+				tmp_obj = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+										"type", ObjTypeString, "enable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DisableTrigAll:
+				tmp_obj = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+										"type", ObjTypeString, "disable trigger all");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_EnableTrigUser:
+				tmp_obj = new_objtree_VA("ENABLE TRIGGER USER", 1,
+										"type", ObjTypeString, "enable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DisableTrigUser:
+				tmp_obj = new_objtree_VA("DISABLE TRIGGER USER", 1,
+										"type", ObjTypeString, "disable trigger user");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_EnableRule:
+				tmp_obj = new_objtree_VA("ENABLE RULE %{rule}I", 2,
+										"type", ObjTypeString, "enable rule",
+										"rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_EnableAlwaysRule:
+				tmp_obj = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2,
+										"type", ObjTypeString, "enable always rule",
+										"rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_EnableReplicaRule:
+				tmp_obj = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2,
+										"type", ObjTypeString, "enable replica rule",
+										"rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DisableRule:
+				tmp_obj = new_objtree_VA("DISABLE RULE %{rule}I", 2,
+										"type", ObjTypeString, "disable rule",
+										"rule", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_AddInherit:
+				tmp_obj = new_objtree_VA("INHERIT %{parent}D", 2,
+										"type", ObjTypeString, "inherit",
+										"parent", ObjTypeObject,
+										new_objtree_for_qualname_id(RelationRelationId,
+																	sub->address.objectId));
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DropInherit:
+				tmp_obj = new_objtree_VA("NO INHERIT %{parent}D", 2,
+										"type", ObjTypeString, "drop inherit",
+										"parent", ObjTypeObject,
+										new_objtree_for_qualname_id(RelationRelationId,
+																	sub->address.objectId));
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_AddOf:
+				tmp_obj = new_objtree_VA("OF %{type_of}T", 2,
+										"type", ObjTypeString, "add of",
+										"type_of", ObjTypeObject,
+										new_objtree_for_type(sub->address.objectId, -1));
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DropOf:
+				tmp_obj = new_objtree_VA("NOT OF", 1,
+										"type", ObjTypeString, "not of");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_ReplicaIdentity:
+				tmp_obj = new_objtree_VA("REPLICA IDENTITY", 1,
+										"type", ObjTypeString, "replica identity");
+				switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type)
+				{
+					case REPLICA_IDENTITY_DEFAULT:
+						append_string_object(tmp_obj, "%{ident}s", "ident",
+											 "DEFAULT");
+						break;
+					case REPLICA_IDENTITY_FULL:
+						append_string_object(tmp_obj, "%{ident}s", "ident",
+											 "FULL");
+						break;
+					case REPLICA_IDENTITY_NOTHING:
+						append_string_object(tmp_obj, "%{ident}s", "ident",
+											 "NOTHING");
+						break;
+					case REPLICA_IDENTITY_INDEX:
+						tmp_obj2 = new_objtree_VA("USING INDEX %{index}I", 1,
+												 "index", ObjTypeString,
+												 ((ReplicaIdentityStmt *) subcmd->def)->name);
+						append_object_object(tmp_obj, "%{ident}s", tmp_obj2);
+						break;
+				}
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_EnableRowSecurity:
+				tmp_obj = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1,
+										"type", ObjTypeString, "enable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+
+			case AT_DisableRowSecurity:
+				tmp_obj = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1,
+										"type", ObjTypeString, "disable row security");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+#ifdef TODOLIST
+			case AT_GenericOptions:
+				tmp_obj = deparse_FdwOptions((List *) subcmd->def, NULL);
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+#endif
+			case AT_AttachPartition:
+				tmp_obj = new_objtree_VA("ATTACH PARTITION %{partition_identity}D", 2,
+										"type", ObjTypeString, "attach partition",
+										"partition_identity", ObjTypeObject,
+										new_objtree_for_qualname_id(RelationRelationId,
+																	sub->address.objectId));
+
+				if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+					append_string_object(tmp_obj, "%{partition_bound}s",
+										 "partition_bound",
+										 RelationGetPartitionBound(sub->address.objectId));
+
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+			case AT_DetachPartition:
+			{
+				PartitionCmd *cmd;
+
+				Assert(IsA(subcmd->def, PartitionCmd));
+				cmd = (PartitionCmd *) subcmd->def;
+
+				tmp_obj = new_objtree_VA("DETACH PARTITION %{partition_identity}D %{concurrent}s", 3,
+										"type", ObjTypeString,
+										"detach partition",
+										"partition_identity", ObjTypeObject,
+										new_objtree_for_qualname_id(RelationRelationId,
+																	sub->address.objectId),
+										cmd->concurrent ? "CONCURRENTLY" : "");
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+			}
+			case AT_DetachPartitionFinalize:
+				tmp_obj = new_objtree_VA("DETACH PARTITION %{partition_identity}D FINALIZE", 2,
+										"type", ObjTypeString, "detach partition finalize",
+										"partition_identity", ObjTypeObject,
+										new_objtree_for_qualname_id(RelationRelationId,
+																	sub->address.objectId));
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+			case AT_AddIdentity:
+				{
+					AttrNumber	attnum;
+					Oid			seq_relid;
+					ObjTree    *seqdef;
+					ColumnDef  *coldef = (ColumnDef *) subcmd->def;
+
+					tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I", 2,
+											"type", ObjTypeString, "add identity",
+											"column", ObjTypeString, subcmd->name);
+
+					attnum = get_attnum(RelationGetRelid(rel), subcmd->name);
+					seq_relid = getIdentitySequence(RelationGetRelid(rel), attnum, true);
+
+					if (OidIsValid(seq_relid))
+					{
+						seqdef = deparse_ColumnIdentity(seq_relid, coldef->identity, false);
+						append_object_object(tmp_obj, "ADD %{identity_column}s", seqdef);
+					}
+
+					subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				}
+				break;
+			case AT_SetIdentity:
+				{
+					DefElem    *defel;
+					char		identity = 0;
+					ObjTree    *seqdef;
+					AttrNumber	attnum;
+					Oid			seq_relid;
+
+
+					tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I", 2,
+											"type", ObjTypeString, "set identity",
+											"column", ObjTypeString, subcmd->name);
+
+					if (subcmd->def)
+					{
+						List	   *def = (List *) subcmd->def;
+
+						Assert(IsA(subcmd->def, List));
+
+						defel = linitial_node(DefElem, def);
+						identity = defGetInt32(defel);
+					}
+
+					attnum = get_attnum(RelationGetRelid(rel), subcmd->name);
+					seq_relid = getIdentitySequence(RelationGetRelid(rel), attnum, true);
+
+					if (OidIsValid(seq_relid))
+					{
+						seqdef = deparse_ColumnIdentity(seq_relid, identity, true);
+						append_object_object(tmp_obj, "%{definition}s", seqdef);
+					}
+
+					subcmds = lappend(subcmds, new_object_object(tmp_obj));
+					break;
+				}
+			case AT_DropIdentity:
+				tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I DROP IDENTITY", 2,
+										"type", ObjTypeString, "drop identity",
+										"column", ObjTypeString, subcmd->name);
+
+				append_string_object(tmp_obj, "%{if_exists}s",
+									 "if_exists",
+									 subcmd->missing_ok ? "IF EXISTS" : "");
+
+				subcmds = lappend(subcmds, new_object_object(tmp_obj));
+				break;
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+
+		/*
+		 * We don't support replicating ALTER TABLE which contains volatile
+		 * functions because It's possible the functions contain DDL/DML in
+		 * which case these operations will be executed twice and cause
+		 * duplicate data. In addition, we don't know whether the tables being
+		 * accessed by these DDL/DML are published or not. So blindly allowing
+		 * such functions can allow unintended clauses like the tables
+		 * accessed in those functions may not even exist on the subscriber.
+		 */
+		if (contain_volatile_functions((Node *) exprs))
+			elog(ERROR, "ALTER TABLE command using volatile function cannot be replicated");
+
+		/*
+		 * Clean the list as we already confirmed there is no volatile
+		 * function.
+		 */
+		list_free(exprs);
+		exprs = NIL;
+	}
+
+	table_close(rel, AccessShareLock);
+
+	if (list_length(subcmds) == 0)
+		return NULL;
+
+	append_array_object(ret, "%{subcmds:, }s", subcmds);
+
+	return ret;
+}
+
+/*
+ * Handle deparsing of DROP commands.
+ *
+ * Verbose syntax
+ * DROP %{objtype}s IF EXISTS %%{objidentity}s %{cascade}s
+ */
+char *
+deparse_drop_command(const char *objidentity, const char *objecttype,
+					 DropBehavior behavior)
+{
+	StringInfoData str;
+	char	   *command;
+	char	   *identity = (char *) objidentity;
+	ObjTree    *stmt;
+	ObjTree    *tmp_obj;
+	Jsonb	   *jsonb;
+
+	initStringInfo(&str);
+
+	stmt = new_objtree_VA("DROP %{objtype}s IF EXISTS %{objidentity}s", 2,
+						  "objtype", ObjTypeString, objecttype,
+						  "objidentity", ObjTypeString, identity);
+
+	tmp_obj = new_objtree_VA("CASCADE", 1,
+							"present", ObjTypeBool, behavior == DROP_CASCADE);
+	append_object_object(stmt, "%{cascade}s", tmp_obj);
+
+	jsonb = objtree_to_jsonb(stmt);
+	command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
+
+	return command;
+}
+
+/*
+ * Deparse an AlterObjectSchemaStmt (ALTER ... SET SCHEMA command)
+ *
+ * Given the object address and the parse tree that created it, return an
+ * ObjTree representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{identity}s SET SCHEMA %{newschema}I
+ */
+static ObjTree *
+deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree,
+							  ObjectAddress old_schema)
+{
+	AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree;
+	char	   *identity;
+	char	   *new_schema = node->newschema;
+	char	   *old_schname;
+	char	   *ident;
+
+	/*
+	 * Since the command has already taken place from the point of view of
+	 * catalogs, getObjectIdentity returns the object name with the already
+	 * changed schema.  The output of our deparsing must return the original
+	 * schema name, however, so we chop the schema name off the identity
+	 * string and then prepend the quoted schema name.
+	 *
+	 * XXX This is pretty clunky. Can we do better?
+	 */
+	identity = getObjectIdentity(&address, false);
+	old_schname = get_namespace_name(old_schema.objectId);
+	if (!old_schname)
+		elog(ERROR, "cache lookup failed for schema with OID %u",
+			 old_schema.objectId);
+
+	ident = psprintf("%s%s", quote_identifier(old_schname),
+					 identity + strlen(quote_identifier(new_schema)));
+
+	return new_objtree_VA("ALTER %{objtype}s %{identity}s SET SCHEMA %{newschema}I", 3,
+						  "objtype", ObjTypeString,
+						  stringify_objtype(node->objectType, false),
+						  "identity", ObjTypeString, ident,
+						  "newschema", ObjTypeString, new_schema);
+}
+
+/*
+ * Deparse an AlterOwnerStmt (ALTER ... OWNER TO ...).
+ *
+ * Given the object address and the parse tree that created it, return an
+ * ObjTree representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I
+ */
+static ObjTree *
+deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+
+	return new_objtree_VA("ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I", 3,
+						  "objtype", ObjTypeString,
+						  stringify_objtype(node->objectType, false),
+						  "identity", ObjTypeString,
+						  getObjectIdentity(&address, false),
+						  "newowner", ObjTypeString,
+						  get_rolespec_name(node->newowner));
+}
+
+/*
+ * Deparse a RenameStmt.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I
+ * OR
+ * ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I
+ * OR
+ * ALTER %{objtype}s %{if_exists}s %{only}s %{identity}D RENAME COLUMN %{colname}I TO %{newname}I %{cascade}s
+ */
+static ObjTree *
+deparse_RenameStmt(ObjectAddress address, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree    *ret;
+	Relation	relation;
+	Oid			schemaId;
+
+	/*
+	 * In an 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.
+	 */
+	switch (node->renameType)
+	{
+		case OBJECT_TABLE:
+			relation = relation_open(address.objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+			ret = new_objtree_VA("ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I", 4,
+								 "objtype", ObjTypeString,
+								 stringify_objtype(node->renameType, false),
+								 "if_exists", ObjTypeString,
+								 node->missing_ok ? "IF EXISTS" : "",
+								 "identity", ObjTypeObject,
+								 new_objtree_for_qualname(schemaId,
+														  node->relation->relname),
+								 "newname", ObjTypeString,
+								 node->newname);
+			relation_close(relation, AccessShareLock);
+			break;
+
+		case OBJECT_TABCONSTRAINT:
+			{
+				HeapTuple	constrtup;
+				Form_pg_constraint constform;
+
+				constrtup = SearchSysCache1(CONSTROID,
+											ObjectIdGetDatum(address.objectId));
+				if (!HeapTupleIsValid(constrtup))
+					elog(ERROR, "cache lookup failed for constraint with OID %u",
+						 address.objectId);
+				constform = (Form_pg_constraint) GETSTRUCT(constrtup);
+
+				ret = new_objtree_VA("ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I", 4,
+									"only",  ObjTypeString,
+									node->relation->inh ? "" : "ONLY",
+									"identity", ObjTypeObject,
+									new_objtree_for_qualname_id(RelationRelationId,
+																constform->conrelid),
+									"oldname", ObjTypeString, node->subname,
+									"newname", ObjTypeString, node->newname);
+				ReleaseSysCache(constrtup);
+			}
+			break;
+
+		case OBJECT_COLUMN:
+			relation = relation_open(address.objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			ret = new_objtree_VA("ALTER %{objtype}s", 1,
+								 "objtype", ObjTypeString,
+								 stringify_objtype(node->relationType, false));
+
+			/* Composite types do not support IF EXISTS */
+			if (node->renameType == OBJECT_COLUMN)
+				append_string_object(ret, "%{if_exists}s",
+									 "if_exists",
+									 node->missing_ok ? "IF EXISTS" : "");
+			if (!node->relation->inh)
+				append_string_object(ret, "%{only}s",
+									 "only",
+									 "ONLY");
+			append_object_object(ret, "%{identity}D",
+								 new_objtree_for_qualname(schemaId,
+														  node->relation->relname));
+			append_string_object(ret, "RENAME COLUMN %{colname}I",
+								 "colname", node->subname);
+
+			append_string_object(ret, "TO %{newname}I", "newname",
+								 node->newname);
+
+			if (node->renameType == OBJECT_ATTRIBUTE)
+				append_object_object(ret, "%{cascade}s",
+									 new_objtree_VA("CASCADE", 1,
+													"present", ObjTypeBool,
+													node->behavior == DROP_CASCADE));
+
+			relation_close(relation, AccessShareLock);
+			break;
+
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	return ret;
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function should cover all cases handled in ProcessUtilitySlow.
+ */
+static ObjTree *
+deparse_simple_command(CollectedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+
+	Assert(cmd->type == SCT_Simple);
+
+	parsetree = cmd->parsetree;
+	objectId = cmd->d.simple.address.objectId;
+
+	if (cmd->in_extension && (nodeTag(parsetree) != T_CreateExtensionStmt))
+		return NULL;
+
+	/* This switch needs to handle everything that ProcessUtilitySlow does */
+	switch (nodeTag(parsetree))
+	{
+		case T_AlterObjectSchemaStmt:
+			return deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
+												 parsetree,
+												 cmd->d.simple.secondaryObject);
+
+		case T_AlterOwnerStmt:
+			return deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree);
+
+		case T_CreateStmt:
+			return deparse_CreateStmt(objectId, parsetree);
+
+		case T_RenameStmt:
+			return deparse_RenameStmt(cmd->d.simple.address, parsetree);
+
+		default:
+			elog(LOG, "unrecognized node type in deparse command: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return NULL;
+}
+
+/*
+ * Workhorse to deparse a CollectedCommand.
+ */
+char *
+deparse_utility_command(CollectedCommand *cmd, bool verbose_mode)
+{
+	OverrideSearchPath *overridePath;
+	MemoryContext oldcxt;
+	MemoryContext tmpcxt;
+	ObjTree    *tree;
+	char	   *command = NULL;
+	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
+	 * to obtain deparsed versions of expressions.  In such results, we want
+	 * all object names to be qualified, so that results are "portable" to
+	 * environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = true;
+	PushOverrideSearchPath(overridePath);
+
+	verbose = verbose_mode;
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			tree = deparse_simple_command(cmd);
+			break;
+		case SCT_AlterTable:
+			tree = deparse_AlterRelation(cmd);
+			break;
+		case SCT_CreateTableAs:
+			tree = deparse_CreateTableAsStmt(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, JSONB_ESTIMATED_LEN);
+	}
+
+	/*
+	 * 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;
+}
+
+/*
+ * Given a CollectedCommand, return a JSON representation of it.
+ *
+ * The command is expanded fully so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+Datum
+ddl_deparse_to_json(PG_FUNCTION_ARGS)
+{
+	CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+	char	   *command;
+
+	command = deparse_utility_command(cmd, true);
+
+	if (command)
+		PG_RETURN_TEXT_P(cstring_to_text(command));
+
+	PG_RETURN_NULL();
+}
diff --git a/src/backend/commands/ddl_json.c b/src/backend/commands/ddl_json.c
new file mode 100644
index 0000000000..3a57d2697c
--- /dev/null
+++ b/src/backend/commands/ddl_json.c
@@ -0,0 +1,780 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_json.c
+ *	  JSON code related to DDL command deparsing
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/ddl_json.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "lib/stringinfo.h"
+#include "tcop/ddl_deparse.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+
+/*
+ * Conversion specifier which determines how to expand the JSON element
+ * into a string.
+ */
+typedef enum
+{
+	SpecDottedName,
+	SpecIdentifier,
+	SpecNumber,
+	SpecOperatorName,
+	SpecRole,
+	SpecString,
+	SpecStringLiteral,
+	SpecTypeName
+} convSpecifier;
+
+/*
+ * A ternary value that represents a boolean type JsonbValue.
+ */
+typedef enum
+{
+	tv_absent,
+	tv_true,
+	tv_false
+}			json_trivalue;
+
+static bool expand_one_jsonb_element(StringInfo buf, char *param,
+									 JsonbValue *jsonval, convSpecifier specifier,
+									 const char *fmt);
+static void expand_jsonb_array(StringInfo buf, char *param,
+							   JsonbValue *jsonarr, char *arraysep,
+							   convSpecifier specifier, const char *fmt);
+static void fmtstr_error_callback(void *arg);
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvBool, an error is raised. If it doesn't
+ * exist, tv_absent is returned; otherwise return the actual json_trivalue.
+ */
+static json_trivalue
+find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	json_trivalue 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 unless 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));
+	}
+
+	if (value->type != jbvString)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("element \"%s\" is not of type string", 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 deparse_ddl_json_to_string.
+ *
+ * Find the "fmt" element in the given container, and expand it into the
+ * provided StringInfo.
+ */
+static void
+expand_fmt_recursive(StringInfo buf, JsonbContainer *container)
+{
+	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 = false;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(buf, *cp);
+			continue;
+		}
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(buf, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after a colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			while (cp < end_ptr)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * Found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("missing conversion name in conversion specifier"));
+
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'L':
+				specifier = SpecStringLiteral;
+				break;
+			case 'T':
+				specifier = SpecTypeName;
+				break;
+			case 'O':
+				specifier = SpecOperatorName;
+				break;
+			case 'n':
+				specifier = SpecNumber;
+				break;
+			case 'R':
+				specifier = SpecRole;
+				break;
+			default:
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid conversion specifier \"%c\"", *cp));
+		}
+
+		/*
+		 * Obtain the element to be expanded.
+		 */
+		key.type = jbvString;
+		key.val.string.val = param;
+		key.val.string.len = strlen(param);
+
+		value = findJsonbValueFromContainer(container, JB_FOBJECT, &key);
+		Assert(value != NULL);
+
+		/*
+		 * Expand the data (possibly an array) into the output StringInfo.
+		 */
+		if (is_array)
+			expand_jsonb_array(buf, param, value, arraysep, specifier, start_ptr);
+		else
+			expand_one_jsonb_element(buf, param, value, specifier, start_ptr);
+	}
+}
+
+/*
+ * Expand a json value as a quoted 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
+ * binary and may 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;
+	JsonbContainer *data = jsonval->val.binary.data;
+
+	Assert(jsonval->type == jbvBinary);
+
+	str = find_string_in_jsonbcontainer(data, "schemaname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+		pfree(str);
+	}
+
+	str = find_string_in_jsonbcontainer(data, "objname", false, NULL);
+	appendStringInfo(buf, "%s", quote_identifier(str));
+	pfree(str);
+
+	str = find_string_in_jsonbcontainer(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;
+	json_trivalue is_array;
+	char	   *array_decor;
+	JsonbContainer *data = jsonval->val.binary.data;
+
+	/*
+	 * 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(data, "schemaname", true, NULL);
+	typename = find_string_in_jsonbcontainer(data, "typename", false, NULL);
+	typmodstr = find_string_in_jsonbcontainer(data, "typmod", true, NULL);
+	is_array = find_bool_in_jsonbcontainer(data, "typarray");
+	switch (is_array)
+	{
+		case tv_true:
+			array_decor = "[]";
+			break;
+
+		case tv_false:
+			array_decor = "";
+			break;
+
+		case tv_absent:
+		default:
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("missing typarray element"));
+	}
+
+	if (schema == NULL)
+		appendStringInfo(buf, "%s", quote_identifier(typename));
+	else if (schema[0] == '\0')
+		appendStringInfo(buf, "%s", typename);	/* Special typmod needs */
+	else
+		appendStringInfo(buf, "%s.%s", quote_identifier(schema),
+						 quote_identifier(typename));
+
+	appendStringInfo(buf, "%s%s", typmodstr ? typmodstr : "", array_decor);
+	pfree(schema);
+	pfree(typename);
+	pfree(typmodstr);
+}
+
+/*
+ * Expand a JSON value as an operator name. The value may contain element
+ * "schemaname" (optional).
+ */
+static void
+expand_jsonval_operator(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+	JsonbContainer *data = jsonval->val.binary.data;
+
+	str = find_string_in_jsonbcontainer(data, "schemaname", true, NULL);
+	/* Schema might be NULL or empty */
+	if (str != NULL && str[0] != '\0')
+	{
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+		pfree(str);
+	}
+
+	str = find_string_in_jsonbcontainer(data, "objname", false, NULL);
+	if (!str)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("missing operator name"));
+
+	appendStringInfoString(buf, str);
+	pfree(str);
+}
+
+/*
+ * Expand a JSON value as a string.  The value must be of type string or of
+ * type Binary.  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.
+ *
+ * Returns false if no actual expansion was made due to the "present" flag
+ * being set to "false".
+ *
+ * The caller is responsible to check jsonval is of type jbvString or jbvBinary.
+ */
+static bool
+expand_jsonval_string(StringInfo buf, JsonbValue *jsonval)
+{
+	bool expanded = false;
+
+	if (jsonval->type == jbvString)
+	{
+		appendBinaryStringInfo(buf, jsonval->val.string.val,
+							   jsonval->val.string.len);
+		expanded = true;
+	}
+	else if (jsonval->type == jbvBinary)
+	{
+		json_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), expand "fmt".
+		 */
+		if (present != tv_false)
+		{
+			expand_fmt_recursive(buf, jsonval->val.binary.data);
+			expanded = true;
+		}
+	}
+
+	return expanded;
+}
+
+/*
+ * Expand a JSON value as a string literal.
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int			dqnextchar = 0;
+
+	str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+
+	/* Easy case: if there are no ' and no \, just use a single quote */
+	if (strpbrk(str, "\'\\") == NULL)
+	{
+		appendStringInfo(buf, "'%s'", str);
+		pfree(str);
+		return;
+	}
+
+	/* Otherwise need to find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(str, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar = dqnextchar % (sizeof(dqsuffixes) - 1);
+	}
+	/* Add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data);
+	pfree(dqdelim.data);
+	pfree(str);
+}
+
+/*
+ * Expand a JSON value as an integer quantity.
+ */
+static void
+expand_jsonval_number(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *strdatum;
+
+	Assert(jsonval->type == jbvNumeric);
+
+	strdatum = DatumGetCString(DirectFunctionCall1(numeric_out,
+												   NumericGetDatum(jsonval->val.numeric)));
+	appendStringInfoString(buf, strdatum);
+	pfree(strdatum);
+}
+
+/*
+ * Expand a JSON value as a role name.  If the 'is_public' element is set to
+ * true, PUBLIC is expanded (no quotes); otherwise, expand the given role name,
+ * quoting as an identifier.
+ */
+static void
+expand_jsonval_role(StringInfo buf, JsonbValue *jsonval)
+{
+	json_trivalue is_public;
+
+	is_public = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											"is_public");
+	if (is_public == tv_true)
+		appendStringInfoString(buf, "PUBLIC");
+	else
+	{
+		char	   *rolename;
+
+		rolename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+												 "rolename", false, NULL);
+		if (rolename)
+		{
+			appendStringInfoString(buf, quote_identifier(rolename));
+			pfree(rolename);
+		}
+	}
+}
+
+/*
+ * Expand one JSON element into the output StringInfo according to the
+ * conversion specifier.  The element type is validated, and an error is raised
+ * if it doesn't match what we expect for the conversion specifier.
+ *
+ * Returns true, except for the formatted string case if no actual expansion
+ * was made (due to the "present" flag being set to "false").
+ */
+static bool
+expand_one_jsonb_element(StringInfo buf, char *param, JsonbValue *jsonval,
+						 convSpecifier specifier, const char *fmt)
+{
+	bool		string_expanded = true;
+	ErrorContextCallback sqlerrcontext;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (!jsonval)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("element \"%s\" not found", param));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON string for %%I element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_identifier(buf, jsonval);
+			break;
+
+		case SpecDottedName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON struct for %%D element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_dottedname(buf, jsonval);
+			break;
+
+		case SpecString:
+			if (jsonval->type != jbvString &&
+				jsonval->type != jbvBinary)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON string or struct for %%s element \"%s\", got %d",
+							   param, jsonval->type));
+			string_expanded = expand_jsonval_string(buf, 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(buf, jsonval);
+			break;
+
+		case SpecTypeName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON struct for %%T element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_typename(buf, jsonval);
+			break;
+
+		case SpecOperatorName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON struct for %%O element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_operator(buf, jsonval);
+			break;
+
+		case SpecNumber:
+			if (jsonval->type != jbvNumeric)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON numeric for %%n element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_number(buf, jsonval);
+			break;
+
+		case SpecRole:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON struct for %%R element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_role(buf, jsonval);
+			break;
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+
+	return string_expanded;
+}
+
+/*
+ * 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 buf, char *param,
+				   JsonbValue *jsonarr, char *arraysep, convSpecifier specifier,
+				   const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+	JsonbContainer *container;
+	JsonbIterator *it;
+	JsonbValue	v;
+	int			type;
+	bool		first = true;
+	StringInfoData arrayelem;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (!jsonarr)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("element \"%s\" not found", param));
+
+	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 (!JsonContainerIsArray(container))
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("element \"%s\" is not a JSON array", param));
+
+	initStringInfo(&arrayelem);
+
+	it = JsonbIteratorInit(container);
+	while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+	{
+		if (type == WJB_ELEM)
+		{
+			resetStringInfo(&arrayelem);
+
+			if (expand_one_jsonb_element(&arrayelem, param, &v, specifier, NULL))
+			{
+				if (!first)
+					appendStringInfoString(buf, arraysep);
+
+				appendBinaryStringInfo(buf, arrayelem.data, arrayelem.len);
+				first = false;
+			}
+		}
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*
+ * Workhorse for ddl_deparse_expand_command.
+ */
+char *
+deparse_ddl_json_to_string(char *json_str)
+{
+	Datum		d;
+	Jsonb	   *jsonb;
+	StringInfo	buf = (StringInfo) palloc0(sizeof(StringInfoData));
+
+	initStringInfo(buf);
+
+	d = DirectFunctionCall1(jsonb_in, PointerGetDatum(json_str));
+	jsonb = (Jsonb *) DatumGetPointer(d);
+
+	expand_fmt_recursive(buf, &jsonb->root);
+
+	return buf->data;
+}
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.  Possible conversion specifiers are:
+ *
+ * %		expand to a literal %
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ * n		expand as a simple number (no quoting)
+ * R		expand as a role name (possibly quoted name, or PUBLIC)
+ *
+ * The element name may have an optional separator specification preceded
+ * by a colon.  Its presence indicates that the element is expected to be
+ * an array; the specified separator is used to join the array elements.
+ *------
+ */
+Datum
+ddl_deparse_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	char	   *json_str;
+
+	json_str = text_to_cstring(json);
+
+	PG_RETURN_TEXT_P(cstring_to_text(deparse_ddl_json_to_string(json_str)));
+}
+
+/*
+ * Error context callback for JSON format string expansion.
+ *
+ * XXX: indicate which element we're expanding, if applicable.
+ */
+static void
+fmtstr_error_callback(void *arg)
+{
+	errcontext("while expanding format string \"%s\"", (char *) arg);
+}
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index d4b00d1a82..cfc36b02d0 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -36,8 +36,10 @@
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
+#include "parser/parser.h"
 #include "pgstat.h"
 #include "tcop/deparse_utility.h"
+#include "tcop/ddl_deparse.h"
 #include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -48,45 +50,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
-typedef struct EventTriggerQueryState
-{
-	/* memory context for this state's objects */
-	MemoryContext cxt;
-
-	/* sql_drop */
-	slist_head	SQLDropList;
-	bool		in_sql_drop;
-
-	/* table_rewrite */
-	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite
-									 * event */
-	int			table_rewrite_reason;	/* AT_REWRITE reason */
-
-	/* Support for command collection */
-	bool		commandCollectionInhibited;
-	CollectedCommand *currentCommand;
-	List	   *commandList;	/* list of CollectedCommand; see
-								 * deparse_utility.h */
-	struct EventTriggerQueryState *previous;
-} EventTriggerQueryState;
-
-static EventTriggerQueryState *currentEventTriggerState = NULL;
-
-/* Support for dropped objects */
-typedef struct SQLDropObject
-{
-	ObjectAddress address;
-	const char *schemaname;
-	const char *objname;
-	const char *objidentity;
-	const char *objecttype;
-	List	   *addrnames;
-	List	   *addrargs;
-	bool		original;
-	bool		normal;
-	bool		istemp;
-	slist_node	next;
-} SQLDropObject;
+EventTriggerQueryState *currentEventTriggerState = NULL;
 
 static void AlterEventTriggerOwner_internal(Relation rel,
 											HeapTuple tup,
@@ -130,7 +94,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
 	if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
 		strcmp(stmt->eventname, "ddl_command_end") != 0 &&
 		strcmp(stmt->eventname, "sql_drop") != 0 &&
-		strcmp(stmt->eventname, "table_rewrite") != 0)
+		strcmp(stmt->eventname, "table_rewrite") != 0 &&
+		strcmp(stmt->eventname, "table_init_write") != 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("unrecognized event name \"%s\"",
@@ -156,7 +121,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
 	/* Validate tag list, if any. */
 	if ((strcmp(stmt->eventname, "ddl_command_start") == 0 ||
 		 strcmp(stmt->eventname, "ddl_command_end") == 0 ||
-		 strcmp(stmt->eventname, "sql_drop") == 0)
+		 strcmp(stmt->eventname, "sql_drop") == 0 ||
+		 strcmp(stmt->eventname, "table_init_write") == 0)
 		&& tags != NULL)
 		validate_ddl_tags("tag", tags);
 	else if (strcmp(stmt->eventname, "table_rewrite") == 0
@@ -560,6 +526,7 @@ EventTriggerCommonSetup(Node *parsetree,
 	List	   *cachelist;
 	ListCell   *lc;
 	List	   *runlist = NIL;
+	int			pub_deparse_func_cnt = 0;
 
 	/*
 	 * We want the list of command tags for which this procedure is actually
@@ -582,7 +549,8 @@ EventTriggerCommonSetup(Node *parsetree,
 		dbgtag = CreateCommandTag(parsetree);
 		if (event == EVT_DDLCommandStart ||
 			event == EVT_DDLCommandEnd ||
-			event == EVT_SQLDrop)
+			event == EVT_SQLDrop ||
+			event == EVT_TableInitWrite)
 		{
 			if (!command_tag_event_trigger_ok(dbgtag))
 				elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag));
@@ -609,6 +577,12 @@ EventTriggerCommonSetup(Node *parsetree,
 	 * once we do anything at all that touches the catalogs, an invalidation
 	 * might leave cachelist pointing at garbage, so we must do this before we
 	 * can do much else.
+	 *
+	 * Special handling for event triggers created as part of publications.
+	 * If there are multiple publications which publish ddls, only one set of the
+	 * event trigger functions need to be invoked. The ddl deparse event triggers
+	 * write to WAL, so no need to duplicate it as all walsenders will read the same
+	 * WAL.
 	 */
 	foreach(lc, cachelist)
 	{
@@ -616,8 +590,25 @@ EventTriggerCommonSetup(Node *parsetree,
 
 		if (filter_event_trigger(tag, item))
 		{
-			/* We must plan to fire this trigger. */
-			runlist = lappend_oid(runlist, item->fnoid);
+			static const char *trigger_func_prefix = "publication_deparse_%s";
+			char		trigger_func_name[NAMEDATALEN];
+			Oid			pub_funcoid;
+			List 		*pubfuncname;
+
+			/* Get function oid of the publication's ddl deparse event trigger */
+			snprintf(trigger_func_name, sizeof(trigger_func_name), trigger_func_prefix,
+					 eventstr);
+			pubfuncname = SystemFuncName(trigger_func_name);
+			pub_funcoid = LookupFuncName(pubfuncname, 0, NULL, true);
+
+			if (item->fnoid != pub_funcoid)
+				runlist = lappend_oid(runlist, item->fnoid);
+			else
+			{
+				/* Only the first ddl deparse event trigger needs to be invoked */
+				if (pub_deparse_func_cnt++ == 0)
+					runlist = lappend_oid(runlist, item->fnoid);
+			}
 		}
 	}
 
@@ -865,6 +856,140 @@ EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason)
 	CommandCounterIncrement();
 }
 
+/*
+ * EventTriggerTableInitWriteStart
+ *     Prepare to receive data on an CREATE TABLE AS/SELET INTO command about
+ *     to be executed.
+ */
+void
+EventTriggerTableInitWriteStart(Node *parsetree)
+{
+	MemoryContext oldcxt;
+	CollectedCommand *command;
+	CreateTableAsStmt *stmt = (CreateTableAsStmt *)parsetree;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	command = palloc(sizeof(CollectedCommand));
+
+	command->type = (stmt->objtype == OBJECT_TABLE) ? SCT_CreateTableAs : SCT_Simple;
+	command->in_extension = creating_extension;
+	command->d.ctas.address = InvalidObjectAddress;
+	command->d.ctas.real_create = NULL;
+	command->parsetree = copyObject(parsetree);
+
+	command->parent = currentEventTriggerState->currentCommand;
+	currentEventTriggerState->currentCommand = command;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerTableInitWriteEnd
+ *		Finish up saving an CREATE TABLE AS/SELECT INTO command.
+ *
+ * FIXME this API isn't considering the possibility that an xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerTableInitWriteEnd(ObjectAddress address)
+{
+	CollectedCommand *parent;
+	CreateTableAsStmt *stmt;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	stmt =  (CreateTableAsStmt *)currentEventTriggerState->currentCommand->parsetree;
+
+	if (stmt->objtype == OBJECT_TABLE)
+	{
+		parent = currentEventTriggerState->currentCommand->parent;
+
+		pfree(currentEventTriggerState->currentCommand);
+
+		currentEventTriggerState->currentCommand = parent;
+	}
+	else
+	{
+		MemoryContext oldcxt;
+
+		oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+		currentEventTriggerState->currentCommand->d.simple.address = address;
+		currentEventTriggerState->commandList =
+			lappend(currentEventTriggerState->commandList,
+					currentEventTriggerState->currentCommand);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+}
+
+/*
+ * Fire table_init_rewrite triggers.
+ */
+void
+EventTriggerTableInitWrite(Node *real_create, ObjectAddress address)
+{
+	List	   *runlist;
+	EventTriggerData trigdata;
+	CollectedCommand *command;
+
+	/*
+	 * See EventTriggerDDLCommandStart for a discussion about why event
+	 * triggers are disabled in single user mode.
+	 */
+	if (!IsUnderPostmaster)
+		return;
+
+	/*
+	 * Also do nothing if our state isn't set up, which it won't be if there
+	 * weren't any relevant event triggers at the start of the current DDL
+	 * command.  This test might therefore seem optional, but it's
+	 * *necessary*, because EventTriggerCommonSetup might find triggers that
+	 * didn't exist at the time the command started.
+	 */
+	if (!currentEventTriggerState)
+		return;
+
+	/* Do nothing if no command was collected. */
+	if (!currentEventTriggerState->currentCommand)
+		return;
+
+	command = currentEventTriggerState->currentCommand;
+
+	runlist = EventTriggerCommonSetup(command->parsetree,
+									  EVT_TableInitWrite,
+									  "table_init_write",
+									  &trigdata);
+	if (runlist == NIL)
+		return;
+
+	/* Set the real CreateTable statment and object address */
+	command->d.ctas.real_create = real_create;
+	command->d.ctas.address = address;
+
+	/* Run the triggers. */
+	EventTriggerInvoke(runlist, &trigdata);
+
+	/* Cleanup. */
+	list_free(runlist);
+
+	/*
+	 * Make sure anything the event triggers did will be visible to the main
+	 * command.
+	 */
+	CommandCounterIncrement();
+}
+
 /*
  * Invoke each event trigger in a list of event triggers.
  */
@@ -1146,7 +1271,8 @@ trackDroppedObjectsNeeded(void)
 	 */
 	return (EventCacheLookup(EVT_SQLDrop) != NIL) ||
 		(EventCacheLookup(EVT_TableRewrite) != NIL) ||
-		(EventCacheLookup(EVT_DDLCommandEnd) != NIL);
+		(EventCacheLookup(EVT_DDLCommandEnd) != NIL) ||
+		(EventCacheLookup(EVT_TableInitWrite) != NIL);
 }
 
 /*
@@ -1537,6 +1663,7 @@ EventTriggerAlterTableStart(Node *parsetree)
 
 	command->d.alterTable.classId = RelationRelationId;
 	command->d.alterTable.objectId = InvalidOid;
+	command->d.alterTable.rewrite = false;
 	command->d.alterTable.subcmds = NIL;
 	command->parsetree = copyObject(parsetree);
 
@@ -1570,7 +1697,7 @@ EventTriggerAlterTableRelid(Oid objectId)
  * internally, so that's all that this code needs to handle at the moment.
  */
 void
-EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
+EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address, bool rewrite)
 {
 	MemoryContext oldcxt;
 	CollectedATSubcmd *newsub;
@@ -1590,12 +1717,140 @@ EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
 	newsub->address = address;
 	newsub->parsetree = copyObject(subcmd);
 
+	currentEventTriggerState->currentCommand->d.alterTable.rewrite |= rewrite;
+	currentEventTriggerState->currentCommand->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTypeStart
+ *		Save data about a single part of an ALTER TYPE.
+ *
+ * ALTER TABLE can have multiple subcommands which might include DROP COLUMN
+ * command and ALTER TYPE referring the drop column in USING expression.
+ * As the dropped column cannot be accessed after the execution of DROP COLUMN,
+ * a special trigger is required to handle this case before the drop column is
+ * executed.
+ */
+void
+EventTriggerAlterTypeStart(AlterTableCmd *subcmd, Relation rel)
+{
+	MemoryContext oldcxt;
+	CollectedATSubcmd *newsub;
+	ColumnDef  *def;
+	Relation	attrelation;
+	HeapTuple	heapTup;
+	Form_pg_attribute attTup;
+	AttrNumber	attnum;
+	ObjectAddress address;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(subcmd->subtype == AT_AlterColumnType);
+	Assert(currentEventTriggerState->currentCommand != NULL);
+	Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId));
+
+	def = (ColumnDef *) subcmd->def;
+	Assert(IsA(def, ColumnDef));
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(CollectedATSubcmd));
+	newsub->parsetree = (Node *)copyObject(subcmd);
+
+	attrelation = table_open(AttributeRelationId, RowExclusiveLock);
+
+	/* Look up the target column */
+	heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), subcmd->name);
+	if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_COLUMN),
+				errmsg("column \"%s\" of relation \"%s\" does not exist",
+					   subcmd->name, RelationGetRelationName(rel)));
+	attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
+	attnum = attTup->attnum;
+
+	ObjectAddressSubSet(address, RelationRelationId,
+						RelationGetRelid(rel), attnum);
+	heap_freetuple(heapTup);
+	table_close(attrelation, RowExclusiveLock);
+	newsub->address = address;
+
+	if (def->raw_default)
+	{
+		char	   *defexpr;
+
+		defexpr = nodeToString(def->cooked_default);
+		newsub->usingexpr = TextDatumGetCString(DirectFunctionCall2(pg_get_expr,
+																	CStringGetTextDatum(defexpr),
+																	RelationGetRelid(rel)));
+	}
+	else
+		newsub->usingexpr = NULL;
+
 	currentEventTriggerState->currentCommand->d.alterTable.subcmds =
 		lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
 
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * EventTriggerAlterTypeEnd
+ *		Finish up saving an ALTER TYPE command, and add it to command list.
+ */
+void
+EventTriggerAlterTypeEnd(Node *subcmd, ObjectAddress address, bool rewrite)
+{
+	MemoryContext oldcxt;
+	CollectedATSubcmd *newsub;
+	ListCell   *cell;
+	CollectedCommand *cmd;
+	AlterTableCmd *altsubcmd = (AlterTableCmd *)subcmd;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	cmd = currentEventTriggerState->currentCommand;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(cmd != NULL);
+	Assert(OidIsValid(cmd->d.alterTable.objectId));
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
+		AlterTableCmd *collcmd = (AlterTableCmd *) sub->parsetree;
+
+		if (collcmd->subtype == altsubcmd->subtype &&
+			address.classId == sub->address.classId &&
+			address.objectId == sub->address.objectId &&
+			address.objectSubId == sub->address.objectSubId)
+		{
+			cmd->d.alterTable.rewrite |= rewrite;
+			return;
+		}
+	}
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(CollectedATSubcmd));
+	newsub->address = address;
+	newsub->parsetree = copyObject(subcmd);
+
+	cmd->d.alterTable.rewrite |= rewrite;
+	cmd->d.alterTable.subcmds = lappend(cmd->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
 /*
  * EventTriggerAlterTableEnd
  *		Finish up saving an ALTER TABLE command, and add it to command list.
@@ -1863,6 +2118,7 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 			case SCT_AlterOpFamily:
 			case SCT_CreateOpClass:
 			case SCT_AlterTSConfig:
+			case SCT_CreateTableAs:
 				{
 					char	   *identity;
 					char	   *type;
@@ -1880,6 +2136,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 						addr = cmd->d.createopc.address;
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
+					else if (cmd->type == SCT_CreateTableAs)
+						addr = cmd->d.ctas.address;
 
 					/*
 					 * If an object was dropped in the same command we may end
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 42cced9ebe..2844d36521 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -17,6 +17,8 @@ backend_sources += files(
   'copyto.c',
   'createas.c',
   'dbcommands.c',
+  'ddl_deparse.c',
+  'ddl_json.c',
   'define.c',
   'discard.c',
   'dropcmds.c',
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index bfe279cddf..2969d3df01 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1708,6 +1708,49 @@ process_owned_by(Relation seqrel, List *owned_by, bool for_identity)
 		relation_close(tablerel, NoLock);
 }
 
+/*
+ * Return sequence parameters, detailed
+ */
+Sequence_values *
+get_sequence_values(Oid sequenceId)
+{
+	Buffer      buf;
+	SeqTable    elm;
+	Relation    seqrel;
+	HeapTuple	seqtuple;
+	HeapTupleData seqtupledata;
+	Form_pg_sequence seqform;
+	Form_pg_sequence_data seq;
+	Sequence_values *seqvalues;
+
+	seqtuple = SearchSysCache1(SEQRELID, sequenceId);
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u", sequenceId);
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+
+	ReleaseSysCache(seqtuple);
+
+	/* Open and lock sequence */
+	init_sequence(sequenceId, &elm, &seqrel);
+
+	if (pg_class_aclcheck(sequenceId, GetUserId(),
+		ACL_SELECT | ACL_USAGE) != ACLCHECK_OK)
+		ereport(ERROR,
+				errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				errmsg("permission denied for sequence %s",
+					   RelationGetRelationName(seqrel)));
+
+	seq = read_seq_tuple(seqrel, &buf, &seqtupledata);
+
+	seqvalues = (Sequence_values *) palloc(sizeof(Sequence_values));
+	seqvalues->last_value = seq->last_value;
+	seqvalues->seqform = seqform;
+
+	UnlockReleaseBuffer(buf);
+	relation_close(seqrel, NoLock);
+
+	return seqvalues;
+}
 
 /*
  * Return sequence parameters in a list of the form created by the parser.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3147dddf28..0f2957367f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4727,6 +4727,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
 									  AT_PASS_UNSET, context);
 			Assert(cmd != NULL);
+
+			EventTriggerAlterTypeStart(cmd, rel);
+
 			/* Performs own recursion */
 			ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
 								  lockmode, context);
@@ -4998,6 +5001,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 {
 	ObjectAddress address = InvalidObjectAddress;
 	Relation	rel = tab->rel;
+	bool		commandCollected = false;
 
 	switch (cmd->subtype)
 	{
@@ -5121,6 +5125,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 		case AT_AlterColumnType:	/* ALTER COLUMN TYPE */
 			/* parse transformation was done earlier */
 			address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
+			EventTriggerAlterTypeEnd((Node *) cmd, address, tab->rewrite);
+			commandCollected = true;
 			break;
 		case AT_AlterColumnGenericOptions:	/* ALTER COLUMN OPTIONS */
 			address =
@@ -5293,8 +5299,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 	/*
 	 * Report the subcommand to interested event triggers.
 	 */
-	if (cmd)
-		EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
+	if (cmd && !commandCollected)
+		EventTriggerCollectAlterTableSubcmd((Node *) cmd, address, tab->rewrite);
 
 	/*
 	 * Bump the command counter to ensure the next subcommand in the sequence
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 15a1dab8c5..8d72eb63f4 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1392,6 +1392,7 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 		atcmd->cmds = atsubcmds;
 		atcmd->objtype = OBJECT_TABLE;
 		atcmd->missing_ok = false;
+		atcmd->table_like = true;
 		result = lcons(atcmd, result);
 	}
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 30b51bf4d3..4501cc337c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1675,8 +1675,11 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateTableAsStmt:
+				EventTriggerTableInitWriteStart(parsetree);
 				address = ExecCreateTableAs(pstate, (CreateTableAsStmt *) parsetree,
 											params, queryEnv, qc);
+				EventTriggerTableInitWriteEnd(address);
+				commandCollected = true;
 				break;
 
 			case T_RefreshMatViewStmt:
@@ -2206,6 +2209,112 @@ UtilityContainsQuery(Node *parsetree)
 	}
 }
 
+/*
+ * Return the given object type as a string.
+ *
+ * If isgrant is true, then this function is called while deparsing GRANT
+ * statement and some object names are replaced.
+ */
+const char *
+stringify_objtype(ObjectType objtype, bool isgrant)
+{
+	switch (objtype)
+	{
+		case OBJECT_AGGREGATE:
+			return "AGGREGATE";
+		case OBJECT_CAST:
+			return "CAST";
+		case OBJECT_COLLATION:
+			return "COLLATION";
+		case OBJECT_COLUMN:
+			return isgrant ? "TABLE" : "COLUMN";
+		case OBJECT_CONVERSION:
+			return "CONVERSION";
+		case OBJECT_DATABASE:
+			return "DATABASE";
+		case OBJECT_DOMAIN:
+			return "DOMAIN";
+		case OBJECT_EVENT_TRIGGER:
+			return "EVENT TRIGGER";
+		case OBJECT_EXTENSION:
+			return "EXTENSION";
+		case OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case OBJECT_FOREIGN_SERVER:
+			return isgrant ? "FOREIGN SERVER" : "SERVER";
+		case OBJECT_FOREIGN_TABLE:
+			return "FOREIGN TABLE";
+		case OBJECT_FUNCTION:
+			return "FUNCTION";
+		case OBJECT_INDEX:
+			return "INDEX";
+		case OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case OBJECT_MATVIEW:
+			return "MATERIALIZED VIEW";
+		case OBJECT_OPCLASS:
+			return "OPERATOR CLASS";
+		case OBJECT_OPERATOR:
+			return "OPERATOR";
+		case OBJECT_OPFAMILY:
+			return "OPERATOR FAMILY";
+		case OBJECT_POLICY:
+			return "POLICY";
+		case OBJECT_PROCEDURE:
+			return "PROCEDURE";
+		case OBJECT_ROLE:
+			return "ROLE";
+		case OBJECT_ROUTINE:
+			return "ROUTINE";
+		case OBJECT_RULE:
+			return "RULE";
+		case OBJECT_SCHEMA:
+			return "SCHEMA";
+		case OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case OBJECT_STATISTIC_EXT:
+			return "STATISTICS";
+		case OBJECT_TABLE:
+			return "TABLE";
+		case OBJECT_TABLESPACE:
+			return "TABLESPACE";
+		case OBJECT_TRIGGER:
+			return "TRIGGER";
+		case OBJECT_TSCONFIGURATION:
+			return "TEXT SEARCH CONFIGURATION";
+		case OBJECT_TSDICTIONARY:
+			return "TEXT SEARCH DICTIONARY";
+		case OBJECT_TSPARSER:
+			return "TEXT SEARCH PARSER";
+		case OBJECT_TSTEMPLATE:
+			return "TEXT SEARCH TEMPLATE";
+		case OBJECT_TYPE:
+			return "TYPE";
+		case OBJECT_USER_MAPPING:
+			return "USER MAPPING";
+		case OBJECT_VIEW:
+			return "VIEW";
+		case OBJECT_ACCESS_METHOD:
+		case OBJECT_AMOP:
+		case OBJECT_AMPROC:
+		case OBJECT_ATTRIBUTE:
+		case OBJECT_DEFAULT:
+		case OBJECT_DEFACL:
+		case OBJECT_DOMCONSTRAINT:
+		case OBJECT_PARAMETER_ACL:
+		case OBJECT_PUBLICATION:
+		case OBJECT_PUBLICATION_NAMESPACE:
+		case OBJECT_PUBLICATION_REL:
+		case OBJECT_SUBSCRIPTION:
+		case OBJECT_TABCONSTRAINT:
+		case OBJECT_TRANSFORM:
+			elog(ERROR, "unsupported object type %d", objtype);
+	}
+
+	return "???";				/* keep compiler quiet */
+}
 
 /*
  * AlterObjectTypeCommandTag
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 12402a0637..7b476adb23 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -27,8 +27,6 @@
 #include "utils/numeric.h"
 #include "utils/syscache.h"
 
-static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
-
 
 /*
  * SQL function: format_type(type_oid, typemod)
@@ -363,7 +361,7 @@ format_type_with_typemod(Oid type_oid, int32 typemod)
 /*
  * Add typmod decoration to the basic type name
  */
-static char *
+char *
 printTypmod(const char *typname, int32 typmod, Oid typmodout)
 {
 	char	   *res;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5f953338f3..ce8cb3e1a1 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -499,22 +499,15 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
 									   deparse_context *context);
 static void get_tablesample_def(TableSampleClause *tablesample,
 								deparse_context *context);
-static void get_opclass_name(Oid opclass, Oid actual_datatype,
-							 StringInfo buf);
 static Node *processIndirection(Node *node, deparse_context *context);
 static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context);
 static char *get_relation_name(Oid relid);
 static char *generate_relation_name(Oid relid, List *namespaces);
 static char *generate_qualified_relation_name(Oid relid);
-static char *generate_function_name(Oid funcid, int nargs,
-									List *argnames, Oid *argtypes,
-									bool has_variadic, bool *use_variadic_p,
-									ParseExprKind special_exprkind);
 static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
 static void add_cast_to(StringInfo buf, Oid typid);
 static char *generate_qualified_type_name(Oid typid);
 static text *string_to_text(char *str);
-static char *flatten_reloptions(Oid relid);
 static void get_reloptions(StringInfo buf, Datum reloptions);
 
 #define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")
@@ -1883,6 +1876,14 @@ pg_get_partkeydef_columns(Oid relid, bool pretty)
 	return pg_get_partkeydef_worker(relid, prettyFlags, true, false);
 }
 
+/* Internal version that reports the full partition key definition */
+char *
+pg_get_partkeydef_string(Oid relid)
+{
+	return pg_get_partkeydef_worker(relid, GET_PRETTY_FLAGS(false), false,
+									false);
+}
+
 /*
  * Internal workhorse to decompile a partition key definition.
  */
@@ -2130,6 +2131,16 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(string_to_text(res));
 }
 
+/*
+ * Internal version that returns the definition of a CONSTRAINT command
+ */
+char *
+pg_get_constraintdef_string(Oid constraintId)
+{
+	return pg_get_constraintdef_worker(constraintId, false,
+									   GET_PRETTY_FLAGS(false), false);
+}
+
 /*
  * Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command
  */
@@ -11579,7 +11590,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
  * actual_datatype.  (If you don't want this behavior, just pass
  * InvalidOid for actual_datatype.)
  */
-static void
+void
 get_opclass_name(Oid opclass, Oid actual_datatype,
 				 StringInfo buf)
 {
@@ -11973,7 +11984,7 @@ generate_qualified_relation_name(Oid relid)
  *
  * The result includes all necessary quoting and schema-prefixing.
  */
-static char *
+char *
 generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
 					   bool has_variadic, bool *use_variadic_p,
 					   ParseExprKind special_exprkind)
@@ -12359,7 +12370,7 @@ get_reloptions(StringInfo buf, Datum reloptions)
 /*
  * Generate a C string representing a relation's reloptions, or NULL if none.
  */
-static char *
+char *
 flatten_reloptions(Oid relid)
 {
 	char	   *result = NULL;
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c
index 1f5e7eb4c6..f2a9f5dcc2 100644
--- a/src/backend/utils/cache/evtcache.c
+++ b/src/backend/utils/cache/evtcache.c
@@ -167,6 +167,8 @@ BuildEventTriggerCache(void)
 			event = EVT_SQLDrop;
 		else if (strcmp(evtevent, "table_rewrite") == 0)
 			event = EVT_TableRewrite;
+		else if (strcmp(evtevent, "table_init_write") == 0)
+			event = EVT_TableInitWrite;
 		else
 			continue;
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5736c1082c..dfe99b6475 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12053,4 +12053,11 @@
   proname => 'any_value_transfn', prorettype => 'anyelement',
   proargtypes => 'anyelement anyelement', prosrc => 'any_value_transfn' },
 
+{ oid => '4642', descr => 'deparse the DDL command into a JSON format string',
+  proname => 'ddl_deparse_to_json', prorettype => 'text',
+  proargtypes => 'pg_ddl_command', prosrc => 'ddl_deparse_to_json' },
+{ oid => '4643', descr => 'expand JSON format DDL to a plain text DDL command',
+  proname => 'ddl_deparse_expand_command', prorettype => 'text',
+  proargtypes => 'text', prosrc => 'ddl_deparse_expand_command' },
+
 ]
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 5ed6ece555..cba4e72455 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -16,6 +16,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "catalog/pg_event_trigger.h"
+#include "lib/ilist.h"
 #include "nodes/parsenodes.h"
 #include "tcop/cmdtag.h"
 #include "tcop/deparse_utility.h"
@@ -29,6 +30,44 @@ typedef struct EventTriggerData
 	CommandTag	tag;
 } EventTriggerData;
 
+typedef struct EventTriggerQueryState
+{
+	/* memory context for this state's objects */
+	MemoryContext cxt;
+
+	/* sql_drop */
+	slist_head	SQLDropList;
+	bool		in_sql_drop;
+
+	/* table_rewrite */
+	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite
+									 * event */
+	int			table_rewrite_reason;	/* AT_REWRITE reason */
+
+	/* Support for command collection */
+	bool		commandCollectionInhibited;
+	CollectedCommand *currentCommand;
+	List	   *commandList;	/* list of CollectedCommand; see
+								 * deparse_utility.h */
+	struct EventTriggerQueryState *previous;
+} EventTriggerQueryState;
+
+/* Support for dropped objects */
+typedef struct SQLDropObject
+{
+	ObjectAddress address;
+	const char *schemaname;
+	const char *objname;
+	const char *objidentity;
+	const char *objecttype;
+	List	   *addrnames;
+	List	   *addrargs;
+	bool		original;
+	bool		normal;
+	bool		istemp;
+	slist_node	next;
+} SQLDropObject;
+
 #define AT_REWRITE_ALTER_PERSISTENCE	0x01
 #define AT_REWRITE_DEFAULT_VAL			0x02
 #define AT_REWRITE_COLUMN_REWRITE		0x04
@@ -55,6 +94,10 @@ extern void EventTriggerDDLCommandEnd(Node *parsetree);
 extern void EventTriggerSQLDrop(Node *parsetree);
 extern void EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason);
 
+extern void EventTriggerTableInitWriteStart(Node *parsetree);
+extern void EventTriggerTableInitWrite(Node *parsetree, ObjectAddress address);
+extern void EventTriggerTableInitWriteEnd(ObjectAddress address);
+
 extern bool EventTriggerBeginCompleteQuery(void);
 extern void EventTriggerEndCompleteQuery(void);
 extern bool trackDroppedObjectsNeeded(void);
@@ -71,7 +114,12 @@ extern void EventTriggerCollectSimpleCommand(ObjectAddress address,
 extern void EventTriggerAlterTableStart(Node *parsetree);
 extern void EventTriggerAlterTableRelid(Oid objectId);
 extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd,
-												ObjectAddress address);
+												ObjectAddress address,
+												bool rewrite);
+
+extern void EventTriggerAlterTypeStart(AlterTableCmd *subcmd, Relation rel);
+extern void EventTriggerAlterTypeEnd(Node *subcmd, ObjectAddress address,
+									 bool rewrite);
 extern void EventTriggerAlterTableEnd(void);
 
 extern void EventTriggerCollectGrant(InternalGrant *istmt);
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 7db7b3da7b..c0a39596ac 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -15,6 +15,7 @@
 
 #include "access/xlogreader.h"
 #include "catalog/objectaddress.h"
+#include "catalog/pg_sequence.h"
 #include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "nodes/parsenodes.h"
@@ -51,9 +52,17 @@ typedef struct xl_seq_rec
 	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
 } xl_seq_rec;
 
+/* Information needed to define a sequence. */
+typedef struct Sequence_values
+{
+	Form_pg_sequence seqform;
+	int64		last_value;
+} Sequence_values;
+
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
+extern Sequence_values *get_sequence_values(Oid sequenceId);
 
 extern ObjectAddress DefineSequence(ParseState *pstate, CreateSeqStmt *seq);
 extern ObjectAddress AlterSequence(ParseState *pstate, AlterSeqStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1c296da326..1acaedfe49 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2167,6 +2167,7 @@ typedef struct AlterTableStmt
 	List	   *cmds;			/* list of subcommands */
 	ObjectType	objtype;		/* type of object */
 	bool		missing_ok;		/* skip error if table missing */
+	bool        table_like;		/* internally generated for TableLikeClause */
 } AlterTableStmt;
 
 typedef enum AlterTableType
diff --git a/src/include/tcop/ddl_deparse.h b/src/include/tcop/ddl_deparse.h
new file mode 100644
index 0000000000..1a2702c5ac
--- /dev/null
+++ b/src/include/tcop/ddl_deparse.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_deparse.h
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/ddl_deparse.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DDL_DEPARSE_H
+#define DDL_DEPARSE_H
+
+#include "tcop/deparse_utility.h"
+
+extern char *deparse_utility_command(CollectedCommand *cmd, bool verbose_mode);
+extern char *deparse_ddl_json_to_string(char *jsonb);
+extern char *deparse_drop_command(const char *objidentity, const char *objecttype,
+								  DropBehavior behavior);
+
+#endif							/* DDL_DEPARSE_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index b585810b9a..a4a12377b8 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -29,7 +29,8 @@ typedef enum CollectedCommandType
 	SCT_AlterOpFamily,
 	SCT_AlterDefaultPrivileges,
 	SCT_CreateOpClass,
-	SCT_AlterTSConfig
+	SCT_AlterTSConfig,
+	SCT_CreateTableAs
 } CollectedCommandType;
 
 /*
@@ -39,6 +40,7 @@ typedef struct CollectedATSubcmd
 {
 	ObjectAddress address;		/* affected column, constraint, index, ... */
 	Node	   *parsetree;
+	char	   *usingexpr;
 } CollectedATSubcmd;
 
 typedef struct CollectedCommand
@@ -62,6 +64,7 @@ typedef struct CollectedCommand
 		{
 			Oid			objectId;
 			Oid			classId;
+			bool		rewrite;
 			List	   *subcmds;
 		}			alterTable;
 
@@ -100,6 +103,13 @@ typedef struct CollectedCommand
 		{
 			ObjectType	objtype;
 		}			defprivs;
+
+		/* CREATE TABLE AS */
+		struct
+		{
+			ObjectAddress address;
+			Node		 *real_create;
+		}			ctas;
 	}			d;
 
 	struct CollectedCommand *parent;	/* when nested */
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 59e64aea07..a68ce3d336 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -99,6 +99,8 @@ extern Query *UtilityContainsQuery(Node *parsetree);
 
 extern CommandTag CreateCommandTag(Node *parsetree);
 
+extern const char *stringify_objtype(ObjectType objtype, bool isgrant);
+
 static inline const char *
 CreateCommandName(Node *parsetree)
 {
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2f8b46d6da..c90c32bff1 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -127,6 +127,7 @@ extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 extern char *format_type_be(Oid type_oid);
 extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
+extern char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
 
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
 
diff --git a/src/include/utils/evtcache.h b/src/include/utils/evtcache.h
index d340026518..91d4bdd6b3 100644
--- a/src/include/utils/evtcache.h
+++ b/src/include/utils/evtcache.h
@@ -22,7 +22,8 @@ typedef enum
 	EVT_DDLCommandStart,
 	EVT_DDLCommandEnd,
 	EVT_SQLDrop,
-	EVT_TableRewrite
+	EVT_TableRewrite,
+	EVT_TableInitWrite
 } EventTriggerEvent;
 
 typedef struct
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 1a42d9f39b..9adc589173 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -26,9 +26,11 @@ extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
 extern char *pg_get_querydef(Query *query, bool pretty);
 
 extern char *pg_get_partkeydef_columns(Oid relid, bool pretty);
+extern char *pg_get_partkeydef_string(Oid relid);
 extern char *pg_get_partconstrdef_string(Oid partitionId, char *aliasname);
 
 extern char *pg_get_constraintdef_command(Oid constraintId);
+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);
@@ -40,7 +42,14 @@ extern List *select_rtable_names_for_explain(List *rtable,
 											 Bitmapset *rels_used);
 extern char *generate_collation_name(Oid collid);
 extern char *generate_opclass_name(Oid opclass);
+extern char *generate_function_name(Oid funcid, int nargs, List *argnames,
+									Oid *argtypes, bool has_variadic,
+									bool *use_variadic_p,
+									ParseExprKind special_exprkind);
 extern char *get_range_partbound_string(List *bound_datums);
+extern void get_opclass_name(Oid opclass, Oid actual_datatype,
+							 StringInfo buf);
+extern char *flatten_reloptions(Oid relid);
 
 extern char *pg_get_statisticsobjdef_string(Oid statextid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 75a296920e..6e5791a46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1626,6 +1626,9 @@ OSInfo
 OSSLCipher
 OSSLDigest
 OVERLAPPED
+ObjElem
+ObjTree
+ObjType
 ObjectAccessDrop
 ObjectAccessNamespaceSearch
 ObjectAccessPostAlter
@@ -3201,6 +3204,7 @@ compare_context
 config_var_value
 contain_aggs_of_level_context
 convert_testexpr_context
+convSpecifier
 copy_data_dest_cb
 copy_data_source_cb
 core_YYSTYPE
-- 
2.30.0.windows.2