From 85ad5b3693f69ec06afa2f8cdd6147c9def8139b Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 27 Jun 2023 14:42:03 +0530
Subject: [PATCH 4/5] Auto generate CreateStmt and DropStmt node deparse
 function for its members.

There are three pg_node_attr value that used in the CREATE TABLE deparser code automation.

- pg_node_attr(deparse_as(str))

In in case, we can generate all the deparser code for this structure field. The
str in the deparse_as function is used as the fmt string if we create a object,
or as a json value. This attribute is used for: CreateStmt::tablespacename,
CreateStmt::accessMethod and CreateStmt::if_not_exists.

- pg_node_attr(deparse_as_special_value)

This means we cannot generate the code for the structure field, but we can use
this field to check if we need to deparse this field or not. And in
gen_node_support.pl, we handle such fields speically by generating different
deparsing codes for it (The usage is simialr to existing write_only_relids
attr). This is used for CreateStmt::partbound, CreateStmt::ofTypename.

The gen_node_support.pl codes that handles these special fields look like:

                if (elem 'deparse_as_special_value', @a)
                {
                        print $ddlff "\tif (node->$f)\n";
                        print $ddlff "\t{\n";

                        if ($f eq 'partbound')
                        {
                                # generate partboud deparser codes.
                        }
                        elsif ($f eq 'ofTypename')
                        {
                                # generate OF TYPE deparser codes.
                        }

                        print $ddlff "\t}\n\n";
                }

- pg_node_attr(deparse_as_special_object)

Similar to deparse_as_special_value, we cannot generate all the deparser codes
for the field. However, since we are creating a sub object here, we can
generate the common object creation codes for it, like:

auto        if (node->options)
auto        {
auto              insert_jsonb_key(state, "options");
auto              pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
special           new_jsonb_VA(state, 1, "fmt", jbvString, "WITH (%{with:, }s)");
special           deparse_withObj_ToJsonb(state, node);
auto              pushJsonbValue(&state, WJB_END_OBJECT, NULL);
auto        }

For the special part, we need to handle it differently in gen_node_support.pl.

Auto generate DropStmt node deparse function for its members.
DropStmt node has the following members:
objects -    No need to auto generate the code for objects member as event
             trigger will convert the objects list and generate individual
             object names for each of the members from the list.
removeType - No need to auto generated the code for removeType as event
             trigger will identify the object type for each of the
             object and pass it to parent deparse function.
behavior -   Implemented generation of json for this member.
missing_ok - Implemented generation of json for this member.
concurrent - Implemented generation of json for this member.
---
 src/backend/nodes/ddldeparsefuncs.c   | 174 +++++---------------
 src/backend/nodes/gen_node_support.pl | 226 +++++++++++++++++++++++++-
 src/include/nodes/nodes.h             |   2 +
 src/include/nodes/parsenodes.h        |  69 ++++++--
 4 files changed, 317 insertions(+), 154 deletions(-)

diff --git a/src/backend/nodes/ddldeparsefuncs.c b/src/backend/nodes/ddldeparsefuncs.c
index 9cd42fd7e3..bdded259c7 100644
--- a/src/backend/nodes/ddldeparsefuncs.c
+++ b/src/backend/nodes/ddldeparsefuncs.c
@@ -48,6 +48,8 @@
 
 extern bool		verbose;
 
+#include "ddldeparsefuncs.funcs.c"
+
 /*
  * Deparse a CreateStmt (CREATE TABLE).
  *
@@ -72,12 +74,14 @@ deparse_create_table(Oid objectId, Node *parsetree)
 	StringInfoData fmtStr;
 	JsonbParseState *state = NULL;
 	JsonbValue *value;
+	bool		telems_present = false;
 
 	initStringInfo(&fmtStr);
 
 	/* mark the begin of ROOT object and start adding elements to it. */
 	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
 
+	/* Start making fmt string */
 	appendStringInfoString(&fmtStr, "CREATE");
 
 	/* PERSISTENCE */
