From 0cd2f44259c4778eb19568d9c2f3a81965642303 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 19 Apr 2022 12:03:19 +0200
Subject: [PATCH 2/2] Automatically generate node support functions

Add a script to automatically generate the node support functions
(copy, equal, out, and read, as well as the node tags enum) from the
struct definitions.

For each of the four node support files, it creates two include files,
e.g., copyfuncs.funcs.c and copyfuncs.switch.c, to include in the main
file.  All the scaffolding of the main file stays in place.

TODO: In this patch, I have only ifdef'ed out the code to could be
removed, mainly so that it won't constantly have merge conflicts.
Eventually, that should all be changed to delete the code.  All the
code comments that are worth keeping from those sections have already
been moved to the header files where the structs are defined.

I have tried to mostly make the coverage of the output match what is
currently there.  For example, one could now do out/read coverage of
utility statement nodes, but I have manually excluded those for now.
The reason is mainly that it's easier to diff the before and after,
and adding a bunch of stuff like this might require a separate
analysis and review.

Subtyping (TidScan -> Scan) is supported.

For the hard cases, you can just write a manual function and exclude
generating one.  For the not so hard cases, there is a way of
annotating struct fields to get special behaviors.  For example,
pg_node_attr(equal_ignore) has the field ignored in equal functions.

Discussion: https://www.postgresql.org/message-id/flat/c1097590-a6a4-486a-64b1-e1f9cc0533ce%40enterprisedb.com
---
 src/backend/Makefile                  |   8 +-
 src/backend/nodes/.gitignore          |   4 +
 src/backend/nodes/Makefile            |  46 ++
 src/backend/nodes/copyfuncs.c         |  19 +-
 src/backend/nodes/equalfuncs.c        |  22 +-
 src/backend/nodes/gen_node_support.pl | 729 ++++++++++++++++++++++++++
 src/backend/nodes/outfuncs.c          |  34 +-
 src/backend/nodes/readfuncs.c         |  23 +-
 src/include/nodes/.gitignore          |   2 +
 src/include/nodes/nodes.h             |  28 +
 src/include/nodes/parsenodes.h        |   3 +-
 src/include/nodes/pathnodes.h         | 170 +++---
 src/include/nodes/plannodes.h         |  90 ++--
 src/include/nodes/primnodes.h         |  33 +-
 src/include/utils/rel.h               |   6 +-
 src/tools/msvc/Solution.pm            |  46 ++
 16 files changed, 1114 insertions(+), 149 deletions(-)
 create mode 100644 src/backend/nodes/.gitignore
 create mode 100644 src/backend/nodes/gen_node_support.pl
 create mode 100644 src/include/nodes/.gitignore

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a02006788..821bef2694 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -143,11 +143,15 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
 submake-catalog-headers:
 	$(MAKE) -C catalog distprep generated-header-symlinks
 
+# run this unconditionally to avoid needing to know its dependencies here:
+submake-nodes-headers:
+	$(MAKE) -C nodes distprep generated-header-symlinks
+
 # run this unconditionally to avoid needing to know its dependencies here:
 submake-utils-headers:
 	$(MAKE) -C utils distprep generated-header-symlinks
 
-.PHONY: submake-catalog-headers submake-utils-headers
+.PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 # Make symlinks for these headers in the include directory. That way
 # we can cut down on the -I options. Also, a symlink is automatically
@@ -162,7 +166,7 @@ submake-utils-headers:
 
 .PHONY: generated-headers
 
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..0c14b5697b
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,4 @@
+/node-support-stamp
+/nodetags.h
+/*funcs.funcs.c
+/*funcs.switch.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..c7b8df4ec2 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,49 @@ OBJS = \
 	value.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+node_headers = \
+	nodes/nodes.h \
+	nodes/execnodes.h \
+	nodes/plannodes.h \
+	nodes/primnodes.h \
+	nodes/pathnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.h \
+	nodes/value.h \
+	commands/trigger.h \
+	commands/event_trigger.h \
+	foreign/fdwapi.h \
+	access/amapi.h \
+	access/tableam.h \
+	access/tsmapi.h \
+	utils/rel.h \
+	nodes/supportnodes.h \
+	executor/tuptable.h \
+	nodes/lockoptions.h \
+	access/sdir.h
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-support-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+node-support-stamp: gen_node_support.pl $(addprefix $(top_srcdir)/src/include/,$(node_headers))
+	$(PERL) $^
+	touch $@
+
+$(top_builddir)/src/include/nodes/header-stamp: node-support-stamp
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	cd '$(dir $@)' && for file in nodetags.h; do \
+	  rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \
+	done
+	touch $@
+
+maintainer-clean: clean
+	rm -f node-support-stamp *funcs.funcs.c *funcs.switch.c nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 836f427ea8..4c1a19f239 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -23,11 +23,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
-#include "nodes/plannodes.h"
 #include "utils/datum.h"
-#include "utils/rel.h"
 
 
 /*
@@ -73,6 +69,9 @@
 	(newnode->fldname = from->fldname)
 
 
+#include "copyfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /* ****************************************************************
  *					 plannodes.h copy functions
  * ****************************************************************
@@ -1465,6 +1464,7 @@ _copyVar(const Var *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * _copyConst
@@ -1504,6 +1504,7 @@ _copyConst(const Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -3246,6 +3247,7 @@ _copyParamRef(const ParamRef *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -3286,6 +3288,7 @@ _copyA_Const(const A_Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -5451,6 +5454,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *					extensible.h copy functions
@@ -5473,6 +5477,7 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /* ****************************************************************
  *					value.h copy functions
  * ****************************************************************
@@ -5543,6 +5548,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5563,6 +5569,8 @@ copyObjectImpl(const void *from)
 
 	switch (nodeTag(from))
 	{
+#include "copyfuncs.switch.c"
+#ifdef OBSOLETE
 			/*
 			 * PLAN NODES
 			 */
@@ -6007,6 +6015,7 @@ copyObjectImpl(const void *from)
 		case T_BitString:
 			retval = _copyBitString(from);
 			break;
+#endif /*OBSOLETE*/
 
 			/*
 			 * LIST NODES
@@ -6024,6 +6033,7 @@ copyObjectImpl(const void *from)
 			retval = list_copy(from);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -6575,6 +6585,7 @@ copyObjectImpl(const void *from)
 		case T_ForeignKeyCacheInfo:
 			retval = _copyForeignKeyCacheInfo(from);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e013c1bbfe..bb17e9a38c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -10,9 +10,6 @@
  * because the circular linkages between RelOptInfo and Path nodes can't
  * be handled easily in a simple depth-first traversal.
  *
- * Currently, in fact, equal() doesn't know how to compare Plan trees
- * either.  This might need to be fixed someday.
- *
  * NOTE: it is intentional that parse location fields (in nodes that have
  * one) are not compared.  This is because we want, for example, a variable
  * "x" to be considered equal() to another reference to "x" in the query.
@@ -30,8 +27,6 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
 #include "utils/datum.h"
 
 
@@ -97,6 +92,9 @@
 	((void) 0)
 
 
+#include "equalfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from primnodes.h
  */
