v1-0009-Add-script-to-generate-node-support-functions.patch

text/plain

Filename: v1-0009-Add-script-to-generate-node-support-functions.patch
Type: text/plain
Part: 8
Message: automatically generating node support functions

Patch

Format: format-patch
Series: patch v1-0009
Subject: Add script to generate node support functions
File+
src/backend/Makefile 6 2
src/backend/nodes/copyfuncs.c 15 0
src/backend/nodes/equalfuncs.c 16 3
src/backend/nodes/gen_node_stuff.pl 641 0
src/backend/nodes/.gitignore 3 0
src/backend/nodes/Makefile 55 0
src/backend/nodes/outfuncs.c 25 0
src/backend/nodes/readfuncs.c 18 1
src/include/nodes/.gitignore 2 0
src/include/nodes/nodes.h 8 0
src/include/nodes/parsenodes.h 1 1
src/include/nodes/pathnodes.h 58 58
src/include/nodes/primnodes.h 9 9
From 79da68dba05e3ea3b506c4ebf6ea1a1c1e2de6f3 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 16:08:54 +0200
Subject: [PATCH v1 09/10] Add script to generate node support functions

---
 src/backend/Makefile                |   8 +-
 src/backend/nodes/.gitignore        |   3 +
 src/backend/nodes/Makefile          |  55 +++
 src/backend/nodes/copyfuncs.c       |  15 +
 src/backend/nodes/equalfuncs.c      |  19 +-
 src/backend/nodes/gen_node_stuff.pl | 641 ++++++++++++++++++++++++++++
 src/backend/nodes/outfuncs.c        |  25 ++
 src/backend/nodes/readfuncs.c       |  19 +-
 src/include/nodes/.gitignore        |   2 +
 src/include/nodes/nodes.h           |   8 +
 src/include/nodes/parsenodes.h      |   2 +-
 src/include/nodes/pathnodes.h       | 116 ++---
 src/include/nodes/primnodes.h       |  18 +-
 13 files changed, 857 insertions(+), 74 deletions(-)
 create mode 100644 src/backend/nodes/.gitignore
 create mode 100644 src/backend/nodes/gen_node_stuff.pl
 create mode 100644 src/include/nodes/.gitignore

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 0da848b1fd..a33db1ae01 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..232c6e1817
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,3 @@
+/node-stuff-stamp
+/nodetags.h
+/*funcs.inc?.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..eb93166e9e 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,58 @@ 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/memnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.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
+
+node_sources = \
+	executor/nodeWindowAgg.c \
+	nodes/tidbitmap.c \
+	utils/mmgr/aset.c \
+	utils/mmgr/generation.c \
+	utils/mmgr/slab.c
+
+node_files = $(addprefix $(top_srcdir)/src/include/,$(node_headers)) $(addprefix $(top_srcdir)/src/backend/,$(node_sources))
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-stuff-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+node-stuff-stamp: gen_node_stuff.pl
+	$(PERL) $< $(node_files)
+	touch $@
+
+$(top_builddir)/src/include/nodes/header-stamp: node-stuff-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-stuff-stamp *funcs.inc?.c nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index dde2d47338..4f57923e9e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -69,6 +69,9 @@
 	(newnode->fldname = from->fldname)
 
 
+#include "copyfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /* ****************************************************************
  *					 plannodes.h copy functions
  * ****************************************************************
@@ -1450,6 +1453,7 @@ _copyVar(const Var *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * _copyConst
@@ -1489,6 +1493,7 @@ _copyConst(const Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -2722,6 +2727,7 @@ _copyParamRef(const ParamRef *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -2754,6 +2760,7 @@ _copyA_Const(const A_Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -4863,6 +4870,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *					extensible.h copy functions
@@ -4919,6 +4927,7 @@ _copyValue(const Value *from)
 }
 
 
+#ifdef OBSOLETE
 static ForeignKeyCacheInfo *
 _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 {
@@ -4935,6 +4944,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 
 /*
@@ -4956,6 +4966,8 @@ copyObjectImpl(const void *from)
 
 	switch (nodeTag(from))
 	{
+#include "copyfuncs.inc2.c"
+#ifdef OBSOLETE
 			/*
 			 * PLAN NODES
 			 */
@@ -5297,6 +5309,7 @@ copyObjectImpl(const void *from)
 		case T_PlaceHolderInfo:
 			retval = _copyPlaceHolderInfo(from);
 			break;
