0008-Create-table-object-tree-removal-2023_05_08.patch

application/octet-stream

Filename: 0008-Create-table-object-tree-removal-2023_05_08.patch
Type: application/octet-stream
Part: 7
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 0008
Subject: create table object tree removal
File+
src/backend/commands/ddldeparse.c 1265 3
From 6d520b7594c980a1eb431036f41f76fcac550ad2 Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Mon, 8 May 2023 15:37:43 +0530
Subject: [PATCH 8/8] create table object tree removal

---
 src/backend/commands/ddldeparse.c | 1268 ++++++++++++++++++++++++++++-
 1 file changed, 1265 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c
index 5878d7e465..f9805935dd 100644
--- a/src/backend/commands/ddldeparse.c
+++ b/src/backend/commands/ddldeparse.c
@@ -160,6 +160,32 @@ static List *deparse_TableElements(Relation relation, List *tableElements, List
 								   bool typed, bool composite);
 static void mark_function_volatile(ddl_deparse_context *context, Node *expr);
 
+static void deparse_ColumnDef_toJsonb(JsonbParseState **state, Relation relation, List *dpcontext, bool composite,
+									  ColumnDef *coldef, bool is_alter, Node **expr, bool *belemsAdded);
+
+static void deparse_TableElementsToJsonb(JsonbParseState **state, Relation relation, List *tableElements, List *dpcontext,
+										 bool typed, bool composite, bool *belemsAdded);
+static void insert_jsonb_key(JsonbParseState *state, char *name);
+
+static void insert_jsonb_str_element(JsonbParseState *state, char *name, char *value);
+
+static void insert_jsonb_bool_element(JsonbParseState *state, char *name, bool value);
+
+static void insert_str_object(JsonbParseState *state, char *fmt, char *key, char *val);
+
+static void insert_not_present_obj(JsonbParseState *state, char *fmt);
+
+static void fmt_to_jsonb_element(JsonbParseState *state, char *fmtStr);
+
+static void new_jsonb_for_type(JsonbParseState *state, Oid typeId, int32 typmod);
+
+static void new_jsonb_for_qualname(JsonbParseState *state, Oid nspid, char *objName, char* keyName);
+
+static void new_jsonb_for_qualname_id(JsonbParseState *state, Oid classId, Oid objectId, char* keyName);
+
+static JsonbValue *parsetree_to_jsonb_create_table(JsonbParseState *state, Oid objectId,
+												   Node *parsetree, char *owner);
+
 /*
  * Mark the func_volatile flag for an expression in the command.
  */
@@ -3187,7 +3213,16 @@ deparse_simple_command(CollectedCommand *cmd, bool *include_owner)
 			return deparse_CreateSeqStmt(objectId, parsetree);
 
 		case T_CreateStmt:
-			return deparse_CreateStmt(objectId, parsetree);
+			JsonbValue *value;
+
+			/* get direct jsonbValue object from parsetree */
+			value = parsetree_to_jsonb_create_table(NULL, objectId, parsetree, NULL);
+
+			/*
+			 * we are returning jsonb*, but typecast to ObjTree* for the
+			 * time being to satisfy the prototype
+			 */
+			return (ObjTree *)JsonbValueToJsonb(value);
 
 		case T_RenameStmt:
 			*include_owner = false;