@@ -241,6 +239,7 @@ _equalVar(const Var *a, const Var *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -263,6 +262,7 @@ _equalConst(const Const *a, const Const *b)
 						a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -1286,6 +1286,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -1307,6 +1308,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const ExtensibleNode *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2797,6 +2799,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2814,6 +2817,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3451,6 +3455,7 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3511,6 +3516,7 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from value.h
  */
@@ -3554,6 +3560,7 @@ _equalBitString(const BitString *a, const BitString *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * equal
@@ -3584,6 +3591,8 @@ equal(const void *a, const void *b)
 
 	switch (nodeTag(a))
 	{
+#include "equalfuncs.switch.c"
+#ifdef OBSOLETE
 			/*
 			 * PRIMITIVE NODES
 			 */
@@ -3804,6 +3813,7 @@ equal(const void *a, const void *b)
 		case T_PlaceHolderInfo:
 			retval = _equalPlaceHolderInfo(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		case T_List:
 		case T_IntList:
@@ -3811,6 +3821,7 @@ equal(const void *a, const void *b)
 			retval = _equalList(a, b);
 			break;
 
+#ifdef OBSOLETE
 		case T_Integer:
 			retval = _equalInteger(a, b);
 			break;
@@ -4410,6 +4421,7 @@ equal(const void *a, const void *b)
 		case T_JsonTableColumn:
 			retval = _equalJsonTableColumn(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
new file mode 100644
index 0000000000..edbafd3e5b
--- /dev/null
+++ b/src/backend/nodes/gen_node_support.pl
@@ -0,0 +1,729 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/backend/nodes/gen_node_support.pl
+#
+#----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use File::Basename;
+
+use FindBin;
+use lib "$FindBin::RealBin/../catalog";
+
+use Catalog;  # for RenameTempFile
+
+
+# Test whether first argument is element of the list in the second
+# argument
+sub elem
+{
+	my $x = shift;
+	return grep { $_ eq $x } @_;
+}
+
+
+# collect node names
+my @node_types = qw(Node);
+# collect info for each node type
+my %node_type_info;
+
+# node types we don't want copy support for
+my @no_copy;
+# node types we don't want read/write support for
+my @no_read_write;
+
+# types that are copied by straight assignment
+my @scalar_types = qw(
+	bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+	AclMode AttrNumber Cardinality Cost Index Oid Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr
+);
+
+# collect enum types
+my @enum_types;
+
+# Abstract types are types that cannot be instantiated but that can be
+# supertypes of other types.  We track their fields, so that subtypes
+# can use them, but we don't emit a node tag, so you can't instantiate
+# them.
+my @abstract_types = qw(
+	Node Expr
+	BufferHeapTupleTableSlot HeapTupleTableSlot MinimalTupleTableSlot VirtualTupleTableSlot
+	JoinPath
+	PartitionPruneStep
+);
+
+# Special cases that either don't have their own struct or the struct
+# is not in a header file.  We just generate node tags for them, but
+# they otherwise don't participate in node support.
+my @extra_tags = qw(
+	IntList OidList
+	AllocSetContext GenerationContext SlabContext
+	TIDBitmap
+	WindowObjectData
+);
+
+# This is a regular node, but we skip parsing it from its header file
+# since we won't use its internal structure here anyway.
+push @node_types, qw(List);
+
+# pathnodes.h exceptions: We don't support copying RelOptInfo,
+# IndexOptInfo, or Path nodes.  There are some subsidiary structs that
+# are useful to copy, though.
+push @no_copy, qw(
+	RelOptInfo IndexOptInfo Path PlannerGlobal EquivalenceClass EquivalenceMember ForeignKeyOptInfo
+	GroupingSetData IncrementalSortPath IndexClause MinMaxAggInfo PathTarget PlannerInfo PlannerParamItem
+	ParamPathInfo RollupData RowIdentityVarInfo StatisticExtInfo
+);
+# EquivalenceClasses are never moved, so just shallow-copy the pointer
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember*);
+push @scalar_types, qw(QualCost);
+
+# See special treatment in outNode() and nodeRead() for these.
+push @no_read_write, qw(BitString Boolean Float Integer List String);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation);
+
+
+## read input
+
+foreach my $infile (@ARGV)
+{
+	my $in_struct;
+	my $subline;
+	my $is_node_struct;
+	my $supertype;
+	my $supertype_field;
+
+	my @my_fields;
+	my %my_field_types;
+	my %my_field_attrs;
+
+	open my $ifh, '<', $infile or die "could not open \"$infile\": $!";
+
+	my $file_content = do { local $/; <$ifh> };
+
+	# strip C comments
+	$file_content =~ s{/\*.*?\*/}{}gs;
+
+	foreach my $line (split /\n/, $file_content)
+	{
+		chomp $line;
+		$line =~ s/\s*$//;
+		next if $line eq '';
+		next if $line =~ /^#(define|ifdef|endif)/;
+
+		# we are analyzing a struct definition
+		if ($in_struct)
+		{
+			$subline++;
+
+			# first line should have opening brace
+			if ($subline == 1)
+			{
+				$is_node_struct = 0;
+				$supertype = undef;
+				next if $line eq '{';
+				die;
+			}
+			# second line should have node tag or supertype
+			elsif ($subline == 2)
+			{
+				if ($line =~ /^\s*NodeTag\s+type;/)
+				{
+					$is_node_struct = 1;
+					next;
+				}
+				elsif ($line =~ /\s*(\w+)\s+(\w+);/ and elem $1, @node_types)
+				{
+					$is_node_struct = 1;
+					$supertype = $1;
+					$supertype_field = $2;
+					next;
+				}
+			}
+
+			# end of struct
+			if ($line =~ /^\}\s*$in_struct;$/ || $line =~ /^\};$/)
+			{
+				if ($is_node_struct)
+				{
+					# This is the end of a node struct definition.
+					# Save everything we have collected.
+
+					# node name
+					push @node_types, $in_struct;
+
+					# field names, types, attributes
+					my @f = @my_fields;
+					my %ft = %my_field_types;
+					my %fa = %my_field_attrs;
+
+					# If there is a supertype, add those fields, too.
+					if ($supertype)
+					{
+						my @superfields;
+						foreach my $sf (@{$node_type_info{$supertype}->{fields}})
+						{
+							my $fn = "${supertype_field}.$sf";
+							push @superfields, $fn;
+							$ft{$fn} = $node_type_info{$supertype}->{field_types}{$sf};
+							$fa{$fn} = $node_type_info{$supertype}->{field_attrs}{$sf};
+							$fa{$fn} =~ s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/ if $fa{$fn};
+						}
+						unshift @f, @superfields;
+					}
+					# save in global info structure
+					$node_type_info{$in_struct}->{fields} = \@f;
+					$node_type_info{$in_struct}->{field_types} = \%ft;
+					$node_type_info{$in_struct}->{field_attrs} = \%fa;
+
+					# Nodes from these files don't need to be
+					# supported, except the node tags.
+					if (elem basename($infile),
+						qw(execnodes.h trigger.h event_trigger.h amapi.h tableam.h
+							tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h))
+					{
+						push @no_copy, $in_struct;
+						push @no_read_write, $in_struct;
+					}
+
+					# We do not support copying Path trees, mainly
+					# because the circular linkages between RelOptInfo
+					# and Path nodes can't be handled easily in a
+					# simple depth-first traversal.
+					if ($supertype && ($supertype eq 'Path' || $supertype eq 'JoinPath'))
+					{
+						push @no_copy, $in_struct;
+					}
+				}
+
+				# start new cycle
+				$in_struct = undef;
+				@my_fields = ();
+				%my_field_types = ();
+				%my_field_attrs = ();
+			}
+			# normal struct field
+			elsif ($line =~ /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w() ]*)\))?;/)
+			{
+				if ($is_node_struct)
+				{
+					my $type = $1;
+					my $name = $2;
+					my $array_size = $3;
+					my $attr = $4;
+
+					# strip "const"
+					$type =~ s/^const\s*//;
+					# strip trailing space
+					$type =~ s/\s*$//;
+					# strip space between type and "*" (pointer) */
+					$type =~ s/\s+\*$/*/;
+
+					die if $type eq '';
+
+					$type = $type . $array_size if $array_size;
+					push @my_fields, $name;
+					$my_field_types{$name} = $type;
+					$my_field_attrs{$name} = $attr;
+				}
+			}
+			else
+			{
+				if ($is_node_struct)
+				{
+					#warn "$infile:$.: could not parse \"$line\"\n";
+				}
+			}
+		}
+		# not in a struct
+		else
+		{
+			# start of a struct?
+			if ($line =~ /^(?:typedef )?struct (\w+)(\s*\/\*.*)?$/ && $1 ne 'Node')
+			{
+				$in_struct = $1;
+				$subline = 0;
+			}
+			# one node type typedef'ed directly from another
+			elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types)
+			{
+				my $alias_of = $1;
+				my $n = $2;
+
+				# copy everything over
+				push @node_types, $n;
+				my @f = @{$node_type_info{$alias_of}->{fields}};
+				my %ft = %{$node_type_info{$alias_of}->{field_types}};
+				my %fa = %{$node_type_info{$alias_of}->{field_attrs}};
+				$node_type_info{$n}->{fields} = \@f;
+				$node_type_info{$n}->{field_types} = \%ft;
+				$node_type_info{$n}->{field_attrs} = \%fa;
+			}
+			# collect enum names
+			elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/)
+			{
+				push @enum_types, $1;
+			}
+		}
+	}
+
+	if ($in_struct)
+	{
+		die "runaway \"$in_struct\" in file \"$infile\"\n";
+	}
+
+	close $ifh;
+} # for each file
+
+
+## write output
+
+my $tmpext  = ".tmp$$";
+
+# nodetags.h
+
+open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
+
+my $i = 1;
+foreach my $n (@node_types,@extra_tags)
+{
+	next if elem $n, @abstract_types;
+	print $nt "\tT_${n} = $i,\n";
+	$i++;
+}
+
+close $nt;
+
+
+# make #include lines necessary to pull in all the struct definitions
+my $node_includes = '';
+foreach my $infile (sort @ARGV)
+{
+	$infile =~ s!.*src/include/!!;
+	$node_includes .= qq{#include "$infile"\n};
+}
+
+
+# copyfuncs.c, equalfuncs.c
+
+open my $cff, '>', 'copyfuncs.funcs.c' . $tmpext or die $!;
+open my $eff, '>', 'equalfuncs.funcs.c' . $tmpext or die $!;
+open my $cfs, '>', 'copyfuncs.switch.c' . $tmpext or die $!;
+open my $efs, '>', 'equalfuncs.switch.c' . $tmpext or die $!;
+
+# add required #include lines to each file set
+print $cff $node_includes;
+print $eff $node_includes;
+
+# Nodes with custom copy implementations are skipped from .funcs.c but
+# need case statements in .switch.c.
+my @custom_copy = qw(A_Const Const ExtensibleNode);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_copy;
+	next if $n eq 'List';
+
+	print $cfs "
+\t\tcase T_${n}:
+\t\t\tretval = _copy${n}(from);
+\t\t\tbreak;";
+
+	print $efs "
+\t\tcase T_${n}:
+\t\t\tretval = _equal${n}(a, b);
+\t\t\tbreak;";
+
+	next if elem $n, @custom_copy;
+
+	print $cff "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+";
+
+	print $eff "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+";
+
+	# 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 $copy_ignore = ($a =~ /\bcopy_ignore\b/);
+		my $equal_ignore = ($a =~ /\bequal_ignore\b/);
+
+		# select instructions by field type
+		if ($t eq 'char*')
+		{
+			print $cff "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_STRING_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $cff "\tCOPY_BITMAPSET_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_BITMAPSET_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $cff "\tCOPY_LOCATION_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_LOCATION_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif (elem $t, @scalar_types or elem $t, @enum_types)
+		{
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			if ($a =~ /\bequal_ignore_if_zero\b/)
+			{
+				print $eff "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
+			}
+			else
+			{
+				print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+			}
+		}
+		# scalar type pointer
+		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
+		{
+			my $tt = $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $cff "\tCOPY_POINTER_FIELD($f, list_length(from->$array_size_field) * sizeof($tt));\n" unless $copy_ignore;
+				print $eff "\tCOMPARE_POINTER_FIELD($f, list_length(a->$array_size_field) * sizeof($tt));\n" unless $equal_ignore;
+			}
+			else
+			{
+				print $cff "\tCOPY_POINTER_FIELD($f, from->$array_size_field * sizeof($tt));\n" unless $copy_ignore;
+				print $eff "\tCOMPARE_POINTER_FIELD($f, a->$array_size_field * sizeof($tt));\n" unless $equal_ignore;
+			}
+		}
+		# node type
+		elsif ($t =~ /(\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+\[/)
+		{
+			print $cff "\tCOPY_ARRAY_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_ARRAY_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			# Fields of these types are required to be a pointer to a
+			# static table of callback functions.  So we don't copy
+			# the table itself, just reference the original one.
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $cff "
+\treturn newnode;
+}
+";
+	print $eff "
+\treturn true;
+}
+";
+}
+
+close $cff;
+close $eff;
+close $cfs;
+close $efs;
+
+
+# outfuncs.c, readfuncs.c
+
+open my $off, '>', 'outfuncs.funcs.c' . $tmpext or die $!;
+open my $rff, '>', 'readfuncs.funcs.c' . $tmpext or die $!;
+open my $ofs, '>', 'outfuncs.switch.c' . $tmpext or die $!;
+open my $rfs, '>', 'readfuncs.switch.c' . $tmpext or die $!;
+
+print $off $node_includes;
+print $rff $node_includes;
+
+my @custom_readwrite = qw(A_Const A_Expr BoolExpr Const Constraint EquivalenceClass ExtensibleNode ForeignKeyOptInfo Query RangeTblEntry);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_read_write;
+
+	# XXX For now, skip all "Stmt"s except that ones that were there before.
+	if ($n =~ /Stmt$/)
+	{
+		my @keep = qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt ImportForeignSchemaStmt IndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt);
+		next unless elem $n, @keep;
+	}
+
+	# XXX Also skip read support for those that didn't have it before.
+	my $no_read = ($n eq 'A_Star' || $n eq 'A_Const' || $n eq 'A_Expr' || $n eq 'Constraint' || $n =~ /Path$/ || $n eq 'EquivalenceClass' || $n eq 'ForeignKeyCacheInfo' || $n eq 'ForeignKeyOptInfo' || $n eq 'PathTarget');
+
+	# output format starts with upper case node type, underscores stripped
+	my $N = uc $n;
+	$N =~ s/_//g;
+
+	print $ofs "\t\t\tcase T_${n}:\n".
+	  "\t\t\t\t_out${n}(str, obj);\n".
+	  "\t\t\t\tbreak;\n";
+
+	print $rfs "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+
+	next if elem $n, @custom_readwrite;
+
+	print $off "
+static void
+_out${n}(StringInfo str, const $n *node)
+{
+\tWRITE_NODE_TYPE(\"$N\");
+
+";
+
+	print $rff "
+static $n *
+_read${n}(void)
+{
+\tREAD_LOCALS($n);
+
+" unless $no_read;
+
+	# 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 $readwrite_ignore = ($a =~ /\breadwrite_ignore\b/);
+		next if $readwrite_ignore;
+
+		# XXX Previously, for subtyping, only the leaf field name is
+		# used. Ponder whether we want to keep it that way.
+
+		# select instructions by field type
+		if ($t eq 'bool')
+		{
+			print $off "\tWRITE_BOOL_FIELD($f);\n";
+			print $rff "\tREAD_BOOL_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $off "\tWRITE_LOCATION_FIELD($f);\n";
+			print $rff "\tREAD_LOCATION_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' || $t eq 'int32' || $t eq 'AttrNumber' || $t eq 'StrategyNumber')
+		{
+			print $off "\tWRITE_INT_FIELD($f);\n";
+			print $rff "\tREAD_INT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint32' || $t eq 'bits32' || $t eq 'AclMode' || $t eq 'BlockNumber' || $t eq 'Index' || $t eq 'SubTransactionId')
+		{
+			print $off "\tWRITE_UINT_FIELD($f);\n";
+			print $rff "\tREAD_UINT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint64')
+		{
+			print $off "\tWRITE_UINT64_FIELD($f);\n";
+			print $rff "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Oid')
+		{
+			print $off "\tWRITE_OID_FIELD($f);\n";
+			print $rff "\tREAD_OID_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'long')
+		{
+			print $off "\tWRITE_LONG_FIELD($f);\n";
+			print $rff "\tREAD_LONG_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char')
+		{
+			print $off "\tWRITE_CHAR_FIELD($f);\n";
+			print $rff "\tREAD_CHAR_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'double')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.6f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cardinality')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.0f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cost')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.2f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'QualCost')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f.startup, \"%.2f\");\n";
+			print $off "\tWRITE_FLOAT_FIELD($f.per_tuple, \"%.2f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read;
+			print $rff "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
+		}
+		elsif ($t eq 'Selectivity')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.4f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $off "\tWRITE_STRING_FIELD($f);\n";
+			print $rff "\tREAD_STRING_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $off "\tWRITE_BITMAPSET_FIELD($f);\n";
+			print $rff "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
+		}
+		elsif (elem $t, @enum_types)
+		{
+			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)
+		{
+			my $tt = uc $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $off "\tWRITE_${tt}_ARRAY($f, list_length(node->$array_size_field));\n";
+				print $rff "\tREAD_${tt}_ARRAY($f, list_length(local_node->$array_size_field));\n" unless $no_read;
+			}
+			else
+			{
+				print $off "\tWRITE_${tt}_ARRAY($f, node->$array_size_field);\n";
+				print $rff "\tREAD_${tt}_ARRAY($f, local_node->$array_size_field);\n" unless $no_read;
+			}
+		}
+		# Special treatments of several Path node fields
+		#
+		# We do not print the parent, else we'd be in infinite
+		# recursion.  We can print the parent's relids for
+		# identification purposes, though.  We print the pathtarget
+		# only if it's not the default one for the rel.  We also do
+		# not print the whole of param_info, since it's printed via
+		# RelOptInfo; it's sufficient and less cluttering to print
+		# just the required outer relids.
+		elsif ($t eq 'RelOptInfo*' && $a eq 'path_hack1')
+		{
+			print $off "\tappendStringInfoString(str, \" :parent_relids \");\n".
+			  "\toutBitmapset(str, node->$f->relids);\n";
+		}
+		elsif ($t eq 'PathTarget*' && $a eq 'path_hack2')
+		{
+			(my $f2 = $f) =~ s/pathtarget/parent/;
+			print $off "\tif (node->$f != node->$f2->reltarget)\n".
+			  "\t\tWRITE_NODE_FIELD($f);\n";
+		}
+		elsif ($t eq 'ParamPathInfo*' && $a eq 'path_hack3')
+		{
+			print $off "\tif (node->$f)\n".
+			  "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
+			  "\telse\n".
+			  "\t\toutBitmapset(str, NULL);\n";
+		}
+		# node type
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $off "\tWRITE_NODE_FIELD($f);\n";
+			print $rff "\tREAD_NODE_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $off q{
+	/* CustomName is a key to lookup CustomScanMethods */
+	appendStringInfoString(str, " :methods ");
+	outToken(str, node->methods->CustomName);
+};
+			print $rff q!
+	{
+		/* Lookup CustomScanMethods by CustomName */
+		char	   *custom_name;
+		const CustomScanMethods *methods;
+		token = pg_strtok(&length); /* skip methods: */
+		token = pg_strtok(&length); /* CustomName */
+		custom_name = nullable_string(token, length);
+		methods = GetCustomScanMethods(custom_name, false);
+		local_node->methods = methods;
+	}
+! unless $no_read;
+		}
+		# various field types to ignore
+		elsif ($t eq 'ParamListInfo' || $t =~ /PartitionBoundInfoData/ || $t eq 'PartitionDirectory' || $t eq 'PartitionScheme' || $t eq 'void*' || $t =~ /\*\*$/)
+		{
+			# ignore
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $off "}
+";
+	print $rff "
+\tREAD_DONE();
+}
+" unless $no_read;
+}
+
+close $off;
+close $rff;
+close $ofs;
+close $rfs;
+
+
+# now rename the temporary files to their final name
+foreach my $file (qw(nodetags.h copyfuncs.funcs.c copyfuncs.switch.c equalfuncs.funcs.c equalfuncs.switch.c outfuncs.funcs.c outfuncs.switch.c readfuncs.funcs.c readfuncs.switch.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6a02f81ad5..49d23de268 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -31,11 +31,10 @@
 
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
-#include "nodes/plannodes.h"
+#include "nodes/bitmapset.h"
+#include "nodes/nodes.h"
+#include "nodes/pg_list.h"
 #include "utils/datum.h"
-#include "utils/rel.h"
 
 static void outChar(StringInfo str, char c);
 
@@ -295,6 +294,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
 }
 
 
+#include "outfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from plannodes.h
  */
@@ -1144,6 +1146,7 @@ _outVar(StringInfo str, const Var *node)
 	WRITE_INT_FIELD(varattnosyn);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outConst(StringInfo str, const Const *node)
@@ -1165,6 +1168,7 @@ _outConst(StringInfo str, const Const *node)
 		outDatum(str, node->constvalue, node->constlen, node->constbyval);
 }
 