+#endif /*OBSOLETE*/
 
 			/*
 			 * VALUE NODES
@@ -5325,6 +5338,7 @@ copyObjectImpl(const void *from)
 			retval = list_copy(from);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -5858,6 +5872,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 e04ec41904..6757337062 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.
@@ -33,6 +30,7 @@
 #include "nodes/extensible.h"
 #include "nodes/pathnodes.h"
 #include "utils/datum.h"
+#include "utils/rel.h"
 
 
 /*
@@ -90,6 +88,9 @@
 	((void) 0)
 
 
+#include "equalfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from primnodes.h
  */
@@ -178,6 +179,7 @@ _equalVar(const Var *a, const Var *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -200,6 +202,7 @@ _equalConst(const Const *a, const Const *b)
 						a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -933,6 +936,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -954,6 +958,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const ExtensibleNode *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2397,6 +2402,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2408,6 +2414,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3016,6 +3023,7 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3135,6 +3143,8 @@ equal(const void *a, const void *b)
 
 	switch (nodeTag(a))
 	{
+#include "equalfuncs.inc2.c"
+#ifdef OBSOLETE
 			/*
 			 * PRIMITIVE NODES
 			 */
@@ -3313,6 +3323,7 @@ equal(const void *a, const void *b)
 		case T_PlaceHolderInfo:
 			retval = _equalPlaceHolderInfo(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		case T_List:
 		case T_IntList:
@@ -3328,6 +3339,7 @@ equal(const void *a, const void *b)
 			retval = _equalValue(a, b);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -3854,6 +3866,7 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/gen_node_stuff.pl b/src/backend/nodes/gen_node_stuff.pl
new file mode 100644
index 0000000000..aafe320e0a
--- /dev/null
+++ b/src/backend/nodes/gen_node_stuff.pl
@@ -0,0 +1,641 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# src/backend/nodes/gen_node_stuff.pl
+#
+#----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use experimental 'smartmatch';
+
+use File::Basename;
+
+use FindBin;
+use lib "$FindBin::RealBin/../catalog";
+
+use Catalog;  # for RenameTempFile
+
+
+my @node_types = qw(Node);
+my %node_type_info;
+
+my @no_copy;
+my @no_read_write;
+
+my @scalar_types = qw(
+	bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+	AclMode AttrNumber Cost Index Oid Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr
+);
+
+my @enum_types;
+
+# For abstract 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
+	BufferHeapTupleTableSlot HeapTupleTableSlot MinimalTupleTableSlot VirtualTupleTableSlot
+	JoinPath
+	MemoryContextData
+	PartitionPruneStep
+);
+
+# These are additional node tags that don't have their own struct.
+my @extra_tags = qw(IntList OidList Integer Float String BitString Null);
+
+# These are regular nodes, but we skip parsing them from their header
+# files since we won't use their internal structure here anyway.
+push @node_types, qw(List Value);
+
+# XXX maybe this should be abstract?
+push @no_copy, qw(Expr);
+push @no_read_write, qw(Expr);
+
+# pathnodes.h exceptions
+push @no_copy, qw(
+	RelOptInfo IndexOptInfo Path PlannerGlobal EquivalenceClass EquivalenceMember ForeignKeyOptInfo
+	GroupingSetData IncrementalSortPath IndexClause MinMaxAggInfo PathTarget PlannerInfo PlannerParamItem
+	ParamPathInfo RollupData RowIdentityVarInfo StatisticExtInfo
+);
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember* QualCost);
+
+# 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\": $!";
+
+	while (my $line = <$ifh>)
+	{
+		chomp $line;
+		$line =~ s!/\*.*$!!;
+		$line =~ s/\s*$//;
+		next if $line eq '';
+		next if $line =~ m!^\s*\*.*$!;  # line starts with *, probably comment continuation
+		next if $line =~ /^#(define|ifdef|endif)/;
+
+		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+);/ && $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)
+				{
+					push @node_types, $in_struct;
+					my @f = @my_fields;
+					my %ft = %my_field_types;
+					my %fa = %my_field_attrs;
+					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};
+						}
+						unshift @f, @superfields;
+					}
+					$node_type_info{$in_struct}->{fields} = \@f;
+					$node_type_info{$in_struct}->{field_types} = \%ft;
+					$node_type_info{$in_struct}->{field_attrs} = \%fa;
+
+					if (basename($infile) eq 'execnodes.h' ||
+						basename($infile) eq 'trigger.h' ||
+						basename($infile) eq 'event_trigger.h' ||
+						basename($infile) eq 'amapi.h' ||
+						basename($infile) eq 'tableam.h' ||
+						basename($infile) eq 'tsmapi.h' ||
+						basename($infile) eq 'fdwapi.h' ||
+						basename($infile) eq 'tuptable.h' ||
+						basename($infile) eq 'replnodes.h' ||
+						basename($infile) eq 'supportnodes.h' ||
+						$infile =~ /\.c$/
+					)
+					{
+						push @no_copy, $in_struct;
+						push @no_read_write, $in_struct;
+					}
+
+					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;
+
+					$type =~ s/^const\s*//;
+					$type =~ s/\s*$//;
+					$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+);$/ && $1 ~~ @node_types)
+			{
+				my $alias_of = $1;
+				my $n = $2;
+
+				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 $n ~~ @abstract_types;
+	print $nt "\tT_${n} = $i,\n";
+	$i++;
+}
+
+close $nt;
+
+
+# copyfuncs.c, equalfuncs.c
+
+open my $cf, '>', 'copyfuncs.inc1.c' . $tmpext or die $!;
+open my $ef, '>', 'equalfuncs.inc1.c' . $tmpext or die $!;
+open my $cf2, '>', 'copyfuncs.inc2.c' . $tmpext or die $!;
+open my $ef2, '>', 'equalfuncs.inc2.c' . $tmpext or die $!;
+
+my @custom_copy = qw(A_Const Const ExtensibleNode);
+
+foreach my $n (@node_types)
+{
+	next if $n ~~ @abstract_types;
+	next if $n ~~ @no_copy;
+	next if $n eq 'List';
+	next if $n eq 'Value';
+
+	print $cf2 "
+\t\tcase T_${n}:
+\t\t\tretval = _copy${n}(from);
+\t\t\tbreak;";
+
+	print $ef2 "
+\t\tcase T_${n}:
+\t\t\tretval = _equal${n}(a, b);
+\t\t\tbreak;";
+
+	next if $n ~~ @custom_copy;
+
+	print $cf "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+";
+
+	print $ef "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+";
+
+	my $last_array_size_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/);
+		if ($t eq 'char*')
+		{
+			print $cf "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_STRING_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $cf "\tCOPY_BITMAPSET_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_BITMAPSET_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $cf "\tCOPY_LOCATION_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_LOCATION_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t ~~ @scalar_types || $t ~~ @enum_types)
+		{
+			print $cf "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			if ($a =~ /\bequal_ignore_if_zero\b/)
+			{
+				print $ef "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
+			}
+			else
+			{
+				print $ef "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+			}
+			$last_array_size_field = "from->$f";
+		}
+		elsif ($t =~ /(\w+)\*/ && $1 ~~ @scalar_types)
+		{
+			my $tt = $1;
+			print $cf "\tCOPY_POINTER_FIELD($f, $last_array_size_field * sizeof($tt));\n" unless $copy_ignore;
+			(my $l2 = $last_array_size_field) =~ s/from/a/;
+			print $ef "\tCOMPARE_POINTER_FIELD($f, $l2 * sizeof($tt));\n" unless $equal_ignore;
+		}
+		elsif ($t =~ /(\w+)\*/ && $1 ~~ @node_types)
+		{
+			print $cf "\tCOPY_NODE_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
+			$last_array_size_field = "list_length(from->$f)" if $t eq 'List*';
+		}
+		elsif ($t =~ /\w+\[/)
+		{
+			# COPY_SCALAR_FIELD might work for these, but let's not assume that
+			print $cf "\tmemcpy(newnode->$f, from->$f, sizeof(newnode->$f));\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_POINTER_FIELD($f, sizeof(a->$f));\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $cf "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $cf "
+\treturn newnode;
+}
+";
+	print $ef "
+\treturn true;
+}
+";
+}
+
+close $cf;
+close $ef;
+close $cf2;
+close $ef2;
+
+
+# outfuncs.c, readfuncs.c
+
+open my $of, '>', 'outfuncs.inc1.c' . $tmpext or die $!;
+open my $rf, '>', 'readfuncs.inc1.c' . $tmpext or die $!;
+open my $of2, '>', 'outfuncs.inc2.c' . $tmpext or die $!;
+open my $rf2, '>', 'readfuncs.inc2.c' . $tmpext or die $!;
+
+my %name_map = (
+	'ARRAYEXPR' => 'ARRAY',
+	'CASEEXPR' => 'CASE',
+	'CASEWHEN' => 'WHEN',
+	'COALESCEEXPR' => 'COALESCE',
+	'COLLATEEXPR' => 'COLLATE',
+	'DECLARECURSORSTMT' => 'DECLARECURSOR',
+	'MINMAXEXPR' => 'MINMAX',
+	'NOTIFYSTMT' => 'NOTIFY',
+	'RANGETBLENTRY' => 'RTE',
+	'ROWCOMPAREEXPR' => 'ROWCOMPARE',
+	'ROWEXPR' => 'ROW',
+);
+
+my @custom_readwrite = qw(A_Const A_Expr BoolExpr Const Constraint ExtensibleNode Query RangeTblEntry);
+
+foreach my $n (@node_types)
+{
+	next if $n ~~ @abstract_types;
+	next if $n ~~ @no_read_write;
+	next if $n eq 'List';
+	next if $n eq 'Value';
+
+	# 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 $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 'ForeignKeyCacheInfo' || $n eq 'ForeignKeyOptInfo' || $n eq 'PathTarget');
+
+	my $N = uc $n;
+	$N =~ s/_//g;
+	$N = $name_map{$N} if $name_map{$N};
+
+	print $of2 "\t\t\tcase T_${n}:\n".
+	  "\t\t\t\t_out${n}(str, obj);\n".
+	  "\t\t\t\tbreak;\n";
+
+	print $rf2 "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+
+	next if $n ~~ @custom_readwrite;
+
+	print $of "
+static void
+_out${n}(StringInfo str, const $n *node)
+{
+\tWRITE_NODE_TYPE(\"$N\");
+
+";
+
+	print $rf "
+static $n *
+_read${n}(void)
+{
+\tREAD_LOCALS($n);
+
+" unless $no_read;
+
+	my $last_array_size_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.
+
+		if ($t eq 'bool')
+		{
+			print $of "\tWRITE_BOOL_FIELD($f);\n";
+			print $rf "\tREAD_BOOL_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $of "\tWRITE_LOCATION_FIELD($f);\n";
+			print $rf "\tREAD_LOCATION_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' || $t eq 'int32' || $t eq 'AttrNumber' || $t eq 'StrategyNumber')
+		{
+			print $of "\tWRITE_INT_FIELD($f);\n";
+			print $rf "\tREAD_INT_FIELD($f);\n" unless $no_read;
+			$last_array_size_field = "node->$f" if $t eq 'int';
+		}
+		elsif ($t eq 'uint32' || $t eq 'bits32' || $t eq 'AclMode' || $t eq 'BlockNumber' || $t eq 'Index' || $t eq 'SubTransactionId')
+		{
+			print $of "\tWRITE_UINT_FIELD($f);\n";
+			print $rf "\tREAD_UINT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint64')
+		{
+			print $of "\tWRITE_UINT64_FIELD($f);\n";
+			print $rf "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Oid')
+		{
+			print $of "\tWRITE_OID_FIELD($f);\n";
+			print $rf "\tREAD_OID_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'long')
+		{
+			print $of "\tWRITE_LONG_FIELD($f);\n";
+			print $rf "\tREAD_LONG_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char')
+		{
+			print $of "\tWRITE_CHAR_FIELD($f);\n";
+			print $rf "\tREAD_CHAR_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'double')
+		{
+			# XXX We out to split these into separate types, like Cost
+			# etc.
+			if ($f eq 'allvisfrac')
+			{
+				print $of "\tWRITE_FLOAT_FIELD($f, \"%.6f\");\n";
+			}
+			else
+			{
+				print $of "\tWRITE_FLOAT_FIELD($f, \"%.0f\");\n";
+			}
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cost')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.2f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'QualCost')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f.startup, \"%.2f\");\n";
+			print $of "\tWRITE_FLOAT_FIELD($f.per_tuple, \"%.2f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read;
+			print $rf "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
+		}
+		elsif ($t eq 'Selectivity')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.4f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $of "\tWRITE_STRING_FIELD($f);\n";
+			print $rf "\tREAD_STRING_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $of "\tWRITE_BITMAPSET_FIELD($f);\n";
+			print $rf "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t ~~ @enum_types)
+		{
+			print $of "\tWRITE_ENUM_FIELD($f, $t);\n";
+			print $rf "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read;
+		}
+		elsif ($t =~ /(\w+)(\*|\[)/ && $1 ~~ @scalar_types)
+		{
+			warn "$t $n.$f" unless $last_array_size_field;
+			my $tt = uc $1;
+			print $of "\tWRITE_${tt}_ARRAY($f, $last_array_size_field);\n";
+			(my $l2 = $last_array_size_field) =~ s/node/local_node/;
+			print $rf "\tREAD_${tt}_ARRAY($f, $l2);\n" unless $no_read;
+		}
+		elsif ($t eq 'RelOptInfo*' && $a eq 'path_hack1')
+		{
+			print $of "\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 $of "\tif (node->$f != node->$f2->reltarget)\n".
+			  "\t\tWRITE_NODE_FIELD($f);\n";
+		}
+		elsif ($t eq 'ParamPathInfo*' && $a eq 'path_hack3')
+		{
+			print $of "\tif (node->$f)\n".
+			  "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
+			  "\telse\n".
+			  "\t\toutBitmapset(str, NULL);\n";
+		}
+		elsif ($t =~ /(\w+)\*/ && $1 ~~ @node_types)
+		{
+			print $of "\tWRITE_NODE_FIELD($f);\n";
+			print $rf "\tREAD_NODE_FIELD($f);\n" unless $no_read;
+			$last_array_size_field = "list_length(node->$f)" if $t eq 'List*';
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $of q{
+	appendStringInfoString(str, " :methods ");
+	outToken(str, node->methods->CustomName);
+};
+			print $rf 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;
+		}
+		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 $of "}
+";
+	print $rf "
+\tREAD_DONE();
+}
+" unless $no_read;
+}
+
+close $of;
+close $rf;
+close $of2;
+close $rf2;
+
+
+foreach my $file (qw(nodetags.h copyfuncs.inc1.c copyfuncs.inc2.c equalfuncs.inc1.c equalfuncs.inc2.c outfuncs.inc1.c outfuncs.inc2.c readfuncs.inc1.c readfuncs.inc2.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ed08f5acf4..0978bc4e81 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -124,6 +124,8 @@ static void outChar(StringInfo str, char c);
 			appendStringInfo(str, " %u", node->fldname[i]); \
 	} while(0)
 
+#define WRITE_INDEX_ARRAY(fldname, len) WRITE_OID_ARRAY(fldname, len)
+
 #define WRITE_INT_ARRAY(fldname, len) \
 	do { \
 		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
@@ -288,6 +290,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
 }
 
 
+#include "outfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from plannodes.h
  */
@@ -1126,6 +1131,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)
@@ -1147,6 +1153,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)
 {
@@ -1316,6 +1323,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)
@@ -1344,6 +1352,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2658,6 +2667,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
 	WRITE_NODE_FIELD(item);
 	WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2680,6 +2690,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -3012,6 +3023,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)
@@ -3084,6 +3096,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3222,6 +3235,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)
@@ -3302,6 +3316,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3325,6 +3340,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)
@@ -3442,6 +3458,7 @@ _outValue(StringInfo str, const Value *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3473,6 +3490,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)
@@ -3484,6 +3502,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)
 {
@@ -3628,6 +3647,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)
@@ -3748,6 +3768,7 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -3808,6 +3829,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -3836,6 +3858,8 @@ outNode(StringInfo str, const void *obj)
 		appendStringInfoChar(str, '{');
 		switch (nodeTag(obj))
 		{
+#include "outfuncs.inc2.c"
+#ifdef OBSOLETE
 			case T_PlannedStmt:
 				_outPlannedStmt(str, obj);
 				break;
@@ -4505,6 +4529,7 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionRangeDatum:
 				_outPartitionRangeDatum(str, obj);
 				break;
+#endif /*OBSOLETE*/
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index fef4de94e6..b4b9379d9d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -240,6 +240,8 @@ readBitmapset(void)
 	return _readBitmapset();
 }
 
+#include "readfuncs.inc1.c"
+
 /*
  * _readQuery
  */
@@ -291,6 +293,7 @@ _readQuery(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readNotifyStmt
  */
@@ -589,6 +592,7 @@ _readVar(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readConst
@@ -615,6 +619,7 @@ _readConst(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readParam
  */
@@ -839,6 +844,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readBoolExpr
@@ -866,6 +872,7 @@ _readBoolExpr(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readSubLink
  */
@@ -1420,6 +1427,7 @@ _readAppendRelInfo(void)
 /*
  *	Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1515,6 +1523,7 @@ _readRangeTblEntry(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2635,6 +2644,7 @@ _readAlternativeSubPlan(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2666,6 +2676,7 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2700,6 +2711,7 @@ _readPartitionRangeDatum(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2724,7 +2736,11 @@ parseNodeString(void)
 #define MATCH(tokname, namelen) \
 	(length == namelen && memcmp(token, tokname, namelen) == 0)
 
-	if (MATCH("QUERY", 5))
+	if (false)
+		;
+#include "readfuncs.inc2.c"
+#ifdef OBSOLETE
+	else if (MATCH("QUERY", 5))
 		return_value = _readQuery();
 	else if (MATCH("WITHCHECKOPTION", 15))
 		return_value = _readWithCheckOption();
@@ -2972,6 +2988,7 @@ parseNodeString(void)
 		return_value = _readPartitionBoundSpec();
 	else if (MATCH("PARTITIONRANGEDATUM", 19))
 		return_value = _readPartitionRangeDatum();
+#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 5e049a67e1..cb140a3b93 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)
 	 */
@@ -527,8 +529,14 @@ typedef enum NodeTag
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * used in node definitions to set extra information for gen_node_stuff.pl
+ */
+#define pg_node_attr(x)
+
 /*
  * 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 cd9115dbb5..b35d1414de 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -121,7 +121,7 @@ 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) */
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 03c80c1620..b98d1958fc 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -226,7 +226,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]
@@ -331,7 +331,7 @@ struct PlannerInfo
 	AttrNumber *grouping_map;	/* 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 */
 
 	double		total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -706,8 +706,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 */
@@ -728,13 +728,13 @@ 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;
+	struct FdwRoutine *fdwroutine pg_node_attr(readwrite_ignore);
 	void	   *fdw_private;
 
 	/* cache space for remembering if we have proven this relation unique */
-	List	   *unique_for_rels;	/* known unique for these other relid
+	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) */
@@ -829,7 +829,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 */
 
 	/* index-size statistics (from pg_class and elsewhere) */
 	BlockNumber pages;			/* number of disk pages in index */
@@ -839,20 +839,20 @@ 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
+	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 */
+	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 */
@@ -869,14 +869,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 */
 };
@@ -926,7 +926,7 @@ typedef struct StatisticExtInfo
 	NodeTag		type;
 
 	Oid			statOid;		/* OID of the statistics row */
-	RelOptInfo *rel;			/* back-link to statistic's table */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to statistic's table */
 	char		kind;			/* statistics kind of this entry */
 	Bitmapset  *keys;			/* attnums of the columns covered */
 	List	   *exprs;			/* expressions */
@@ -1171,10 +1171,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? */
@@ -2051,19 +2051,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;
@@ -2075,47 +2075,47 @@ 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	   *scansel_cache pg_node_attr(copy_ignore equal_ignore);	/* list of MergeScanSelCache structs */
 
 	/* 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 operator used for result cache, else InvalidOid */
-	Oid			hasheqoperator;
+	Oid			hasheqoperator pg_node_attr(equal_ignore);
 } RestrictInfo;
 
 /*
@@ -2170,8 +2170,8 @@ typedef struct MergeScanSelCache
 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;
@@ -2420,7 +2420,7 @@ 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 */
 	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/primnodes.h b/src/include/nodes/primnodes.h
index 3418e23873..62f6413684 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -63,7 +63,7 @@ typedef enum OnCommitAction
 typedef struct RangeVar
 {
 	NodeTag		type;
-	char	   *catalogname;	/* the catalog (database) name, or NULL */
+	char	   *catalogname pg_node_attr(readwrite_ignore);	/* the catalog (database) name, or NULL */
 	char	   *schemaname;		/* the schema name, or NULL */
 	char	   *relname;		/* the relation/sequence name */
 	bool		inh;			/* expand rel by inheritance? recursively act
@@ -196,8 +196,8 @@ 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 */
+	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;
 
@@ -324,7 +324,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 */
 	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 */
@@ -371,8 +371,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;
@@ -540,7 +540,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 */
@@ -589,8 +589,8 @@ 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			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 */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
 	List	   *args;			/* the scalar and array operands */
-- 
2.31.1