@@ -3265,9 +3300,18 @@ deparse_utility_command(CollectedCommand *cmd, ddl_deparse_context *context)
 	{
 		Jsonb	   *jsonb;
 
-		jsonb = context->include_owner ? objtree_to_jsonb(tree, cmd->role) :
+		if (nodeTag(cmd->parsetree) == T_CreateStmt)
+		{
+			/* no object tree as intermediate stage for Create-Table */
+			jsonb = (Jsonb *)tree;
+			command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
+		}
+		else
+		{
+			jsonb = context->include_owner ? objtree_to_jsonb(tree, cmd->role) :
 										 objtree_to_jsonb(tree, NULL);
-		command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
+			command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
+		}
 	}
 
 	/*
@@ -3304,3 +3348,1221 @@ ddl_deparse_to_json(PG_FUNCTION_ARGS)
 
 	PG_RETURN_NULL();
 }
+
+/*
+ * Insert JsonbValue key to JsonbParseState
+ */
+static void
+insert_jsonb_key(JsonbParseState *state, char *name)
+{
+	JsonbValue	key;
+
+	/* Push the key */
+	key.type = jbvString;
+	key.val.string.val = name;
+	key.val.string.len = strlen(name);
+	pushJsonbValue(&state, WJB_KEY, &key);
+}
+
+/*
+ * Insert JsonbValue key:value pair to JsonbParseState, the
+ * value here is of type jbvString.
+ */
+static void
+insert_jsonb_str_element(JsonbParseState *state, char *name, char *value)
+{
+	JsonbValue	val;
+
+	/* Push the key first */
+	insert_jsonb_key(state, name);
+
+	/* Push the value now */
+	val.type = jbvString;
+	val.val.string.len = strlen(value);
+	val.val.string.val = pstrdup(value);
+	pushJsonbValue(&state, WJB_VALUE, &val);
+}
+
+/*
+ * Insert JsonbValue key:value pair to JsonbParseState, the
+ * value here is of type jbvBool.
+ */
+static void
+insert_jsonb_bool_element(JsonbParseState *state, char *name, bool value)
+{
+	JsonbValue	val;
+
+	/* Push the key first */
+	insert_jsonb_key(state, name);
+
+	/* Push the value now */
+	val.type = jbvBool;
+	val.val.boolean = value;
+	pushJsonbValue(&state, WJB_VALUE, &val);
+}
+
+/*
+ * Insert JsonbValue key and the jsonb array to JsonbParseState
+ */
+static void
+insert_jsonb_array_oid(JsonbParseState *state, char *keyname, List *array)
+{
+	ListCell   *lc;
+
+	/* Push the key first */
+	insert_jsonb_key(state, keyname);
+
+	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+	/* Push the array elements now */
+	foreach(lc, array)
+		new_jsonb_for_qualname_id(state, RelationRelationId, lfirst_oid(lc),
+								  NULL);
+
+	pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+}
+
+/*
+ * Insert new Object with one key:value pair to JsonbParseState, the
+ * value here is of jbvString.
+ */
+static void
+insert_str_object(JsonbParseState *state, char *fmt, char *keynm, char *keyval)
+{
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	fmt_to_jsonb_element(state, fmt);
+
+	/* push key-value pair */
+	insert_jsonb_str_element(state, keynm, keyval);
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+
+/*
+ * Insert new object with present:false as key-value pair
+ * to JsonbParseState
+ */
+static void
+insert_not_present_obj(JsonbParseState *state, char *fmt)
+{
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	fmt_to_jsonb_element(state, fmt);
+
+	/* push key-value pair */
+	insert_jsonb_bool_element(state, "present", false);
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Insert the format string into the output parse state.
+ */
+static void
+fmt_to_jsonb_element(JsonbParseState *state, char *fmtStr)
+{
+	Assert(fmtStr);
+	insert_jsonb_str_element(state, "fmt", fmtStr);
+}
+
+/*
+ * A helper routine to insert jsonb for coltyp to JsonbParseState
+ */
+static void
+new_jsonb_for_type(JsonbParseState *state, 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("");
+
+	/* create object now for its value */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	/* coltype object will have 4 elements */
+	insert_jsonb_str_element(state, "schemaname", type_nsp);
+	insert_jsonb_str_element(state, "typename", type_name);
+	insert_jsonb_str_element(state, "typmod", typmodstr);
+	insert_jsonb_bool_element(state, "typarray", type_array);
+
+	/* mark end of object */
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * A helper routine to set up name: schemaname, objname
+ *
+ * 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 void
+new_jsonb_for_qualname(JsonbParseState *state, Oid nspid, char *objName, char *keyName)
+{
+	char	   *namespace;
+
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+
+	/* Push the key first */
+	if (keyName)
+		insert_jsonb_key(state, keyName);
+
+	/* create object now for its value */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	/* Add schemaname and objname now */
+	insert_jsonb_str_element(state, "schemaname", namespace);
+	insert_jsonb_str_element(state, "objname", objName);
+
+	/* mark end of object */
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * A helper routine to set up name: 'schemaname, objname' where the object is
+ * specified by classId and objId.
+ */
+static void
+new_jsonb_for_qualname_id(JsonbParseState *state, Oid classId, Oid objectId, char *keyName)
+{
+	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);
+
+	new_jsonb_for_qualname(state, DatumGetObjectId(obj_nsp),
+						   NameStr(*DatumGetName(obj_name)), keyName);
+	table_close(catalog, AccessShareLock);
+}
+
+/*
+ * A helper function to insert collate object for column definition
+ */
+static void
+insertCollateObject(JsonbParseState *state, char *fmt, Oid classId, Oid objectId)
+{
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	fmt_to_jsonb_element(state, fmt);
+
+	/* push object now */
+	new_jsonb_for_qualname_id(state, classId, objectId, "collation_name");
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * A helper function to insert identity object for the table definition
+ */
+static void
+insertIdentityObject(JsonbParseState *state, Oid nspid, char *relname)
+{
+	char	   *namespace;
+
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+
+	/* Push the key first */
+	insert_jsonb_key(state, "identity");
+
+	/* create object now for its value */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	/* identity object will have 2 elements */
+	insert_jsonb_str_element(state, "schemaname", namespace);
+	insert_jsonb_str_element(state, "objname", relname);
+
+	/* mark end of object */
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence CACHE option to Jsonb
+ *
+ * Verbose syntax
+ * SET CACHE %{value}s
+ * OR
+ * CACHE %{value}
+ */
+static inline void
+deparse_Seq_Cache_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata, bool alter_table)
+{
+	char	   *fmt;
+
+	fmt = alter_table ? "SET CACHE %{value}s" : "CACHE %{value}s";
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	fmt_to_jsonb_element(state, fmt);
+
+	insert_jsonb_str_element(state, "clause", "cache");
+	insert_jsonb_str_element(state, "value", psprintf(INT64_FORMAT, seqdata->seqcache));
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence CYCLE option to Jsonb.
+ *
+ * Verbose syntax
+ * SET %{no}s CYCLE
+ * OR
+ * %{no}s CYCLE
+ */
+static inline void
+deparse_Seq_Cycle_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata, bool alter_table)
+{
+	char	   *fmt;
+
+	fmt = alter_table ? "SET %{no}s CYCLE" : "%{no}s CYCLE";
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	fmt_to_jsonb_element(state, fmt);
+
+	insert_jsonb_str_element(state, "clause", "cycle");
+	insert_jsonb_str_element(state, "no", seqdata->seqcycle ? "" : "NO");
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence INCREMENT BY option to Jsonb
+ *
+ * Verbose syntax
+ * SET INCREMENT BY %{value}s
+ * OR
+ * INCREMENT BY %{value}s
+ */
+static inline void
+deparse_Seq_IncrementBy_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata, bool alter_table)
+{
+	char	   *fmt;
+
+	fmt = alter_table ? "SET INCREMENT BY %{value}s" : "INCREMENT BY %{value}s";
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	fmt_to_jsonb_element(state, fmt);
+
+	insert_jsonb_str_element(state, "clause", "seqincrement");
+	insert_jsonb_str_element(state, "value", psprintf(INT64_FORMAT, seqdata->seqincrement));
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence MAXVALUE option to Jsonb.
+ *
+ * Verbose syntax
+ * SET MAXVALUE %{value}s
+ * OR
+ * MAXVALUE %{value}s
+ */
+static inline void
+deparse_Seq_Maxvalue_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata, bool alter_table)
+{
+	char	   *fmt;
+
+	fmt = alter_table ? "SET MAXVALUE %{value}s" : "MAXVALUE %{value}s";
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	fmt_to_jsonb_element(state, fmt);
+
+	insert_jsonb_str_element(state, "clause", "maxvalue");
+	insert_jsonb_str_element(state, "value", psprintf(INT64_FORMAT, seqdata->seqmax));
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence MINVALUE option to Jsonb
+ *
+ * Verbose syntax
+ * SET MINVALUE %{value}s
+ * OR
+ * MINVALUE %{value}s
+ */
+static inline void
+deparse_Seq_Minvalue_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata, bool alter_table)
+{
+	char	   *fmt;
+
+	fmt = alter_table ? "SET MINVALUE %{value}s" : "MINVALUE %{value}s";
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	fmt_to_jsonb_element(state, fmt);
+
+	insert_jsonb_str_element(state, "clause", "minvalue");
+	insert_jsonb_str_element(state, "value", psprintf(INT64_FORMAT, seqdata->seqmin));
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence START WITH option to Jsonb.
+ *
+ * Verbose syntax
+ * SET START WITH %{value}s
+ * OR
+ * START WITH %{value}s
+ */
+static inline void
+deparse_Seq_Startwith_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata, bool alter_table)
+{
+	char	   *fmt;
+
+	fmt = alter_table ? "SET START WITH %{value}s" : "START WITH %{value}s";
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	fmt_to_jsonb_element(state, fmt);
+
+	insert_jsonb_str_element(state, "clause", "start");
+	insert_jsonb_str_element(state, "value", psprintf(INT64_FORMAT, seqdata->seqstart));
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence RESTART option to Jsonb
+ *
+ * Verbose syntax
+ * RESTART %{value}s
+ */
+static inline void
+deparse_Seq_Restart_toJsonb(JsonbParseState *state, int64 last_value)
+{
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	fmt_to_jsonb_element(state, "RESTART %{value}s");
+
+	insert_jsonb_str_element(state, "clause", "restart");
+	insert_jsonb_str_element(state, "value", psprintf(INT64_FORMAT, last_value));
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+
+/*
+ * Deparse the definition of column identity to Jsonb.
+ *
+ * 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 void
+deparse_ColumnIdentity_toJsonb(JsonbParseState *state, Oid seqrelid, char identity, bool alter_table)
+{
+	Form_pg_sequence seqform;
+	Sequence_values *seqvalues;
+	char	   *fmt;
+
+	if (alter_table)
+		fmt = "%{identity_type}s %{seq_definition: }s";
+	else
+		fmt = "%{identity_type}s ( %{seq_definition: }s )";
+
+	/* create object now for value of identity_column */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	fmt_to_jsonb_element(state, fmt);
+
+	/* identity_type object creation */
+	{
+		char	   *identfmt;
+
+		/* Push the key first */
+		insert_jsonb_key(state, "identity_type");
+
+		if (alter_table)
+			identfmt = "SET GENERATED %{option}s";
+		else
+			identfmt = "GENERATED %{option}s AS IDENTITY";
+
+		if (identity == ATTRIBUTE_IDENTITY_ALWAYS)
+			insert_str_object(state, identfmt, "option", "ALWAYS");
+		else if (identity == ATTRIBUTE_IDENTITY_BY_DEFAULT)
+			insert_str_object(state, identfmt, "option", "BY DEFAULT");
+		else
+			insert_not_present_obj(state, verbose ? identfmt : (alter_table ? "SET GENERATED " : "GENERATED "));
+	}
+
+	/* seq_definition array object creation */
+	{
+
+		/* Push the key first */
+		insert_jsonb_key(state, "seq_definition");
+
+		pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+		seqvalues = get_sequence_values(seqrelid);
+		seqform = seqvalues->seqform;
+
+		/* Definition elements */
+		deparse_Seq_Cache_toJsonb(state, seqform, alter_table);
+		deparse_Seq_Cycle_toJsonb(state, seqform, alter_table);
+		deparse_Seq_IncrementBy_toJsonb(state, seqform, alter_table);
+		deparse_Seq_Minvalue_toJsonb(state, seqform, alter_table);
+		deparse_Seq_Maxvalue_toJsonb(state, seqform, alter_table);
+		deparse_Seq_Startwith_toJsonb(state, seqform, alter_table);
+		deparse_Seq_Restart_toJsonb(state, seqvalues->last_value);
+		/* We purposefully do not emit OWNED BY here */
+
+		pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+	}
+
+	/* end of idendity_column object */
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+
+/*
+ * 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 STORAGE %{colstorage}s %{compression}s %{collation}s
+ *  %{not_null}s %{default}s %{identity_column}s %{generated_column}s"
+ */
+static void
+deparse_ColumnDef_toJsonb(JsonbParseState **state, Relation relation,
+						  List *dpcontext, bool composite, ColumnDef *coldef,
+						  bool is_alter, Node **expr, bool *belemsAdded)
+{
+	Oid			relid = RelationGetRelid(relation);
+	HeapTuple	attrTup;
+	Form_pg_attribute attrForm;
+	Oid			typid;
+	int32		typmod;
+	Oid			typcollation;
+	char	   *fmtStr = "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s "
+		"%{not_null}s %{default}s %{identity_column}s %{generated_column}s";
+	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;
+
+	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);
+
+	if (!*belemsAdded)
+	{
+		*belemsAdded = true;
+		insert_jsonb_key(*state, "table_elements");
+		pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
+	}
+
+	/* start making column object */
+	pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
+	fmt_to_jsonb_element(*state, fmtStr);
+
+	/* creat name and type elements for column */
+	insert_jsonb_str_element(*state, "name", coldef->colname);
+	insert_jsonb_str_element(*state, "type", "column");
+
+	/*
+	 * create coltype object having 4 elements: schemaname, typename, typemod,
+	 * typearray
+	 */
+	{
+		/* Push the key first */
+		insert_jsonb_key(*state, "coltype");
+
+		/* Push the value */
+		new_jsonb_for_type(*state, typid, typmod);
+	}
+
+	/* STORAGE clause */
+	if (!composite)
+		insert_jsonb_str_element(*state, "colstorage", storage_name(attrForm->attstorage));
+
+	/* COMPRESSION clause */
+	{
+		/* Push the key first */
+		insert_jsonb_key(*state, "compression");
+
+		/* Push the value now: object in this case */
+		if (coldef->compression)
+			insert_str_object(*state, "COMPRESSION %{compression_method}I",
+					"compression_method", coldef->compression);
+		else
+			insert_not_present_obj(*state, verbose ? "COMPRESSION %{compression_method}I" : "COMPRESSION");
+	}
+
+	/* COLLATE clause */
+	{
+		/* Push the key first */
+		insert_jsonb_key(*state, "collation");
+
+		if (OidIsValid(typcollation))
+			insertCollateObject(*state, "COLLATE %{collation_name}D", CollationRelationId, typcollation);
+		else
+			insert_not_present_obj(*state, verbose ? "COLLATE %{collation_name}D" : "COLLATE");
+	}
+
+	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;
+
+		/* NOT NULL */
+		insert_jsonb_str_element(*state, "not_null", saw_notnull ? "NOT NULL" : "");
+
+		/* DEFAULT: Push the key first */
+		insert_jsonb_key(*state, "default");
+
+		/* DEFAULT: Push the value now */
+		if (attrForm->atthasdef &&
+			coldef->generated != ATTRIBUTE_GENERATED_STORED)
+		{
+			char	   *defstr;
+
+			defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+											  dpcontext, expr);
+
+			insert_str_object(*state, "DEFAULT %{default}s", "default",  defstr);
+		}
+		else
+			insert_not_present_obj(*state, verbose ? "DEFAULT %{default}s" : "DEFAULT");
+
+
+		/* IDENTITY COLUMN */
+		if (coldef->identity)
+		{
+			/*
+			 * For identity column, find the sequence owned by column in order
+			 * to deparse the column definition.
+			 */
+			seqrelid = getIdentitySequence(relid, attrForm->attnum, true);
+			if (OidIsValid(seqrelid) && coldef->identitySequence)
+				seqrelid = RangeVarGetRelid(coldef->identitySequence, NoLock, false);
+		}
+
+		/* IDENTITY COLUMN: Push the key first */
+		insert_jsonb_key(*state, "identity_column");
+
+		/* IDENTITY COLUMN: Push the value now */
+		if (OidIsValid(seqrelid))
+		{
+
+			deparse_ColumnIdentity_toJsonb(*state, seqrelid, coldef->identity, is_alter);
+		}
+		else
+			insert_not_present_obj(*state, verbose ? "%{identity_column}s" : "");
+
+		/* GENERATED COLUMN EXPRESSION: Push the key first */
+		insert_jsonb_key(*state, "generated_column");
+
+		/* GENERATED COLUMN EXPRESSION: Push the value now */
+		if (coldef->generated == ATTRIBUTE_GENERATED_STORED)
+		{
+			char	   *defstr;
+
+			defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+											  dpcontext, expr);
+			insert_str_object(*state, "GENERATED ALWAYS AS (%{generation_expr}s) STORED", "generation_expr", defstr);
+		}
+		else
+			insert_not_present_obj(*state, verbose ? "GENERATED ALWAYS AS (%{generation_expr}s) STORED" : "GENERATED ALWAYS AS");
+	}
+	ReleaseSysCache(attrTup);
+
+	/* mark the end of one column object */
+	pushJsonbValue(state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * 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 void
+deparse_ColumnDef_typed_toJsonb(JsonbParseState **state, Relation relation,
+								List *dpcontext, ColumnDef *coldef,
+								bool *belemsAdded)
+{
+	Oid			relid = RelationGetRelid(relation);
+	HeapTuple	attrTup;
+	Form_pg_attribute attrForm;
+	Oid			typid;
+	int32		typmod;
+	Oid			typcollation;
+	bool		saw_notnull;
+	ListCell   *cell;
+	char	   *fmtStr = "%{name}I WITH OPTIONS %{not_null}s %{default}s";
+
+	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;
+	}
+
+	if (!*belemsAdded)
+	{
+		*belemsAdded = true;
+		insert_jsonb_key(*state, "table_elements");
+		pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
+	}
+
+	/* start making column object */
+	pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
+	fmt_to_jsonb_element(*state, fmtStr);
+
+	/* Insert TYPE, NAME and NOT_NULL elements */
+	insert_jsonb_str_element(*state, "type", "column");
+	insert_jsonb_str_element(*state, "name", coldef->colname);
+	insert_jsonb_str_element(*state, "not_null", saw_notnull ? "NOT NULL" : "");
+
+	/* DEFAULT: Push the key first */
+	insert_jsonb_key(*state, "default");
+
+	/* DEFAULT: Push the value now */
+	if (attrForm->atthasdef)
+	{
+		char	   *defstr;
+
+		defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+										  dpcontext, NULL);
+		insert_str_object(*state, "DEFAULT %{default}s", "default",  defstr);
+	}
+	else
+		insert_not_present_obj(*state, verbose ? "DEFAULT %{default}s" : "DEFAULT");
+
+	/* mark the end of column object */
+	pushJsonbValue(state, WJB_END_OBJECT, NULL);
+
+	/* Generated columns are not supported on typed tables, so we are done */
+
+	ReleaseSysCache(attrTup);
+}
+
+
+/*
+ * 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 void
+deparse_TableElementsToJsonb(JsonbParseState **state, Relation relation,
+							 List *tableElements, List *dpcontext,
+							 bool typed, bool composite, bool *belemsAdded)
+{
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					if (typed)
+						deparse_ColumnDef_typed_toJsonb(state, relation, dpcontext,
+												(ColumnDef *) elt, belemsAdded);
+					else
+						deparse_ColumnDef_toJsonb(state, relation, dpcontext,
+												 composite, (ColumnDef *) elt,
+												 false, NULL, belemsAdded);
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+
+}
+
+/*
+ * Subroutine for CREATE TABLE deparsing.
+ *
+ * Deparse the INHERITS relations.
+ *
+ * Given a table OID, return a schema-qualified table list representing
+ * the parent tables.
+ */
+static List *
+deparse_InhRelationsToJsonb(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)))
+	{
+		Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple);
+
+		parents = lappend_oid(parents, formInh->inhparent);
+	}
+
+	systable_endscan(scan);
+	table_close(inhRel, RowExclusiveLock);
+
+	return parents;
+}
+
+/*
+ * Subroutine for CREATE TABLE deparsing.
+ *
+ * Given a table OID, obtain its constraints and append them to the given
+ * JsonbParseState.
+ *
+ * This works for typed tables, regular tables.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static void
+obtainConstraintsInJsonb(JsonbParseState *state, List *elements, Oid relationId,
+						 bool *belemsAdded)
+{
+	Relation	conRel;
+	ScanKeyData key;
+	SysScanDesc scan;
+	HeapTuple	tuple;
+
+	Assert(OidIsValid(relationId));
+
+	/*
+	 * 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, ConstraintRelidTypidNameIndexId, 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;
+		char	   *fmtStr = "CONSTRAINT %{name}I %{definition}s";
+
+		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_EXCLUSION:
+				contype = "exclusion";
+				break;
+			default:
+				elog(ERROR, "unrecognized constraint type");
+		}
+
+		if (!*belemsAdded)
+		{
+			*belemsAdded = true;
+			insert_jsonb_key(state, "table_elements");
+			pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+		}
+
+		/*
+		 * "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.
+		 */
+
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+		fmt_to_jsonb_element(state, fmtStr);
+
+		/* create name and type elements for constraint */
+		insert_jsonb_str_element(state, "type", "constraint");
+		insert_jsonb_str_element(state, "contype", contype);
+		insert_jsonb_str_element(state, "name", NameStr(constrForm->conname));
+		insert_jsonb_str_element(state, "definition",
+								 pg_get_constraintdef_string(constrForm->oid));
+
+		if (constrForm->conindid &&
+			(constrForm->contype == CONSTRAINT_PRIMARY ||
+			 constrForm->contype == CONSTRAINT_UNIQUE ||
+			 constrForm->contype == CONSTRAINT_EXCLUSION))
+		{
+			Oid			tblspc = get_rel_tablespace(constrForm->conindid);
+
+			if (OidIsValid(tblspc))
+			{
+				char	   *tblspcname = get_tablespace_name(tblspc);
+
+				if (!tblspcname)
+				{
+					elog(ERROR, "cache lookup failed for tablespace %u",
+						 tblspc);
+				}
+
+				fmt_to_jsonb_element(state, "USING INDEX TABLESPACE %{tblspc}s");
+				insert_jsonb_str_element(state, "tblspc", tblspcname);
+			}
+		}
+	}
+
+	systable_endscan(scan);
+	table_close(conRel, AccessShareLock);
+}
+
+/*
+ * Deparse a CreateStmt (CREATE TABLE).
+ *
+ * Given a table OID and the parse tree that created it, return JsonbValue
+ * 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
+ * TODO: with_clause
+ */
+static JsonbValue *
+parsetree_to_jsonb_create_table(JsonbParseState *state, Oid objectId, Node *parsetree, char *owner)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Oid			nspid = relation->rd_rel->relnamespace;
+	char	   *relname = RelationGetRelationName(relation);
+	List	   *dpcontext;
+	bool		belemsAdded = false;
+
+	StringInfoData fmtStr;
+
+	initStringInfo(&fmtStr);
+
+	dpcontext = deparse_context_for(RelationGetRelationName(relation),
+									objectId);
+
+	appendStringInfoString(&fmtStr, "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D");
+
+	/* mark the start of ROOT object */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	/* create owner jsonb element */
+	role_to_jsonb_element(state, owner);
+
+	/* PERSISTENCE clause creation */
+	insert_jsonb_str_element(state, "persistence", get_persistence_str(relation->rd_rel->relpersistence));
+
+	/* IF NOT EXISTS clause creation */
+	insert_jsonb_str_element(state, "if_not_exists", node->if_not_exists ? "IF NOT EXISTS" : "");
+
+
+	/* IDENTITY creation */
+	insertIdentityObject(state, nspid, relname);
+
+	/*
+	 * TABLE-ELEMENTS array creation
+	 *
+	 * 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)
+	{
+		/*
+		 * 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)
+		{
+			appendStringInfoString(&fmtStr, " OF %{type}T");
+
+			/* Push the key first */
+			insert_jsonb_key(state, "type");
+
+			/* Push the value */
+			new_jsonb_for_type(state, relation->rd_rel->reloftype, -1);
+		}
+		else
+		{
+			List	   *parents;
+			Oid			objid;
+
+			appendStringInfoString(&fmtStr, " PARTITION OF %{parent_identity}D");
+
+			parents = deparse_InhRelationsToJsonb(objectId);
+			objid = linitial_oid(parents);
+
+			Assert(list_length(parents) == 1);
+
+			new_jsonb_for_qualname_id(state, RelationRelationId,
+									  objid, "parent_identity");
+		}
+
+		deparse_TableElementsToJsonb(&state, relation, node->tableElts, dpcontext,
+									 true, /* typed table */
+									 false,	/* not composite */
+									 &belemsAdded);
+
+		obtainConstraintsInJsonb(state, node->tableElts, objectId, &belemsAdded);
+
+		if (belemsAdded)
+		{
+			appendStringInfoString(&fmtStr, " (%{table_elements:, }s)");
+			pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+		}
+	}
+	else
+	{
+		List	   *inhrelations;
+		belemsAdded = true;
+
+		/*
+		 * 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.
+		 */
+		appendStringInfoString(&fmtStr, " (%{table_elements:, }s)");
+
+		insert_jsonb_key(state, "table_elements");
+
+		/*
+		 * It will be of array type for multi-columns table, so lets begin an
+		 * arrayobject. deparse_TableElementsToJsonb() will add elements to
+		 * it.
+		 */
+		pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+		deparse_TableElementsToJsonb(&state, relation, node->tableElts, dpcontext,
+									 false, /* not typed table */
+									 false,	/* not composite */
+									 &belemsAdded);
+		obtainConstraintsInJsonb(state, node->tableElts, objectId, &belemsAdded);
+
+		pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+		/*
+		 * 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.
+		 */
+		if (node->inhRelations != NIL)
+		{
+			inhrelations = deparse_InhRelationsToJsonb(objectId);
+			appendStringInfoString(&fmtStr, " INHERITS (%{parents:, }D)");
+
+			insert_jsonb_array_oid(state, "parents", inhrelations);
+		}
+	}
+
+
+	/* FOR VALUES clause */
+	if (node->partbound)
+	{
+		appendStringInfoString(&fmtStr, " %{partition_bound}s");
+		insert_jsonb_key(state, "partition_bound");
+
+		/*
+		 * Get pg_class.relpartbound. We cannot use partbound in the parsetree
+		 * directly as it's the original partbound expression which haven't
+		 * been transformed.
+		 */
+		insert_jsonb_str_element(state, "partition_bound",
+								 RelationGetPartitionBound(objectId));
+	}
+
+	/* PARTITION BY clause object creation */
+	{
+		appendStringInfoString(&fmtStr, " %{partition_by}s");
+		insert_jsonb_key(state, "partition_by");
+
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			insert_str_object(state, "PARTITION BY %{definition}s", "definition",
+							  pg_get_partkeydef_string(objectId));
+		else
+			insert_not_present_obj(state, verbose ? "PARTITION BY %{definition}s" : "PARTITION BY");
+
+	}
+
+	/* USING clause object creation */
+	{
+		appendStringInfoString(&fmtStr, " %{access_method}s");
+		insert_jsonb_key(state, "access_method");
+
+		if (node->accessMethod)
+			insert_str_object(state, "USING %{access_method}I", "access_method",
+							  node->accessMethod);
+		else
+			insert_not_present_obj(state, verbose ? "USING %{access_method}I" : "USING");
+	}
+
+	/* TABLESPACE Object creation */
+	{
+		appendStringInfoString(&fmtStr, " %{tablespace}s");
+		insert_jsonb_key(state, "tablespace");
+
+		/* Push the value now: object in this case */
+		if (node->tablespacename)
+			insert_str_object(state, "TABLESPACE %{tablespace}I", "tablespace", node->tablespacename);
+		else
+			insert_not_present_obj(state, verbose ? "TABLESPACE %{tablespace}I" : "TABLESPACE");
+	}
+
+	relation_close(relation, AccessShareLock);
+
+	/* we have full fmt by now, so add jsonb element for that */
+	fmt_to_jsonb_element(state, fmtStr.data);
+
+	/* mark the end of ROOT object */
+	return pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
-- 
2.34.1