+#ifdef OBSOLETE
 static void
 _outParam(StringInfo str, const Param *node)
 {
@@ -1335,6 +1339,7 @@ _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 	WRITE_NODE_FIELD(args);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outBoolExpr(StringInfo str, const BoolExpr *node)
@@ -1363,6 +1368,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2573,6 +2579,7 @@ _outIndexOptInfo(StringInfo str, const IndexOptInfo *node)
 	WRITE_BOOL_FIELD(hypothetical);
 	/* we don't bother with fields copied from the index AM's API struct */
 }
+#endif /* OBSOLETE */
 
 static void
 _outForeignKeyOptInfo(StringInfo str, const ForeignKeyOptInfo *node)
@@ -2600,6 +2607,7 @@ _outForeignKeyOptInfo(StringInfo str, const ForeignKeyOptInfo *node)
 		appendStringInfo(str, " %d", list_length(node->rinfos[i]));
 }
 
+#ifdef OBSOLETE
 static void
 _outStatisticExtInfo(StringInfo str, const StatisticExtInfo *node)
 {
@@ -2611,6 +2619,7 @@ _outStatisticExtInfo(StringInfo str, const StatisticExtInfo *node)
 	WRITE_CHAR_FIELD(kind);
 	WRITE_BITMAPSET_FIELD(keys);
 }