@@ -93,11 +97,7 @@ deparse_create_table(Oid objectId, Node *parsetree)
 
 	/* IF NOT EXISTS */
 	if (node->if_not_exists)
-	{
 		appendStringInfoString(&fmtStr, " %{if_not_exists}s");
-		new_jsonb_VA(state, 1,
-					 "if_not_exists", jbvString, "IF NOT EXISTS");
-	}
 
 	/* IDENTITY */
 	appendStringInfoString(&fmtStr, " %{identity}D");
@@ -114,100 +114,39 @@ deparse_create_table(Oid objectId, Node *parsetree)
 	 * 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)
-	{
-		/* Insert the "of type" or "partition of" clause whichever present */
-		if (node->ofTypename)
-		{
-			appendStringInfoString(&fmtStr, " OF %{of_type}T");
-			new_jsonb_for_type(state, "of_type",
-							   relation->rd_rel->reloftype, -1);
-		}
-		else
-		{
-			List	   *parents;
-			Oid			objid;
-
-			appendStringInfoString(&fmtStr, " PARTITION OF %{parent_identity}D");
-			parents = deparse_InhRels_ToJsonb(objectId);
-			objid = linitial_oid(parents);
-			Assert(list_length(parents) == 1);
-			new_jsonb_for_qualname_id(state, RelationRelationId,
-									  objid, "parent_identity", true);
-		}
-
-		/*
-		 * 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 first check the presence and then add
-		 * the elements.
-		 */
-		add_table_elems_if_any(state, &fmtStr, relation,
-							   node->tableElts, dpcontext, objectId,
-							   true,	/* typed table */
-							   false);	/* not composite */
-	}
-	else
-	{
-		List	   *inhrelations;
-		bool		telems_present = false;
-
-		/*
-		 * There is no need to process LIKE clauses separately; they have
-		 * already been transformed into columns and constraints.
-		 */
+	if (node->ofTypename)
+		appendStringInfoString(&fmtStr, " OF %{of_type}T");
 
-		/* Check if table elements are present, if so, add them. */
-		telems_present = add_table_elems_if_any(state, &fmtStr, relation,
-												node->tableElts, dpcontext, objectId,
-												false,	/* not typed table */
-												false); /* not composite */
-
-		/*
-		 * If no table elements added, then add empty "()" needed for
-		 * 'inherit' create table syntax. Example: CREATE TABLE t1 () INHERITS
-		 * (t0);
-		 */
-		if (!telems_present)
-			appendStringInfoString(&fmtStr, " ()");
-
-		/*
-		 * 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)
-		{
-			appendStringInfoString(&fmtStr, " %{inherits}s");
-			insert_jsonb_key(state, "inherits");
+	if (node->partbound)
+		appendStringInfoString(&fmtStr, " PARTITION OF %{parent_identity}D");
 
-			pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	telems_present = add_table_elems_if_any(state, &fmtStr, relation,
+											node->tableElts, dpcontext, objectId,
+											node->ofTypename || node->partbound,	/* typed table */
+											false);	/* not composite */
 
-			new_jsonb_VA(state, 1, "fmt", jbvString, "INHERITS (%{parents:, }D)");
-			inhrelations = deparse_InhRels_ToJsonb(objectId);
+	/*
+	 * If no table elements added, then add empty "()" needed for
+	 * 'inherit' create table syntax. Example: CREATE TABLE t1 () INHERITS
+	 * (t0);
+	 */
+	if (!node->ofTypename && !node->partbound && !telems_present)
+		appendStringInfoString(&fmtStr, " ()");
 
-			new_jsonbArray_for_qualname_id(state, "parents", inhrelations);
-			pushJsonbValue(&state, WJB_END_OBJECT, 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 && node->partbound == NULL)
+		appendStringInfoString(&fmtStr, " %{inhRelations}s");
 
 	/* FOR VALUES clause */
 	if (node->partbound)
