Peter Eisentraut <peter.eisentr...@enterprisedb.com> writes:
> On 18.07.22 18:08, Tom Lane wrote:
>> I'm kind of tempted to mount an effort to get rid of as many of
>> pathnodes.h's "read_write_ignore" annotations as possible.  Some are
>> necessary to prevent infinite recursion, and others represent considered
>> judgments that they'd bloat node dumps more than they're worth --- but
>> I think quite a lot of them arose from plain laziness about updating
>> outfuncs.c.  With the infrastructure we have now, that's no longer
>> a good reason.

> That was my impression as well, and I agree it would be good to sort 
> that out.

I had a go at doing this, and ended up with something that seems
reasonable for now (attached).  The thing that'd have to be done to
make additional progress is to convert a lot of partitioning-related
structs into full Nodes.  That seems like it might possibly be
worth doing, but I don't feel like doing it.  I doubt that making
planner node dumps smarter is a sufficient excuse for that anyway.
(But possibly if we then larded related code with castNode() and
sibling macros, there'd be enough of a gain in type-safety to
justify it?)

I learned a couple of interesting things along the way:

* I'd thought we already had outfuncs support for writing an array
of node pointers.  We don't, but it's easily added.  I chose to
write the array with parenthesis decoration, mainly because that
eases moving around it in emacs.

* WRITE_OID_ARRAY and WRITE_BOOL_ARRAY needed extension to handle a null
array pointer.  I think we should make all the WRITE_FOO_ARRAY macros
work alike, so I added that to all of them.  I first tried to make the
rest work like WRITE_INDEX_ARRAY, but that failed because readfuncs.c
isn't expecting "<>" for an empty array; it's expecting nothing at
all.  (Note there is no readfuncs equivalent to WRITE_INDEX_ARRAY.)
What I've done here is to change WRITE_INDEX_ARRAY to work like the
others and print nothing for an empty array, but I wonder if now
wouldn't be a good time to redefine the serialized representation
to be more robust.  I'm imagining "<>" for a NULL array pointer and
"(item item item)" otherwise, allowing a cross-check that we're
getting the right number of items.

* gen_node_support.pl was being insufficiently careful about parsing
type names, so I tightened its regexes a bit.

                        regards, tom lane

diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index f3309c3000..bee48696c7 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -441,6 +441,8 @@ foreach my $infile (@ARGV)
 					$type =~ s/\s*$//;
 					# strip space between type and "*" (pointer) */
 					$type =~ s/\s+\*$/*/;
+					# strip space between type and "**" (array of pointers) */
+					$type =~ s/\s+\*\*$/**/;
 
 					die
 					  "$infile:$lineno: cannot parse data type in \"$line\"\n"
@@ -745,8 +747,8 @@ _equal${n}(const $n *a, const $n *b)
 				  unless $equal_ignore || $t eq 'CoercionForm';
 			}
 		}
-		# scalar type pointer
-		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
+		# arrays of scalar types
+		elsif ($t =~ /^(\w+)\*$/ and elem $1, @scalar_types)
 		{
 			my $tt = $1;
 			if (!defined $array_size_field)
@@ -780,13 +782,14 @@ _equal${n}(const $n *a, const $n *b)
 			print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
 		}
 		# node type
-		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		elsif (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+			and elem $1, @node_types)
 		{
 			print $cff "\tCOPY_NODE_FIELD($f);\n"    unless $copy_ignore;
 			print $eff "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
 		}
 		# array (inline)