+#endif /* OBSOLETE */
 
 static void
 _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
@@ -2639,6 +2648,7 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
 	WRITE_UINT_FIELD(ec_max_security);
 }
 
+#ifdef OBSOLETE
 static void
 _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 {
@@ -2823,6 +2833,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
 	WRITE_NODE_FIELD(item);
 	WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2845,6 +2856,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -3178,6 +3190,7 @@ _outStatsElem(StringInfo str, const StatsElem *node)
 	WRITE_STRING_FIELD(name);
 	WRITE_NODE_FIELD(expr);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outQuery(StringInfo str, const Query *node)
@@ -3252,6 +3265,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3417,6 +3431,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_NODE_FIELD(colCollations);
 	WRITE_NODE_FIELD(groupClauses);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
@@ -3497,6 +3512,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3520,6 +3536,7 @@ _outTableSampleClause(StringInfo str, const TableSampleClause *node)
 	WRITE_NODE_FIELD(args);
 	WRITE_NODE_FIELD(repeatable);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outA_Expr(StringInfo str, const A_Expr *node)
@@ -3638,6 +3655,7 @@ _outBitString(StringInfo str, const BitString *node)
 	appendStringInfoString(str, node->bsval);
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3669,6 +3687,7 @@ _outRawStmt(StringInfo str, const RawStmt *node)
 	WRITE_LOCATION_FIELD(stmt_location);
 	WRITE_INT_FIELD(stmt_len);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outA_Const(StringInfo str, const A_Const *node)
@@ -3685,6 +3704,7 @@ _outA_Const(StringInfo str, const A_Const *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outA_Star(StringInfo str, const A_Star *node)
 {
@@ -3829,6 +3849,7 @@ _outRangeTableFuncCol(StringInfo str, const RangeTableFuncCol *node)
 	WRITE_NODE_FIELD(coldefexpr);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outConstraint(StringInfo str, const Constraint *node)
@@ -3951,6 +3972,7 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -4011,6 +4033,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -4042,6 +4065,8 @@ outNode(StringInfo str, const void *obj)
 		appendStringInfoChar(str, '{');
 		switch (nodeTag(obj))
 		{
+#include "outfuncs.switch.c"
+#ifdef OBSOLETE
 			case T_PlannedStmt:
 				_outPlannedStmt(str, obj);
 				break;
@@ -4753,6 +4778,7 @@ outNode(StringInfo str, const void *obj)
 			case T_JsonTableSibling:
 				_outJsonTableSibling(str, obj);
 				break;
+#endif /*OBSOLETE*/
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ddf76ac778..a55d52f66b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -33,9 +33,7 @@
 #include <math.h>
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/parsenodes.h"
-#include "nodes/plannodes.h"
+#include "nodes/bitmapset.h"
 #include "nodes/readfuncs.h"
 
 
@@ -238,6 +236,8 @@ readBitmapset(void)
 	return _readBitmapset();
 }
 
+#include "readfuncs.funcs.c"
+
 /*
  * _readQuery
  */
@@ -291,6 +291,7 @@ _readQuery(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readNotifyStmt
  */
@@ -629,6 +630,7 @@ _readVar(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readConst
@@ -655,6 +657,7 @@ _readConst(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readParam
  */
@@ -880,6 +883,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readBoolExpr
@@ -907,6 +911,7 @@ _readBoolExpr(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readSubLink
  */
@@ -1647,6 +1652,7 @@ _readAppendRelInfo(void)
 /*
  *	Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1742,6 +1748,7 @@ _readRangeTblEntry(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2870,6 +2877,7 @@ _readAlternativeSubPlan(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2901,6 +2909,7 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2935,6 +2944,7 @@ _readPartitionRangeDatum(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2959,7 +2969,11 @@ parseNodeString(void)
 #define MATCH(tokname, namelen) \
 	(length == namelen && memcmp(token, tokname, namelen) == 0)
 
-	if (MATCH("QUERY", 5))
+	if (false)
+		;
+#include "readfuncs.switch.c"
+#ifdef OBSOLETE
+	else if (MATCH("QUERY", 5))
 		return_value = _readQuery();
 	else if (MATCH("WITHCHECKOPTION", 15))
 		return_value = _readWithCheckOption();
@@ -3233,6 +3247,7 @@ parseNodeString(void)
 		return_value = _readJsonTableParent();
 	else if (MATCH("JSONTABSNODE", 12))
 		return_value = _readJsonTableSibling();
+#endif /*OBSOLETE*/
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/include/nodes/.gitignore b/src/include/nodes/.gitignore
new file mode 100644
index 0000000000..99fb1d3787
--- /dev/null
+++ b/src/include/nodes/.gitignore
@@ -0,0 +1,2 @@
+/nodetags.h
+/header-stamp
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 340d28f4e1..ad96316ca6 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -27,6 +27,8 @@ typedef enum NodeTag
 {
 	T_Invalid = 0,
 
+#include "nodes/nodetags.h"
+#ifdef OBSOLETE
 	/*
 	 * TAGS FOR EXECUTOR NODES (execnodes.h)
 	 */
@@ -562,8 +564,34 @@ typedef enum NodeTag
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition, /* in nodes/supportnodes.h */
 	T_SupportRequestWFuncMonotonic	/* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * Used in node definitions to set extra information for gen_node_support.pl
+ *
+ * The argument is a space-separated list of attributes.  The following
+ * attributes are currently used:
+ *
+ * - array_size(OTHERFIELD): This field is a dynamically allocated array with
+ *   size indicated by the mentioned other field.  The other field is either a
+ *   scalar or a list, in which case the length of the list is used.
+ *
+ * - copy_ignore: Ignore the field for copy.
+ *
+ * - equal_ignore: Ignore the field for equality.
+ *
+ * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
+ *   (Otherwise, compare normally.)
+ *
+ * - readwrite_ignore: Ignore the field for read/write.
+ *
+ * Unknown attributes are ignored.  Some additional attributes are used for
+ * special "hack" cases.
+ */
+#define pg_node_attr(attrs)
+
+
 /*
  * The first field of a node of any type is guaranteed to be the NodeTag.
  * Hence the type of any node can be gotten by casting it to Node. Declaring
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b1f81feb46..068cf76ba3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -123,7 +123,8 @@ typedef struct Query
 
 	QuerySource querySource;	/* where did I come from? */
 
-	uint64		queryId;		/* query identifier (can be set by plugins) */
+	uint64		queryId pg_node_attr(equal_ignore);		/* query identifier (can be set by plugins);
+														   ignored for equal, might not be set */
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index c5ab53e05c..96397c486a 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -227,7 +227,7 @@ struct PlannerInfo
 	 * GEQO.
 	 */
 	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
-	struct HTAB *join_rel_hash; /* optional hashtable for join relations */
+	struct HTAB *join_rel_hash pg_node_attr(readwrite_ignore); /* optional hashtable for join relations */
 
 	/*
 	 * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -329,10 +329,10 @@ struct PlannerInfo
 	List	   *update_colnos;
 
 	/* Fields filled during create_plan() for use in setrefs.c */
-	AttrNumber *grouping_map;	/* for GroupingFunc fixup */
+	AttrNumber *grouping_map pg_node_attr(array_size(update_colnos));	/* for GroupingFunc fixup */
 	List	   *minmax_aggs;	/* List of MinMaxAggInfos */
 
-	MemoryContext planner_cxt;	/* context holding PlannerInfo */
+	MemoryContext planner_cxt pg_node_attr(readwrite_ignore);	/* context holding PlannerInfo */
 
 	Cardinality	total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -369,8 +369,8 @@ struct PlannerInfo
 	List	   *curOuterParams; /* not-yet-assigned NestLoopParams */
 
 	/* These fields are workspace for setrefs.c */
-	bool	   *isAltSubplan;	/* array corresponding to glob->subplans */
-	bool	   *isUsedSubplan;	/* array corresponding to glob->subplans */
+	bool	   *isAltSubplan pg_node_attr(array_size(curOuterParams));	/* array corresponding to glob->subplans */
+	bool	   *isUsedSubplan pg_node_attr(array_size(curOuterParams));	/* array corresponding to glob->subplans */
 
 	/* optional private data for join_search_hook, e.g., GEQO */
 	void	   *join_search_private;
@@ -711,8 +711,8 @@ typedef struct RelOptInfo
 	RTEKind		rtekind;		/* RELATION, SUBQUERY, FUNCTION, etc */
 	AttrNumber	min_attr;		/* smallest attrno of rel (often <0) */
 	AttrNumber	max_attr;		/* largest attrno of rel */
-	Relids	   *attr_needed;	/* array indexed [min_attr .. max_attr] */
-	int32	   *attr_widths;	/* array indexed [min_attr .. max_attr] */
+	Relids	   *attr_needed pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
+	int32	   *attr_widths pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
 	List	   *lateral_vars;	/* LATERAL Vars and PHVs referenced by rel */
 	Relids		lateral_referencers;	/* rels that reference me laterally */
 	List	   *indexlist;		/* list of IndexOptInfo */
@@ -733,13 +733,14 @@ typedef struct RelOptInfo
 	Oid			userid;			/* identifies user to check access as */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
-	struct FdwRoutine *fdwroutine;
-	void	   *fdw_private;
+	struct FdwRoutine *fdwroutine pg_node_attr(readwrite_ignore);
+	void	   *fdw_private pg_node_attr(readwrite_ignore);
 
-	/* cache space for remembering if we have proven this relation unique */
-	List	   *unique_for_rels;	/* known unique for these other relid
+	/* cache space for remembering if we have proven this relation unique;
+	   can't print, BMSes aren't Nodes */
+	List	   *unique_for_rels pg_node_attr(readwrite_ignore);	/* known unique for these other relid
 									 * set(s) */
-	List	   *non_unique_for_rels;	/* known not unique for these set(s) */
+	List	   *non_unique_for_rels pg_node_attr(readwrite_ignore);	/* known not unique for these set(s) */
 
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;	/* RestrictInfo structures (if base rel) */
@@ -837,7 +838,7 @@ struct IndexOptInfo
 
 	Oid			indexoid;		/* OID of the index relation */
 	Oid			reltablespace;	/* tablespace of index (not table) */
-	RelOptInfo *rel;			/* back-link to index's table */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to index's table; don't print, else infinite recursion */
 
 	/* index-size statistics (from pg_class and elsewhere) */
 	BlockNumber pages;			/* number of disk pages in index */
@@ -847,20 +848,22 @@ struct IndexOptInfo
 	/* index descriptor information */
 	int			ncolumns;		/* number of columns in index */
 	int			nkeycolumns;	/* number of key columns in index */
-	int		   *indexkeys;		/* column numbers of index's attributes both
+	/* array fields aren't really worth the trouble to print */
+	int		   *indexkeys pg_node_attr(readwrite_ignore);		/* column numbers of index's attributes both
 								 * key and included columns, or 0 */
-	Oid		   *indexcollations;	/* OIDs of collations of index columns */
-	Oid		   *opfamily;		/* OIDs of operator families for columns */
-	Oid		   *opcintype;		/* OIDs of opclass declared input data types */
-	Oid		   *sortopfamily;	/* OIDs of btree opfamilies, if orderable */
-	bool	   *reverse_sort;	/* is sort order descending? */
-	bool	   *nulls_first;	/* do NULLs come first in the sort order? */
-	bytea	  **opclassoptions; /* opclass-specific options for columns */
-	bool	   *canreturn;		/* which index cols can be returned in an
+	Oid		   *indexcollations pg_node_attr(readwrite_ignore);	/* OIDs of collations of index columns */
+	Oid		   *opfamily pg_node_attr(readwrite_ignore);		/* OIDs of operator families for columns */
+	Oid		   *opcintype pg_node_attr(readwrite_ignore);		/* OIDs of opclass declared input data types */
+	Oid		   *sortopfamily pg_node_attr(readwrite_ignore);	/* OIDs of btree opfamilies, if orderable */
+	bool	   *reverse_sort pg_node_attr(readwrite_ignore);	/* is sort order descending? */
+	bool	   *nulls_first pg_node_attr(readwrite_ignore);	/* do NULLs come first in the sort order? */
+	bytea	  **opclassoptions pg_node_attr(readwrite_ignore); /* opclass-specific options for columns */
+	bool	   *canreturn pg_node_attr(readwrite_ignore);		/* which index cols can be returned in an
 								 * index-only scan? */
 	Oid			relam;			/* OID of the access method (in pg_am) */
 
-	List	   *indexprs;		/* expressions for non-simple index columns */
+	/* indexprs is redundant to print since we print indextlist */
+	List	   *indexprs pg_node_attr(readwrite_ignore);		/* expressions for non-simple index columns */
 	List	   *indpred;		/* predicate if a partial index, else NIL */
 
 	List	   *indextlist;		/* targetlist representing index columns */
@@ -877,14 +880,14 @@ struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
-	bool		amoptionalkey;	/* can query omit key for the first column? */
-	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
-	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
-	bool		amhasgettuple;	/* does AM have amgettuple interface? */
-	bool		amhasgetbitmap; /* does AM have amgetbitmap interface? */
-	bool		amcanparallel;	/* does AM support parallel scan? */
-	bool		amcanmarkpos;	/* does AM support mark/restore? */
+	bool		amcanorderbyop pg_node_attr(readwrite_ignore); /* does AM support order by operator result? */
+	bool		amoptionalkey pg_node_attr(readwrite_ignore);	/* can query omit key for the first column? */
+	bool		amsearcharray pg_node_attr(readwrite_ignore);	/* can AM handle ScalarArrayOpExpr quals? */
+	bool		amsearchnulls pg_node_attr(readwrite_ignore);	/* can AM search for NULL/NOT NULL entries? */
+	bool		amhasgettuple pg_node_attr(readwrite_ignore);	/* does AM have amgettuple interface? */
+	bool		amhasgetbitmap pg_node_attr(readwrite_ignore); /* does AM have amgetbitmap interface? */
+	bool		amcanparallel pg_node_attr(readwrite_ignore);	/* does AM support parallel scan? */
+	bool		amcanmarkpos pg_node_attr(readwrite_ignore);	/* does AM support mark/restore? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
 };
@@ -905,9 +908,9 @@ typedef struct ForeignKeyOptInfo
 	Index		con_relid;		/* RT index of the referencing table */
 	Index		ref_relid;		/* RT index of the referenced table */
 	int			nkeys;			/* number of columns in the foreign key */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 
 	/* Derived info about whether FK's equality conditions match the query: */
 	int			nmatched_ec;	/* # of FK cols matched by ECs */
@@ -934,8 +937,9 @@ typedef struct StatisticExtInfo
 	NodeTag		type;
 
 	Oid			statOid;		/* OID of the statistics row */
-	bool		inherit;		/* includes child relations */
-	RelOptInfo *rel;			/* back-link to statistic's table */
+	bool		inherit pg_node_attr(readwrite_ignore);		/* includes child relations */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to statistic's table;
+															   don't print, infinite recursion on plan tree dump */
 	char		kind;			/* statistics kind of this entry */
 	Bitmapset  *keys;			/* attnums of the columns covered */
 	List	   *exprs;			/* expressions */
@@ -1119,7 +1123,7 @@ typedef struct PathTarget
 {
 	NodeTag		type;
 	List	   *exprs;			/* list of expressions to be computed */
-	Index	   *sortgrouprefs;	/* corresponding sort/group refnos, or 0 */
+	Index	   *sortgrouprefs pg_node_attr(array_size(exprs));	/* corresponding sort/group refnos, or 0 */
 	QualCost	cost;			/* cost of evaluating the expressions */
 	int			width;			/* estimated avg width of result tuples */
 	VolatileFunctionStatus has_volatile_expr;	/* indicates if exprs contain
@@ -1190,10 +1194,10 @@ typedef struct Path
 
 	NodeTag		pathtype;		/* tag identifying scan/join method */
 
-	RelOptInfo *parent;			/* the relation this path can build */
-	PathTarget *pathtarget;		/* list of Vars/Exprs, cost, width */
+	RelOptInfo *parent pg_node_attr(path_hack1);			/* the relation this path can build */
+	PathTarget *pathtarget pg_node_attr(path_hack2);		/* list of Vars/Exprs, cost, width */
 
-	ParamPathInfo *param_info;	/* parameterization info, or NULL if none */
+	ParamPathInfo *param_info pg_node_attr(path_hack3);	/* parameterization info, or NULL if none */
 
 	bool		parallel_aware; /* engage parallel-aware logic? */
 	bool		parallel_safe;	/* OK to use as part of parallel plan? */
@@ -2065,6 +2069,12 @@ typedef struct LimitPath
  * apply only one.  We mark clauses of this kind by setting parent_ec to
  * point to the generating EquivalenceClass.  Multiple clauses with the same
  * parent_ec in the same join are redundant.
+ *
+ * Most fields are ignored for equality, since they may not be set yet, and
+ * should be derivable from the clause anyway.
+ *
+ * parent_ec, left_ec, right_ec are not printed, lest it lead to infinite
+ * recursion in plan tree dump.
  */
 
 typedef struct RestrictInfo
@@ -2077,19 +2087,19 @@ typedef struct RestrictInfo
 
 	bool		outerjoin_delayed;	/* true if delayed by lower outer join */
 
-	bool		can_join;		/* see comment above */
+	bool		can_join pg_node_attr(equal_ignore);		/* see comment above */
 
-	bool		pseudoconstant; /* see comment above */
+	bool		pseudoconstant pg_node_attr(equal_ignore); /* see comment above */
 
-	bool		leakproof;		/* true if known to contain no leaked Vars */
+	bool		leakproof pg_node_attr(equal_ignore);		/* true if known to contain no leaked Vars */
 
-	VolatileFunctionStatus has_volatile;	/* to indicate if clause contains
+	VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);	/* to indicate if clause contains
 											 * any volatile functions. */
 
 	Index		security_level; /* see comment above */
 
 	/* The set of relids (varnos) actually referenced in the clause: */
-	Relids		clause_relids;
+	Relids		clause_relids pg_node_attr(equal_ignore);
 
 	/* The set of relids required to evaluate the clause: */
 	Relids		required_relids;
@@ -2101,48 +2111,54 @@ typedef struct RestrictInfo
 	Relids		nullable_relids;
 
 	/* These fields are set for any binary opclause: */
-	Relids		left_relids;	/* relids in left side of clause */
-	Relids		right_relids;	/* relids in right side of clause */
+	Relids		left_relids pg_node_attr(equal_ignore);	/* relids in left side of clause */
+	Relids		right_relids pg_node_attr(equal_ignore);	/* relids in right side of clause */
 
 	/* This field is NULL unless clause is an OR clause: */
-	Expr	   *orclause;		/* modified clause with RestrictInfos */
+	Expr	   *orclause pg_node_attr(equal_ignore);		/* modified clause with RestrictInfos */
 
 	/* This field is NULL unless clause is potentially redundant: */
-	EquivalenceClass *parent_ec;	/* generating EquivalenceClass */
+	EquivalenceClass *parent_ec pg_node_attr(equal_ignore readwrite_ignore);	/* generating EquivalenceClass */
 
 	/* cache space for cost and selectivity */
-	QualCost	eval_cost;		/* eval cost of clause; -1 if not yet set */
-	Selectivity norm_selec;		/* selectivity for "normal" (JOIN_INNER)
+	QualCost	eval_cost pg_node_attr(equal_ignore);		/* eval cost of clause; -1 if not yet set */
+	Selectivity norm_selec pg_node_attr(equal_ignore);		/* selectivity for "normal" (JOIN_INNER)
 								 * semantics; -1 if not yet set; >1 means a
 								 * redundant clause */
-	Selectivity outer_selec;	/* selectivity for outer join semantics; -1 if
+	Selectivity outer_selec pg_node_attr(equal_ignore);	/* selectivity for outer join semantics; -1 if
 								 * not yet set */
 
 	/* valid if clause is mergejoinable, else NIL */
-	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *mergeopfamilies pg_node_attr(equal_ignore);	/* opfamilies containing clause operator */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
-	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
-	EquivalenceClass *right_ec; /* EquivalenceClass containing righthand */
-	EquivalenceMember *left_em; /* EquivalenceMember for lefthand */
-	EquivalenceMember *right_em;	/* EquivalenceMember for righthand */
-	List	   *scansel_cache;	/* list of MergeScanSelCache structs */
+	EquivalenceClass *left_ec pg_node_attr(equal_ignore readwrite_ignore);	/* EquivalenceClass containing lefthand */
+	EquivalenceClass *right_ec pg_node_attr(equal_ignore readwrite_ignore); /* EquivalenceClass containing righthand */
+	EquivalenceMember *left_em pg_node_attr(equal_ignore); /* EquivalenceMember for lefthand */
+	EquivalenceMember *right_em pg_node_attr(equal_ignore);	/* EquivalenceMember for righthand */
+
+	/*
+	 * List of MergeScanSelCache structs.  Those aren't Nodes, so hard to
+	 * copy.  Ignoring it will have the effect that copying will just reset
+	 * the cache.
+	 */
+	List	   *scansel_cache pg_node_attr(copy_ignore equal_ignore);
 
 	/* transient workspace for use while considering a specific join path */
-	bool		outer_is_left;	/* T = outer var on left, F = on right */
+	bool		outer_is_left pg_node_attr(equal_ignore);	/* T = outer var on left, F = on right */
 
 	/* valid if clause is hashjoinable, else InvalidOid: */
-	Oid			hashjoinoperator;	/* copy of clause operator */
+	Oid			hashjoinoperator pg_node_attr(equal_ignore);	/* copy of clause operator */
 
 	/* cache space for hashclause processing; -1 if not yet set */
-	Selectivity left_bucketsize;	/* avg bucketsize of left side */
-	Selectivity right_bucketsize;	/* avg bucketsize of right side */
-	Selectivity left_mcvfreq;	/* left side's most common val's freq */
-	Selectivity right_mcvfreq;	/* right side's most common val's freq */
+	Selectivity left_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of left side */
+	Selectivity right_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of right side */
+	Selectivity left_mcvfreq pg_node_attr(equal_ignore);	/* left side's most common val's freq */
+	Selectivity right_mcvfreq pg_node_attr(equal_ignore);	/* right side's most common val's freq */
 
 	/* hash equality operators used for memoize nodes, else InvalidOid */
-	Oid			left_hasheqoperator;
-	Oid			right_hasheqoperator;
+	Oid			left_hasheqoperator pg_node_attr(equal_ignore);
+	Oid			right_hasheqoperator pg_node_attr(equal_ignore);
 } RestrictInfo;
 
 /*
@@ -2192,13 +2208,24 @@ typedef struct MergeScanSelCache
  * Although the planner treats this as an expression node type, it is not
  * recognized by the parser or executor, so we declare it here rather than
  * in primnodes.h.
+ *
+ * We intentionally do not compare phexpr.  Two PlaceHolderVars with the
+ * same ID and levelsup should be considered equal even if the contained
+ * expressions have managed to mutate to different states.  This will
+ * happen during final plan construction when there are nested PHVs, since
+ * the inner PHV will get replaced by a Param in some copies of the outer
+ * PHV.  Another way in which it can happen is that initplan sublinks
+ * could get replaced by differently-numbered Params when sublink folding
+ * is done.  (The end result of such a situation would be some
+ * unreferenced initplans, which is annoying but not really a problem.) On
+ * the same reasoning, there is no need to examine phrels.
  */
 
 typedef struct PlaceHolderVar
 {
 	Expr		xpr;
-	Expr	   *phexpr;			/* the represented expression */
-	Relids		phrels;			/* base relids syntactically within expr src */
+	Expr	   *phexpr pg_node_attr(equal_ignore);			/* the represented expression */
+	Relids		phrels pg_node_attr(equal_ignore);			/* base relids syntactically within expr src */
 	Index		phid;			/* ID for PHV (unique within planner run) */
 	Index		phlevelsup;		/* > 0 if PHV belongs to outer query */
 } PlaceHolderVar;
@@ -2359,7 +2386,7 @@ typedef struct AppendRelInfo
 	 * child column is dropped or doesn't exist in the parent.
 	 */
 	int			num_child_cols; /* length of array */
-	AttrNumber *parent_colnos;	/* array of parent attnos, or zeroes */
+	AttrNumber *parent_colnos pg_node_attr(array_size(num_child_cols));	/* array of parent attnos, or zeroes */
 
 	/*
 	 * We store the parent table's OID here for inheritance, or InvalidOid for
@@ -2428,7 +2455,7 @@ typedef struct PlaceHolderInfo
 	NodeTag		type;
 
 	Index		phid;			/* ID for PH (unique within planner run) */
-	PlaceHolderVar *ph_var;		/* copy of PlaceHolderVar tree */
+	PlaceHolderVar *ph_var;		/* copy of PlaceHolderVar tree (should be redundant for comparison, could be ignored) */
 	Relids		ph_eval_at;		/* lowest level we can evaluate value at */
 	Relids		ph_lateral;		/* relids of contained lateral refs, if any */
 	Relids		ph_needed;		/* highest level the value is needed at */
@@ -2447,7 +2474,8 @@ typedef struct MinMaxAggInfo
 	Oid			aggfnoid;		/* pg_proc Oid of the aggregate */
 	Oid			aggsortop;		/* Oid of its sort operator */
 	Expr	   *target;			/* expression we are aggregating on */
-	PlannerInfo *subroot;		/* modified "root" for planning the subquery */
+	PlannerInfo *subroot pg_node_attr(readwrite_ignore);		/* modified "root" for planning the subquery;
+																   not printed, too large, not interesting enough */
 	Path	   *path;			/* access path for subquery */
 	Cost		pathcost;		/* estimated cost to fetch first row */
 	Param	   *param;			/* param for subplan's output */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e43e360d9b..6429195ce1 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -278,10 +278,10 @@ typedef struct MergeAppend
 	List	   *mergeplans;
 	/* these fields are just like the sort-key info in struct Sort: */
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 	/* Info for run-time subplan pruning; NULL if we're not doing that */
 	struct PartitionPruneInfo *part_prune_info;
 } MergeAppend;
@@ -301,9 +301,9 @@ typedef struct RecursiveUnion
 	/* Remaining fields are zero/null in UNION ALL case */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
-	Oid		   *dupCollations;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 	long		numGroups;		/* estimated number of groups in input */
 } RecursiveUnion;
 
@@ -780,10 +780,10 @@ typedef struct MergeJoin
 	bool		skip_mark_restore;	/* Can we skip mark/restore calls? */
 	List	   *mergeclauses;	/* mergeclauses as expression trees */
 	/* these are arrays, but have the same length as the mergeclauses list: */
-	Oid		   *mergeFamilies;	/* per-clause OIDs of btree opfamilies */
-	Oid		   *mergeCollations;	/* per-clause OIDs of collations */
-	int		   *mergeStrategies;	/* per-clause ordering (ASC or DESC) */
-	bool	   *mergeNullsFirst;	/* per-clause nulls ordering */
+	Oid		   *mergeFamilies pg_node_attr(array_size(mergeclauses));	/* per-clause OIDs of btree opfamilies */
+	Oid		   *mergeCollations pg_node_attr(array_size(mergeclauses));	/* per-clause OIDs of collations */
+	int		   *mergeStrategies pg_node_attr(array_size(mergeclauses));	/* per-clause ordering (ASC or DESC) */
+	bool	   *mergeNullsFirst pg_node_attr(array_size(mergeclauses));	/* per-clause nulls ordering */
 } MergeJoin;
 
 /* ----------------
@@ -823,8 +823,8 @@ typedef struct Memoize
 
 	int			numKeys;		/* size of the two arrays below */
 
-	Oid		   *hashOperators;	/* hash operators for each key */
-	Oid		   *collations;		/* cache keys */
+	Oid		   *hashOperators pg_node_attr(array_size(numKeys));	/* hash operators for each key */
+	Oid		   *collations pg_node_attr(array_size(numKeys));		/* cache keys */
 	List	   *param_exprs;	/* exprs containing parameters */
 	bool		singlerow;		/* true if the cache entry should be marked as
 								 * complete after we store the first tuple in
@@ -845,10 +845,10 @@ typedef struct Sort
 {
 	Plan		plan;
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 } Sort;
 
 /* ----------------
@@ -871,9 +871,9 @@ typedef struct Group
 {
 	Plan		plan;
 	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
-	Oid		   *grpCollations;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 } Group;
 
 /* ---------------
@@ -896,9 +896,9 @@ typedef struct Agg
 	AggStrategy aggstrategy;	/* basic strategy, see nodes.h */
 	AggSplit	aggsplit;		/* agg-splitting mode, see nodes.h */
 	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
-	Oid		   *grpCollations;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 	long		numGroups;		/* estimated number of groups in input */
 	uint64		transitionSpace;	/* for pass-by-ref transition data */
 	Bitmapset  *aggParams;		/* IDs of Params used in Aggref inputs */
@@ -916,13 +916,13 @@ typedef struct WindowAgg
 	Plan		plan;
 	Index		winref;			/* ID referenced by window functions */
 	int			partNumCols;	/* number of columns in partition clause */
-	AttrNumber *partColIdx;		/* their indexes in the target list */
-	Oid		   *partOperators;	/* equality operators for partition columns */
-	Oid		   *partCollations; /* collations for partition columns */
+	AttrNumber *partColIdx pg_node_attr(array_size(partNumCols));		/* their indexes in the target list */
+	Oid		   *partOperators pg_node_attr(array_size(partNumCols));	/* equality operators for partition columns */
+	Oid		   *partCollations pg_node_attr(array_size(partNumCols)); /* collations for partition columns */
 	int			ordNumCols;		/* number of columns in ordering clause */
-	AttrNumber *ordColIdx;		/* their indexes in the target list */
-	Oid		   *ordOperators;	/* equality operators for ordering columns */
-	Oid		   *ordCollations;	/* collations for ordering columns */
+	AttrNumber *ordColIdx pg_node_attr(array_size(ordNumCols));		/* their indexes in the target list */
+	Oid		   *ordOperators pg_node_attr(array_size(ordNumCols));	/* equality operators for ordering columns */
+	Oid		   *ordCollations pg_node_attr(array_size(ordNumCols));	/* collations for ordering columns */
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
@@ -946,9 +946,9 @@ typedef struct Unique
 {
 	Plan		plan;
 	int			numCols;		/* number of columns to check for uniqueness */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+	AttrNumber *uniqColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *uniqOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *uniqCollations pg_node_attr(array_size(numCols)); /* collations for equality comparisons */
 } Unique;
 
 /* ------------
@@ -984,10 +984,10 @@ typedef struct GatherMerge
 	int			rescan_param;	/* ID of Param that signals a rescan, or -1 */
 	/* remaining fields are just like the sort-key info in struct Sort */
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 	Bitmapset  *initParam;		/* param id's of initplans which are referred
 								 * at gather merge or one of it's child node */
 } GatherMerge;
@@ -1027,9 +1027,9 @@ typedef struct SetOp
 	SetOpStrategy strategy;		/* how to do it, see nodes.h */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
-	Oid		   *dupCollations;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 	AttrNumber	flagColIdx;		/* where is the flag column, if any */
 	int			firstFlag;		/* flag value for first input relation */
 	long		numGroups;		/* estimated number of groups in input */
@@ -1065,9 +1065,9 @@ typedef struct Limit
 	Node	   *limitCount;		/* COUNT parameter, or NULL if none */
 	LimitOption limitOption;	/* limit type */
 	int			uniqNumCols;	/* number of columns to check for similarity  */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+	AttrNumber *uniqColIdx pg_node_attr(array_size(uniqNumCols));		/* their indexes in the target list */
+	Oid		   *uniqOperators pg_node_attr(array_size(uniqNumCols));	/* equality operators to compare with */
+	Oid		   *uniqCollations pg_node_attr(array_size(uniqNumCols)); /* collations for equality comparisons */
 } Limit;
 
 
@@ -1226,9 +1226,9 @@ typedef struct PartitionedRelPruneInfo
 	Bitmapset  *present_parts;	/* Indexes of all partitions which subplans or
 								 * subparts are present for */
 	int			nparts;			/* Length of the following arrays: */
-	int		   *subplan_map;	/* subplan index by partition index, or -1 */
-	int		   *subpart_map;	/* subpart index by partition index, or -1 */
-	Oid		   *relid_map;		/* relation OID by partition index, or 0 */
+	int		   *subplan_map pg_node_attr(array_size(nparts));	/* subplan index by partition index, or -1 */
+	int		   *subpart_map pg_node_attr(array_size(nparts));	/* subpart index by partition index, or -1 */
+	Oid		   *relid_map pg_node_attr(array_size(nparts));		/* relation OID by partition index, or 0 */
 
 	/*
 	 * initial_pruning_steps shows how to prune during executor startup (i.e.,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 66d32fc006..ced612fa2c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -63,7 +63,9 @@ typedef enum OnCommitAction
 typedef struct RangeVar
 {
 	NodeTag		type;
-	char	   *catalogname;	/* the catalog (database) name, or NULL */
+	/* the catalog (database) name, or NULL; ignored for read/write, since it
+	 * is presently not semantically meaningful */
+	char	   *catalogname pg_node_attr(readwrite_ignore);
 	char	   *schemaname;		/* the schema name, or NULL */
 	char	   *relname;		/* the relation/sequence name */
 	bool		inh;			/* expand rel by inheritance? recursively act
@@ -205,8 +207,15 @@ typedef struct Var
 	Index		varlevelsup;	/* for subquery variables referencing outer
 								 * relations; 0 in a normal var, >0 means N
 								 * levels up */
-	Index		varnosyn;		/* syntactic relation index (0 if unknown) */
-	AttrNumber	varattnosyn;	/* syntactic attribute number */
+
+	/*
+	 * varnosyn/varattnosyn are ignored for equality, because Vars with
+	 * different syntactic identifiers are semantically the same as long as
+	 * their varno/varattno match.
+	 */
+	Index		varnosyn pg_node_attr(equal_ignore);		/* syntactic relation index (0 if unknown) */
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore);	/* syntactic attribute number */
+
 	int			location;		/* token location, or -1 if unknown */
 } Var;
 
@@ -333,7 +342,7 @@ typedef struct Aggref
 	Oid			aggtype;		/* type Oid of result of the aggregate */
 	Oid			aggcollid;		/* OID of collation of result */
 	Oid			inputcollid;	/* OID of collation that function should use */
-	Oid			aggtranstype;	/* type Oid of aggregate's transition value */
+	Oid			aggtranstype pg_node_attr(equal_ignore);	/* type Oid of aggregate's transition value; ignored for equal since it might not be set yet */
 	List	   *aggargtypes;	/* type Oids of direct and aggregated args */
 	List	   *aggdirectargs;	/* direct arguments, if an ordered-set agg */
 	List	   *args;			/* aggregated arguments and sort expressions */
@@ -380,8 +389,8 @@ typedef struct GroupingFunc
 	Expr		xpr;
 	List	   *args;			/* arguments, not evaluated but kept for
 								 * benefit of EXPLAIN etc. */
-	List	   *refs;			/* ressortgrouprefs of arguments */
-	List	   *cols;			/* actual column positions set by planner */
+	List	   *refs pg_node_attr(equal_ignore);			/* ressortgrouprefs of arguments */
+	List	   *cols pg_node_attr(equal_ignore);			/* actual column positions set by planner */
 	Index		agglevelsup;	/* same as Aggref.agglevelsup */
 	int			location;		/* token location */
 } GroupingFunc;