-	{
 		appendStringInfoString(&fmtStr, " %{partition_bound}s");
 
-		/*
-		 * Get pg_class.relpartbound. We cannot use partbound in the parsetree
-		 * directly as it's the original partbound expression which haven't
-		 * been transformed.
-		 */
-		new_jsonb_VA(state, 1,
-					 "partition_bound", jbvString,
-					 RelationGetPartitionBound(objectId));
-	}
-
 	/* PARTITION BY clause */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
@@ -225,43 +164,17 @@ deparse_create_table(Oid objectId, Node *parsetree)
 
 	/* USING clause */
 	if (node->accessMethod)
-	{
-		appendStringInfoString(&fmtStr, " %{access_method}s");
-		insert_jsonb_key(state, "access_method");
-		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
-
-		new_jsonb_VA(state, 2,
-					 "fmt", jbvString, "USING %{access_method}I",
-					 "access_method", jbvString, node->accessMethod);
-		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
-	}
+		appendStringInfoString(&fmtStr, " %{accessMethod}s");
 
 	/* WITH clause */
 	if (node->options)
-	{
-		appendStringInfoString(&fmtStr, " %{with_clause}s");
-		insert_jsonb_key(state, "with_clause");
-		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
-		new_jsonb_VA(state, 1,
-					 "fmt", jbvString, "WITH (%{with:, }s)");
-
-		deparse_withObj_ToJsonb(state, node);
-
-		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
-
-	}
+		appendStringInfoString(&fmtStr, " %{options}s");
 
 	/* TABLESPACE */
 	if (node->tablespacename)
-	{
-		appendStringInfoString(&fmtStr, " %{tablespace}s");
-		insert_jsonb_key(state, "tablespace");
-		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
-		new_jsonb_VA(state, 2,
-					 "fmt", jbvString, "TABLESPACE %{tablespace}I",
-					 "tablespace", jbvString, node->tablespacename);
-		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
-	}
+		appendStringInfoString(&fmtStr, " %{tablespacename}s");
+
+	deparse_CreateStmt(state, objectId, relation, parsetree);
 
 	relation_close(relation, AccessShareLock);
 
@@ -283,7 +196,7 @@ deparse_create_table(Oid objectId, Node *parsetree)
  * jsonb string representing the drop command.
  *
  * Verbose syntax