-		elsif ($t =~ /\w+\[/)
+		elsif ($t =~ /^\w+\[\w+\]$/)
 		{
 			print $cff "\tCOPY_ARRAY_FIELD($f);\n"    unless $copy_ignore;
 			print $eff "\tCOMPARE_ARRAY_FIELD($f);\n" unless $equal_ignore;
@@ -894,11 +897,16 @@ _read${n}(void)
 		my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
 
 		# extract per-field attributes
-		my $read_write_ignore = 0;
+		my $array_size_field;
 		my $read_as_field;
+		my $read_write_ignore = 0;
 		foreach my $a (@a)
 		{
-			if ($a =~ /^read_as\(([\w.]+)\)$/)
+			if ($a =~ /^array_size\(([\w.]+)\)$/)
+			{
+				$array_size_field = $1;
+			}
+			elsif ($a =~ /^read_as\(([\w.]+)\)$/)
 			{
 				$read_as_field = $1;
 			}
@@ -1015,19 +1023,10 @@ _read${n}(void)
 			print $off "\tWRITE_ENUM_FIELD($f, $t);\n";
 			print $rff "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read;
 		}
-		# arrays
-		elsif ($t =~ /(\w+)(\*|\[)/ and elem $1, @scalar_types)
+		# arrays of scalar types
+		elsif ($t =~ /^(\w+)(\*|\[\w+\])$/ and elem $1, @scalar_types)
 		{
 			my $tt = uc $1;
-			my $array_size_field;
-			foreach my $a (@a)
-			{
-				if ($a =~ /^array_size\(([\w.]+)\)$/)
-				{
-					$array_size_field = $1;
-					last;
-				}
-			}
 			if (!defined $array_size_field)
 			{
 				die "no array size defined for $n.$f of type $t\n";
@@ -1080,11 +1079,38 @@ _read${n}(void)
 			  . "\t\toutBitmapset(str, NULL);\n";
 		}
 		# node type
-		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		elsif (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+			and elem $1, @node_types)
 		{
 			print $off "\tWRITE_NODE_FIELD($f);\n";
 			print $rff "\tREAD_NODE_FIELD($f);\n" unless $no_read;
 		}
+		# arrays of node pointers (currently supported for write only)
+		elsif (($t =~ /^(\w+)\*\*$/ or $t =~ /^struct\s+(\w+)\*\*$/)
+		       and elem $1, @node_types)
+		{
+			if (!defined $array_size_field)
+			{
+				die "no array size defined for $n.$f of type $t\n";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq
+				'List*')
+			{
+				print $off
+				  "\tWRITE_NODE_ARRAY($f, list_length(node->$array_size_field));\n";
+				print $rff
+				  "\tREAD_NODE_ARRAY($f, list_length(local_node->$array_size_field));\n"
+				  unless $no_read;
+			}
+			else
+			{
+				print $off
+				  "\tWRITE_NODE_ARRAY($f, node->$array_size_field);\n";
+				print $rff
+				  "\tREAD_NODE_ARRAY($f, local_node->$array_size_field);\n"
+				  unless $no_read;
+			}
+		}
 		elsif ($t eq 'struct CustomPathMethods*'
 			|| $t eq 'struct CustomScanMethods*')
 		{
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2b85f97f39..01d70a75e4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -96,49 +96,63 @@ static void outChar(StringInfo str, char c);
 	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
 	 outBitmapset(str, node->fldname))
 
+#define WRITE_NODE_ARRAY(fldname, len) \
+	do { \
+		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
+		if (node->fldname) \
+		{ \
+			appendStringInfoChar(str, '('); \
+			for (int i = 0; i < len; i++) \
+			{ \
+				appendStringInfoChar(str, ' '); \
+				outNode(str, node->fldname[i]); \
+			} \
+			appendStringInfoChar(str, ')'); \
+		} \
+		else \
+			appendStringInfoString(str, "<>"); \
+	} while(0)
+
 #define WRITE_ATTRNUMBER_ARRAY(fldname, len) \
 	do { \
 		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
-		for (int i = 0; i < len; i++) \
-			appendStringInfo(str, " %d", node->fldname[i]); \
+		if (node->fldname) \
+			for (int i = 0; i < len; i++) \
+				appendStringInfo(str, " %d", node->fldname[i]); \
 	} while(0)
 
 #define WRITE_OID_ARRAY(fldname, len) \
 	do { \
 		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
-		for (int i = 0; i < len; i++) \
-			appendStringInfo(str, " %u", node->fldname[i]); \
+		if (node->fldname) \
+			for (int i = 0; i < len; i++) \
+				appendStringInfo(str, " %u", node->fldname[i]); \
 	} while(0)
 
-/*
- * This macro supports the case that the field is NULL.  For the other array
- * macros, that is currently not needed.
- */
 #define WRITE_INDEX_ARRAY(fldname, len) \
 	do { \
 		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
 		if (node->fldname) \
 			for (int i = 0; i < len; i++) \
 				appendStringInfo(str, " %u", node->fldname[i]); \
-		else \
-			appendStringInfoString(str, "<>"); \
 	} while(0)
 
 #define WRITE_INT_ARRAY(fldname, len) \
 	do { \
 		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
-		for (int i = 0; i < len; i++) \
-			appendStringInfo(str, " %d", node->fldname[i]); \
+		if (node->fldname) \
+			for (int i = 0; i < len; i++) \
+				appendStringInfo(str, " %d", node->fldname[i]); \
 	} while(0)
 
 #define WRITE_BOOL_ARRAY(fldname, len) \
 	do { \
 		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
-		for (int i = 0; i < len; i++) \
-			appendStringInfo(str, " %s", booltostr(node->fldname[i])); \
+		if (node->fldname) \
+			for (int i = 0; i < len; i++) \
+				appendStringInfo(str, " %s", booltostr(node->fldname[i])); \
 	} while(0)
 
-
 #define booltostr(x)  ((x) ? "true" : "false")
 
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index e650af5ff2..1f95e46a58 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -89,7 +89,7 @@ typedef enum UpperRelationKind
  * planned.
  *
  * Not all fields are printed.  (In some cases, there is no print support for
- * the field type.)
+ * the field type; in others, doing so would lead to infinite recursion.)
  *----------
  */
 typedef struct PlannerGlobal
@@ -177,7 +177,8 @@ typedef struct PlannerGlobal
  * either here or in that header, whichever is read first.
  *
  * Not all fields are printed.  (In some cases, there is no print support for
- * the field type.)
+ * the field type; in others, doing so would lead to infinite recursion or
+ * bloat dump output more than seems useful.)
  *----------
  */
 #ifndef HAVE_PLANNERINFO_TYPEDEF
@@ -220,14 +221,15 @@ struct PlannerInfo
 	 * does not correspond to a base relation, such as a join RTE or an
 	 * unreferenced view RTE; or if the RelOptInfo hasn't been made yet.
 	 */
-	struct RelOptInfo **simple_rel_array pg_node_attr(read_write_ignore);
+	struct RelOptInfo **simple_rel_array pg_node_attr(array_size(simple_rel_array_size));
 	/* allocated size of array */
-	int			simple_rel_array_size pg_node_attr(read_write_ignore);
+	int			simple_rel_array_size;
 
 	/*
 	 * simple_rte_array is the same length as simple_rel_array and holds
 	 * pointers to the associated rangetable entries.  Using this is a shade
-	 * faster than using rt_fetch(), mostly due to fewer indirections.
+	 * faster than using rt_fetch(), mostly due to fewer indirections.  (Not
+	 * printed because it'd be redundant with parse->rtable.)
 	 */
 	RangeTblEntry **simple_rte_array pg_node_attr(read_write_ignore);
 
@@ -237,7 +239,7 @@ struct PlannerInfo
 	 * child_relid, or NULL if the rel is not an appendrel child.  The array
 	 * itself is not allocated if append_rel_list is empty.
 	 */
-	struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore);
+	struct AppendRelInfo **append_rel_array pg_node_attr(array_size(simple_rel_array_size));
 
 	/*
 	 * all_baserels is a Relids set of all base relids (but not "other"
@@ -274,7 +276,7 @@ struct PlannerInfo
 	 * automatically added to the join_rel_level[join_cur_level] list.
 	 * join_rel_level is NULL if not in use.
 	 */
-	/* lists of join-relation RelOptInfos */
+	/* lists of join-relation RelOptInfos (too bulky to print) */
 	List	  **join_rel_level pg_node_attr(read_write_ignore);
 	/* index of list being extended */
 	int			join_cur_level;
@@ -403,8 +405,8 @@ struct PlannerInfo
 	/*
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
-	/* for GroupingFunc fixup */
-	AttrNumber *grouping_map pg_node_attr(array_size(update_colnos), read_write_ignore);
+	/* for GroupingFunc fixup (can't print: array length not known here) */
+	AttrNumber *grouping_map pg_node_attr(read_write_ignore);
 	/* List of MinMaxAggInfos */
 	List	   *minmax_aggs;
 
@@ -458,7 +460,7 @@ struct PlannerInfo
 	/* PARAM_EXEC ID for the work table */
 	int			wt_param_id;
 	/* a path for non-recursive term */
-	struct Path *non_recursive_path pg_node_attr(read_write_ignore);
+	struct Path *non_recursive_path;
 
 	/*
 	 * These fields are workspace for createplan.c
@@ -470,7 +472,9 @@ struct PlannerInfo
 
 	/*
 	 * These fields are workspace for setrefs.c.  Each is an array
-	 * corresponding to glob->subplans.
+	 * corresponding to glob->subplans.  (We could probably teach
+	 * gen_node_support.pl how to determine the array length, but it doesn't
+	 * seem worth the trouble, so just mark them read_write_ignore.)
 	 */
 	bool	   *isAltSubplan pg_node_attr(read_write_ignore);
 	bool	   *isUsedSubplan pg_node_attr(read_write_ignore);
@@ -928,16 +932,17 @@ typedef struct RelOptInfo
 	 * Number of partitions; -1 if not yet set; in case of a join relation 0
 	 * means it's considered unpartitioned
 	 */
-	int			nparts pg_node_attr(read_write_ignore);
+	int			nparts;
 	/* Partition bounds */
 	struct PartitionBoundInfoData *boundinfo pg_node_attr(read_write_ignore);
 	/* True if partition bounds were created by partition_bounds_merge() */
 	bool		partbounds_merged;
 	/* Partition constraint, if not the root */
-	List	   *partition_qual pg_node_attr(read_write_ignore);
+	List	   *partition_qual;
 
 	/*
 	 * Array of RelOptInfos of partitions, stored in the same order as bounds
+	 * (don't print, too bulky)
 	 */
 	struct RelOptInfo **part_rels pg_node_attr(read_write_ignore);
 
@@ -948,6 +953,12 @@ typedef struct RelOptInfo
 	Bitmapset  *live_parts;
 	/* Relids set of all partition relids */
 	Relids		all_partrels;
+
+	/*
+	 * These arrays are of length partkey->partnatts, which we don't have at
+	 * hand, so don't try to print
+	 */
+
 	/* Non-nullable partition key expressions */
 	List	  **partexprs pg_node_attr(read_write_ignore);
 	/* Nullable partition key expressions */
@@ -1042,30 +1053,26 @@ struct IndexOptInfo
 	int			nkeycolumns;
 
 	/*
-	 * array fields aren't really worth the trouble to print
+	 * table column numbers of index's columns (both key and included
+	 * columns), or 0 for expression columns
 	 */
-
-	/*
-	 * column numbers of index's attributes both key and included columns, or
-	 * 0
-	 */
-	int		   *indexkeys pg_node_attr(read_write_ignore);
+	int		   *indexkeys pg_node_attr(array_size(ncolumns));
 	/* OIDs of collations of index columns */
-	Oid		   *indexcollations pg_node_attr(read_write_ignore);
+	Oid		   *indexcollations pg_node_attr(array_size(nkeycolumns));
 	/* OIDs of operator families for columns */
-	Oid		   *opfamily pg_node_attr(read_write_ignore);
+	Oid		   *opfamily pg_node_attr(array_size(nkeycolumns));
 	/* OIDs of opclass declared input data types */
-	Oid		   *opcintype pg_node_attr(read_write_ignore);
+	Oid		   *opcintype pg_node_attr(array_size(nkeycolumns));
 	/* OIDs of btree opfamilies, if orderable */
-	Oid		   *sortopfamily pg_node_attr(read_write_ignore);
+	Oid		   *sortopfamily pg_node_attr(array_size(nkeycolumns));
 	/* is sort order descending? */
-	bool	   *reverse_sort pg_node_attr(read_write_ignore);
+	bool	   *reverse_sort pg_node_attr(array_size(nkeycolumns));
 	/* do NULLs come first in the sort order? */
-	bool	   *nulls_first pg_node_attr(read_write_ignore);
+	bool	   *nulls_first pg_node_attr(array_size(nkeycolumns));
 	/* opclass-specific options for columns */
 	bytea	  **opclassoptions pg_node_attr(read_write_ignore);
 	/* which index cols can be returned in an index-only scan? */
-	bool	   *canreturn pg_node_attr(read_write_ignore);
+	bool	   *canreturn pg_node_attr(array_size(ncolumns));
 	/* OID of the access method (in pg_am) */
 	Oid			relam;
 
@@ -1098,19 +1105,19 @@ struct IndexOptInfo
 
 	/*
 	 * Remaining fields are copied from the index AM's API struct
-	 * (IndexAmRoutine).  We don't bother to dump them.
+	 * (IndexAmRoutine).
 	 */
-	bool		amcanorderbyop pg_node_attr(read_write_ignore);
-	bool		amoptionalkey pg_node_attr(read_write_ignore);
-	bool		amsearcharray pg_node_attr(read_write_ignore);
-	bool		amsearchnulls pg_node_attr(read_write_ignore);
+	bool		amcanorderbyop;
+	bool		amoptionalkey;
+	bool		amsearcharray;
+	bool		amsearchnulls;
 	/* does AM have amgettuple interface? */
-	bool		amhasgettuple pg_node_attr(read_write_ignore);
+	bool		amhasgettuple;
 	/* does AM have amgetbitmap interface? */
-	bool		amhasgetbitmap pg_node_attr(read_write_ignore);
-	bool		amcanparallel pg_node_attr(read_write_ignore);
+	bool		amhasgetbitmap;
+	bool		amcanparallel;
 	/* does AM have ammarkpos interface? */
-	bool		amcanmarkpos pg_node_attr(read_write_ignore);
+	bool		amcanmarkpos;
 	/* AM's cost estimator */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) () pg_node_attr(read_write_ignore);
@@ -1184,12 +1191,9 @@ typedef struct StatisticExtInfo
 	Oid			statOid;
 
 	/* includes child relations */
-	bool		inherit pg_node_attr(read_write_ignore);
+	bool		inherit;
 
-	/*
-	 * back-link to statistic's table; don't print, infinite recursion on plan
-	 * tree dump
-	 */
+	/* back-link to statistic's table; don't print, else infinite recursion */
 	RelOptInfo *rel pg_node_attr(read_write_ignore);
 
 	/* statistics kind of this entry */

Reply via email to