@@ -549,7 +558,7 @@ typedef struct OpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of underlying function */
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of underlying function */
 	Oid			opresulttype;	/* PG_TYPE OID of result value */
 	bool		opretset;		/* true if operator returns set */
 	Oid			opcollid;		/* OID of collation of result */
@@ -601,14 +610,18 @@ typedef OpExpr NullIfExpr;
  * corresponding function and won't be used during execution.  For
  * non-hashtable based NOT INs, negfuncid will be set to InvalidOid.  See
  * convert_saop_to_hashed_saop().
+ *
+ * Similar to OpExpr, opfuncid, hashfuncid, and negfuncid are not necessarily
+ * filled in right away, so will be ignored for equality if they are not set
+ * yet.
  */
 typedef struct ScalarArrayOpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of comparison function */
-	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
-	Oid			negfuncid;		/* PG_PROC OID of negator of opfuncid function
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of comparison function */
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of hash func or InvalidOid */
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of negator of opfuncid function
 								 * or InvalidOid.  See above */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index eadbd00904..81225a6e4a 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -274,9 +274,9 @@ typedef struct ForeignKeyCacheInfo
 	Oid			confrelid;		/* relation referenced by the foreign key */
 	int			nkeys;			/* number of columns in the foreign key */
 	/* these arrays each have nkeys valid entries: */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 } ForeignKeyCacheInfo;
 
 
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index cb2ad6cd29..80a9911720 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -843,6 +843,52 @@ EOF
 		close($chs);
 	}
 