- * DROP %{objtype}s %{concurrently}s %{if_exists}s %{objidentity}s %{cascade}s
+ * DROP %{objtype}s %{concurrent}s %{missing_ok}s %{objidentity}s %{behavior}s
  */
 char *
 deparse_drop_table(const char *objidentity, const char *objecttype,
@@ -310,18 +223,11 @@ deparse_drop_table(const char *objidentity, const char *objecttype,
 
 	/* CONCURRENTLY */
 	if (node->concurrent)
-	{
-		appendStringInfoString(&fmtStr, " %{concurrently}s");
-		new_jsonb_VA(state, 1,
-					 "concurrently", jbvString, "CONCURRENTLY");
-	}
+		appendStringInfoString(&fmtStr, " %{concurrent}s");
 
 	/* IF EXISTS */
 	if (node->missing_ok)
-	{
-		appendStringInfoString(&fmtStr, " %{if_exists}s");
-		new_jsonb_VA(state, 1, "if_exists", jbvString, "IF EXISTS");
-	}
+		appendStringInfoString(&fmtStr, " %{missing_ok}s");
 
 	/* IDENTITY */
 	appendStringInfoString(&fmtStr, " %{objidentity}s");
@@ -329,10 +235,10 @@ deparse_drop_table(const char *objidentity, const char *objecttype,
 
 	/* CASCADE */
 	if (node->behavior == DROP_CASCADE)
-	{
-		appendStringInfoString(&fmtStr, " %{cascade}s");
-		new_jsonb_VA(state, 1, "cascade", jbvString, "CASCADE");
-	}
+		appendStringInfoString(&fmtStr, " %{behavior}s");
+
+	/* Add jsonb values for auto generated fields */
+	deparse_DropStmt(state, InvalidOid, NULL, parsetree);
 
 	/* We have full fmt by now, so add jsonb element for that */
 	new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 72c7963578..04757b79fb 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -123,6 +123,8 @@ my @no_copy;
 my @no_equal;
 # node types we don't want query jumble support for
 my @no_query_jumble;
+# node types we don't want ddl deparse support for
+my @no_ddl_deparse;
 # node types we don't want read support for
 my @no_read;
 # node types we don't want read/write support for
@@ -164,6 +166,7 @@ push @node_types, qw(List);
 push @no_copy, qw(List);
 push @no_equal, qw(List);
 push @no_query_jumble, qw(List);
+push @no_ddl_deparse, qw(List);
 push @special_read_write, qw(List);
 
 # Nodes with custom copy/equal implementations are skipped from
@@ -176,6 +179,9 @@ my @custom_read_write;
 # Similarly for custom query jumble implementation.
 my @custom_query_jumble;
 
+# Similarly for custom ddl deparse implementation.
+my @custom_ddl_deparse;
+
 # Track node types with manually assigned NodeTag numbers.
 my %manual_nodetag_number;
 
@@ -329,6 +335,10 @@ foreach my $infile (@ARGV)
 						{
 							push @custom_query_jumble, $in_struct;
 						}
+						elsif ($attr eq 'custom_ddl_deparse')
+						{
+							push @custom_ddl_deparse, $in_struct;
+						}
 						elsif ($attr eq 'no_copy')
 						{
 							push @no_copy, $in_struct;
@@ -436,7 +446,7 @@ foreach my $infile (@ARGV)
 			}
 			# normal struct field
 			elsif ($line =~
-				/^\s*(.+)\s*\b(\w+)(\[[\w\s+]+\])?\s*(?:pg_node_attr\(([\w(), ]*)\))?;/
+				/^\s*(.+)\s*\b(\w+)(\[[\w\s+]+\])?\s*(?:pg_node_attr\(([\w(%{}\w:), ]*)\))?;/
 			  )
 			{
 				if ($is_node_struct)
@@ -468,6 +478,8 @@ foreach my $infile (@ARGV)
 							if (   $attr !~ /^array_size\(\w+\)$/
 								&& $attr !~ /^copy_as\(\w+\)$/
 								&& $attr !~ /^read_as\(\w+\)$/
+								&& $attr !~ /^deparse_as\([%\w\s{} ]+\)$/
+								&& $attr !~ /^deparse_as\(([%\w:{} ]*)\)$/
 								&& !elem $attr,
 								qw(copy_as_scalar
 								equal_as_scalar
@@ -476,6 +488,9 @@ foreach my $infile (@ARGV)
 								query_jumble_ignore
 								query_jumble_location
 								read_write_ignore
+								deparse_ignore
+								deparse_as_special_value
+								deparse_as_special_object
 								write_only_relids
 								write_only_nondefault_pathtarget
 								write_only_req_outer))
@@ -494,7 +509,7 @@ foreach my $infile (@ARGV)
 			}
 			# function pointer field
 			elsif ($line =~
-				/^\s*([\w\s*]+)\s*\(\*(\w+)\)\s*\((.*)\)\s*(?:pg_node_attr\(([\w(), ]*)\))?;/
+				/^\s*([\w\s*]+)\s*\(\*(\w+)\)\s*\((.*)\)\s*(?:pg_node_attr\(([\w(), %]*)\))?;/
 			  )
 			{
 				if ($is_node_struct)
@@ -1339,6 +1354,213 @@ _jumble${n}(JumbleState *jstate, Node *node)
 close $jff;
 close $jfs;
 
+# ddldeparsefuncs.c
+
+push @output_files, 'ddldeparsefuncs.funcs.c';
+open my $ddlff, '>', "$output_path/ddldeparsefuncs.funcs.c$tmpext" or die $!;
+push @output_files, 'ddldeparsefuncs.switch.c';
+open my $ddlfs, '>', "$output_path/ddldeparsefuncs.switch.c$tmpext" or die $!;
+
+printf $ddlff $header_comment, 'ddldeparsefuncs.funcs.c';
+printf $ddlfs $header_comment, 'ddldeparsefuncs.switch.c';
+
+print $ddlff $node_includes;
+
+my @node_ddl_deparse;
+push @node_ddl_deparse, "DropStmt";
+push @node_ddl_deparse, "CreateStmt";
+
+foreach my $n (@node_ddl_deparse)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @nodetag_only;
+
+	my $struct_no_ddl_deparse = (elem $n, @no_ddl_deparse);
+
+	if ($n eq 'DropStmt')
+	{
+		#print $ddlfs "\t\t\tcase T_${n}:\n"
+		#	. "\t\t\t\tcontext->include_owner = false;\n"
+		#	. "\t\t\t\tjsonb = deparse_${n}(state, parsetree);\n"
+		#	. "\t\t\t\tbreak;\n"
+		#unless $struct_no_ddl_deparse;
+	}
+	else
+	{
+		print $ddlfs "\t\t\tcase T_${n}:\n"
+			. "\t\t\t\tjsonb = deparse_${n}(cmd, context);\n"
+			. "\t\t\t\tbreak;\n"
+		unless $struct_no_ddl_deparse;
+	}
+
+	next if elem $n, @custom_ddl_deparse;
+
+	print $ddlff "
+static void
+deparse_${n}(JsonbParseState *state, Oid objectId, Relation relation, Node *parsetree)
+{
+\t${n} *node = (${n} *) parsetree;
+
+" unless $struct_no_ddl_deparse;
+
+	# print instructions for each field
+	foreach my $f (@{ $node_type_info{$n}->{fields} })
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
+		my $ddl_deparse_ignore = $struct_no_ddl_deparse;
+
+		# extract per-field attributes
+		my $array_size_field;
+		my $read_as_field1;
+		my $read_as_field2;
+		my $deparse_ignore = 0;
+		foreach my $a (@a)
+		{
+			if ($a =~ /^deparse_as\(([\w\s.%{}]+)\)$/)
+			{
+				$read_as_field1 = $1;
+			}
+			if ($a =~ /^deparse_as\((([\w\s]+):([\w\s]+))\)$/)
+			{
+				$read_as_field1 = $2;
+				$read_as_field2 = $3;
+				print "field1 = $2\n";
+				print "field2 = $3\n";
+			}
+			elsif ($a eq 'deparse_ignore')
+			{
+				$ddl_deparse_ignore = 1;
+			}
+		}
+
+		if ($ddl_deparse_ignore)
+		{
+			# nothing to do if no_read
+			next if $struct_no_ddl_deparse;
+		}
+
+		if (elem 'deparse_as_special_value', @a)
+		{
+			print $ddlff "\tif (node->$f)\n";
+			print $ddlff "\t{\n";
+
+			if ($f eq 'partbound')
+			{
+				print $ddlff "\t\tList *parents = deparse_InhRels_ToJsonb(objectId);\n"
+					. "\t\tOid objid = linitial_oid(parents);\n"
+					. "\t\tAssert(list_length(parents) == 1);\n"
+					. "\t\tnew_jsonb_for_qualname_id(state, RelationRelationId,\n"
+					. "\t\t\t\t\t\t\t\t  objid, \"parent_identity\", true);\n\n"
+					. "\t\tnew_jsonb_VA(state, 1,"
+					. "\t\t\t\t\t \"partition_bound\", jbvString,\n"
+					. "\t\t\t\t\t RelationGetPartitionBound(objectId));\n";
+			}
+			elsif ($f eq 'ofTypename')
+			{
+				print $ddlff "\t\tnew_jsonb_for_type(state, \"of_type\", relation->rd_rel->reloftype, -1);\n";
+			}
+
+			print $ddlff "\t}\n\n";
+
+			next;
+		}
+		elsif (elem 'deparse_as_special_object', @a)
+		{
+			if ($f eq 'inhRelations')
+			{
+				print $ddlff "\tif (node->$f && !node->partbound)\n";
+			}
+			else
+			{
+				print $ddlff "\tif (node->$f)\n";
+			}
+
+			print $ddlff "\t{\n";
+			print $ddlff "\t\tinsert_jsonb_key(state, \"$f\");\n"
+			  . "\t\tpushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);\n";
+
+			if ($f eq 'inhRelations')
+			{
+				print $ddlff "\t\tnew_jsonb_VA(state, 1, \"fmt\", jbvString, \"INHERITS (%{parents:, }D)\");\n"
+					. "\t\tnew_jsonbArray_for_qualname_id(state, \"parents\", deparse_InhRels_ToJsonb(objectId));\n";
+			}
+			elsif ($f eq 'options')
+			{
+				print $ddlff "\t\tnew_jsonb_VA(state, 1, \"fmt\", jbvString, \"WITH (%{with:, }s)\");\n"
+					. "\t\tdeparse_withObj_ToJsonb(state, node);\n";
+			}
+
+			print $ddlff "\t\tpushJsonbValue(&state, WJB_END_OBJECT, NULL);\n"
+				. "\t}\n\n";
+			next;
+		}
+
+		# node type
+		if (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+			and elem $1, @node_types)
+		{
+			print $ddlff "\tDEPARSE_NODE($f);\n"
+			  unless $ddl_deparse_ignore;
+		}
+		elsif ($t eq 'char*')
+		{
+			if (defined $read_as_field1 && !$ddl_deparse_ignore)
+			{
+				print $ddlff "\tif (node->$f)\n";
+
+				# if %{} add "fmt" and field name-value
+				print $ddlff "\t{\n";
+				if ($read_as_field1 =~ /\s*%\s*/)
+				{
+					print $ddlff "\t\tinsert_jsonb_key(state, \"$f\");\n"
+					  . "\t\tpushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);\n"
+					  . "\t\tnew_jsonb_VA(state, 2,\n"
+					  . "\t\t\t\t\t \"fmt\", jbvString, \"$read_as_field1\",\n"
+					  . "\t\t\t\t\t \"$f\", jbvString, node->$f);\n"
+					  . "\t\tpushJsonbValue(&state, WJB_END_OBJECT, NULL);\n";
+				}
+				else
+				{
+					print $ddlff "\t\tnew_jsonb_VA\(state, 1, \"$f\", jbvString, node->$f ? \"$read_as_field1\" : \"\"\);\n";
+				}
+				print $ddlff "\t}\n\n";
+			}
+		}
+		elsif ($t eq 'bool')
+		{
+			if (defined $read_as_field1)
+			{
+				print $ddlff "\tif (node->$f)\n"
+					unless $ddl_deparse_ignore;
+				print $ddlff "\t\tnew_jsonb_VA\(state, 1, \"$f\", jbvString, \"$read_as_field1\"\);\n\n"
+					unless $ddl_deparse_ignore;
+			}
+		}
+		elsif (elem $t, @enum_types)
+		{
+			print $ddlff "\tif (node->behavior == $read_as_field1)\n"
+				unless $ddl_deparse_ignore;
+			print $ddlff "\t\tnew_jsonb_VA\(state, 1, \"$f\", jbvString, \"$read_as_field2\");\n\n"
+				unless $ddl_deparse_ignore;
+		}
+	}
+
+	# Some nodes have no attributes like CheckPointStmt,
+	# so tweak things for empty contents.
+	if (scalar(@{ $node_type_info{$n}->{fields} }) == 0)
+	{
+		print $ddlff "\t(void) expr;\n"
+		  unless $struct_no_ddl_deparse;
+	}
+
+	print $ddlff "}
+" unless $struct_no_ddl_deparse;
+}
+
+close $ddlff;
+close $ddlfs;
+
 # now rename the temporary files to their final names
 foreach my $file (@output_files)
 {
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f8e8fe699a..d049ce91ff 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -55,6 +55,8 @@ typedef enum NodeTag
  *
  * - custom_query_jumble: Has custom implementation in queryjumblefuncs.c.
  *
+ * - custom_ddl_deparse: Has custom implementation in ddldeparsefuncs.c.
+ *
  * - no_copy: Does not support copyObject() at all.
  *
  * - no_equal: Does not support equal() at all.
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b8ab8dae6c..86532a3f02 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2472,19 +2472,42 @@ typedef struct VariableShowStmt
 typedef struct CreateStmt
 {
 	NodeTag		type;
-	RangeVar   *relation;		/* relation to create */
-	List	   *tableElts;		/* column definitions (list of ColumnDef) */
-	List	   *inhRelations;	/* relations to inherit from (list of
-								 * RangeVar) */
-	PartitionBoundSpec *partbound;	/* FOR VALUES clause */
-	PartitionSpec *partspec;	/* PARTITION BY clause */
-	TypeName   *ofTypename;		/* OF typename */
-	List	   *constraints;	/* constraints (list of Constraint nodes) */
-	List	   *options;		/* options from WITH clause */
-	OnCommitAction oncommit;	/* what do we do at COMMIT? */
-	char	   *tablespacename; /* table space to use, or NULL */
-	char	   *accessMethod;	/* table access method */
-	bool		if_not_exists;	/* just do nothing if it already exists? */
+
+	/* relation to create */
+	RangeVar   *relation		pg_node_attr(deparse_ignore);
+
+	/* column definitions (list of ColumnDef) */
+	List	   *tableElts		pg_node_attr(deparse_ignore);
+
+	/* relations to inherit from (list of RangeVar) */
+	List	   *inhRelations	pg_node_attr(deparse_as_special_object);
+
+	/* FOR VALUES clause */
+	PartitionBoundSpec *partbound	pg_node_attr(deparse_as_special_value);
+
+	/* PARTITION BY clause */
+	PartitionSpec *partspec		pg_node_attr(deparse_ignore);
+
+	/* OF typename */
+	TypeName   *ofTypename		pg_node_attr(deparse_as_special_value);
+
+	/* constraints (list of Constraint nodes) */
+	List	   *constraints		pg_node_attr(deparse_ignore);
+
+	/* options from WITH clause */
+	List	   *options			pg_node_attr(deparse_as_special_object);
+
+	/* what do we do at COMMIT? */
+	OnCommitAction oncommit		pg_node_attr(deparse_ignore);
+
+	/* table space to use, or NULL */
+	char	   *tablespacename	pg_node_attr(deparse_as(TABLESPACE %{tablespacename}I));
+
+	/* table access method */
+	char	   *accessMethod	pg_node_attr(deparse_as(USING %{accessMethod}I));
+
+	/* just do nothing if it already exists? */
+	bool		if_not_exists	pg_node_attr(deparse_as(IF NOT EXISTS));
 } CreateStmt;
 
 /* ----------
@@ -3059,11 +3082,21 @@ typedef struct AlterOpFamilyStmt
 typedef struct DropStmt
 {
 	NodeTag		type;
-	List	   *objects;		/* list of names */
-	ObjectType	removeType;		/* object type */
-	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
-	bool		missing_ok;		/* skip error if object is missing? */
-	bool		concurrent;		/* drop index concurrently? */
+
+	/* list of names */
+	List	   *objects pg_node_attr(deparse_ignore);
+
+	/* object type */
+	ObjectType	removeType	pg_node_attr(deparse_ignore);
+
+	/* RESTRICT or CASCADE behavior */
+	DropBehavior behavior pg_node_attr(deparse_as(DROP_CASCADE:CASCADE));
+
+	/* skip error if object is missing? */
+	bool		missing_ok pg_node_attr(deparse_as(IF EXISTS));
+
+	/* drop index concurrently? */
+	bool		concurrent pg_node_attr(deparse_as(CONCURRENTLY));
 } DropStmt;
 
 /* ----------------------
-- 
2.34.1