+	if (IsNewer('src/backend/nodes/node-support-stamp',
+		'src/backend/nodes/gen_node_support.pl'))
+	{
+		# XXX duplicates src/backend/nodes/Makefile
+
+		my @node_headers = qw(
+			nodes/nodes.h
+			nodes/execnodes.h
+			nodes/plannodes.h
+			nodes/primnodes.h
+			nodes/pathnodes.h
+			nodes/extensible.h
+			nodes/parsenodes.h
+			nodes/replnodes.h
+			nodes/value.h
+			commands/trigger.h
+			commands/event_trigger.h
+			foreign/fdwapi.h
+			access/amapi.h
+			access/tableam.h
+			access/tsmapi.h
+			utils/rel.h
+			nodes/supportnodes.h
+			executor/tuptable.h
+			nodes/lockoptions.h
+			access/sdir.h
+		);
+
+		chdir('src/backend/nodes');
+
+		my @node_files = map { "../../../src/include/$_" } @node_headers;
+
+		system("perl gen_node_support.pl @node_files");
+		open(my $f, '>', 'node-support-stamp') || confess "Could not touch node-support-stamp";
+		close($f);
+		chdir('../../..');
+	}
+
+	if (IsNewer(
+			'src/include/nodes/nodetags.h',
+			'src/backend/nodes/nodetags.h'))
+	{
+		copyFile('src/backend/nodes/nodetags.h',
+			'src/include/nodes/nodetags.h');
+	}
+
 	open(my $o, '>', "doc/src/sgml/version.sgml")
 	  || croak "Could not write to version.sgml\n";
 	print $o <<EOF;
-- 
2.30.2

