The new patch addresses almost all of these issues.

> Also, I share David's upthread allergy to the option names
> "path_hackN" and to documenting those only inside the conversion
> script.

I have given these real names now and documented them with the other attributes.

> BTW, I think this: "Unknown attributes are ignored" is a seriously
> bad idea; it will allow typos to escape detection.

fixed

(I have also changed the inside of pg_node_attr to be comma-separated, rather than space-separated. This matches better how attribute-type things look in C.)

I think we ought to put it into the *nodes.h headers as much as
possible, perhaps like this:

typedef struct A_Const pg_node_attr(custom_copy)
{ ...

done

So I propose that we handle these things via struct-level pg_node_attr
markers, rather than node-type lists embedded in the script:

abstract_types
no_copy
no_read_write
no_read
custom_copy
custom_readwrite

done (no_copy is actually no_copy_equal, hence renamed)

The hacks for scalar-copying EquivalenceClass*, EquivalenceMember*,
struct CustomPathMethods*, and CustomScan.methods should be replaced
with "pg_node_attr(copy_as_scalar)" labels on affected fields.

Hmm, at least for Equivalence..., this is repeated a bunch of times for each field. I don't know if this is really a property of the type or something you can choose for each field? [not changed in v7 patch]

I wonder whether this:

                     # 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.

couldn't be done better by inventing an inheritable no_copy attr
to attach to the Path supertype.  Or maybe it'd be okay to just
automatically inherit the no_xxx properties from the supertype?

This is an existing comment in copyfuncs.c. I haven't looked into it any further.

I don't terribly like the ad-hoc mechanism for not comparing
CoercionForm fields.  OTOH, I am not sure whether replacing it
with per-field equal_ignore attrs would be better; there's at least
an argument that that invites bugs of omission.  But implementing
this with an uncommented test deep inside a script that most hackers
should not need to read is not good.  On the whole I'd lean towards
the equal_ignore route.

The definition of CoercionForm in primnodes.h says that the comparison behavior is a property of the type, so it needs to be handled somewhere centrally, not on each field. [not changed in v7 patch]

I'm confused by the "various field types to ignore" at the end
of the outfuncs/readfuncs code.  Do we really ignore those now?
How could that be safe?  If it is safe, wouldn't it be better
to handle that with per-field pg_node_attrs?  Silently doing
what might be the wrong thing doesn't seem good.

I have replaced these with explicit ignore markings in pathnodes.h (PlannerGlobal, PlannerInfo, RelOptInfo). (This could then use a bit more rearranging some of the per-field comments.)

* copyfuncs.switch.c and equalfuncs.switch.c are missing trailing
newlines.

fixed

* pgindent is not very happy with a lot of your comments in *nodes.h.

fixed

* I think we should add explicit dependencies in backend/nodes/Makefile,
along the lines of

copyfuncs.o: copyfuncs.c copyfuncs.funcs.c copyfuncs.switch.c

Otherwise the whole thing is a big gotcha for anyone not using
--enable-depend.

fixed -- I think, could use more testing
From c82ee081a7a8cdc77b44f325d1df695b55a60b06 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Wed, 6 Jul 2022 12:13:32 +0200
Subject: [PATCH v7] 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                  |  10 +-
 src/backend/nodes/.gitignore          |   4 +
 src/backend/nodes/Makefile            |  59 ++
 src/backend/nodes/copyfuncs.c         |  19 +-
 src/backend/nodes/equalfuncs.c        |  22 +-
 src/backend/nodes/gen_node_support.pl | 770 ++++++++++++++++++++++++++
 src/backend/nodes/outfuncs.c          |  34 +-
 src/backend/nodes/readfuncs.c         |  23 +-
 src/include/Makefile                  |   1 +
 src/include/executor/tuptable.h       |   8 +-
 src/include/nodes/.gitignore          |   2 +
 src/include/nodes/extensible.h        |   2 +-
 src/include/nodes/nodes.h             |  52 ++
 src/include/nodes/parsenodes.h        |  19 +-
 src/include/nodes/pathnodes.h         | 315 +++++++----
 src/include/nodes/plannodes.h         |  92 +--
 src/include/nodes/primnodes.h         |  45 +-
 src/include/nodes/value.h             |  10 +-
 src/include/utils/rel.h               |  11 +-
 src/tools/msvc/Solution.pm            |  46 ++
 20 files changed, 1320 insertions(+), 224 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..953c80db5a 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` && \
@@ -185,6 +189,7 @@ distprep:
        $(MAKE) -C parser       gram.c gram.h scan.c
        $(MAKE) -C bootstrap    bootparse.c bootscanner.c
        $(MAKE) -C catalog      distprep
+       $(MAKE) -C nodes        distprep
        $(MAKE) -C replication  repl_gram.c repl_scanner.c syncrep_gram.c 
syncrep_scanner.c
        $(MAKE) -C storage/lmgr lwlocknames.h lwlocknames.c
        $(MAKE) -C utils        distprep
@@ -297,6 +302,7 @@ distclean: clean
 
 maintainer-clean: distclean
        $(MAKE) -C catalog $@
+       $(MAKE) -C nodes $@
        $(MAKE) -C utils $@
        rm -f bootstrap/bootparse.c \
              bootstrap/bootscanner.c \
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..1a0d5b9314 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,62 @@ 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 records the last time we ran gen_node_support.pl.
+# We don't rely on the timestamps of the individual output files,
+# because the Perl script won't update them if they didn't change (to
+# avoid unnecessary recompiles).
+node-support-stamp: gen_node_support.pl $(addprefix 
$(top_srcdir)/src/include/,$(node_headers))
+       $(PERL) $^
+       touch $@
+
+# These generated headers must be symlinked into builddir/src/include/,
+# using absolute links for the reasons explained in src/backend/Makefile.
+# We use header-stamp to record that we've done this because the symlinks
+# themselves may appear older than node-support-stamp.
+$(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 $@
+
+copyfuncs.o: copyfuncs.c copyfuncs.funcs.c copyfuncs.switch.c | 
node-support-stamp
+equalfuncs.o: equalfuncs.c equalfuncs.funcs.c equalfuncs.switch.c | 
node-support-stamp
+outfuncs.o: outfuncs.c outfuncs.funcs.c outfuncs.switch.c | node-support-stamp
+readfuncs.o:  readfuncs.c readfuncs.funcs.c readfuncs.switch.c | 
node-support-stamp
+
+maintainer-clean: clean
+       rm -f node-support-stamp $(addsuffix funcs.funcs.c,copy equal out read) 
$(addsuffix funcs.switch.c,copy equal out read) nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 706d283a92..48778aa4ef 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
  */
@@ -3248,6 +3249,7 @@ _copyParamRef(const ParamRef *from)
 
        return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -3288,6 +3290,7 @@ _copyA_Const(const A_Const *from)
        return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -5453,6 +5456,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt 
*from)
 
        return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *                                     extensible.h copy functions
@@ -5475,6 +5479,7 @@ _copyExtensibleNode(const ExtensibleNode *from)
        return newnode;
 }
 
+#ifdef OBSOLETE
 /* ****************************************************************
  *                                     value.h copy functions
  * ****************************************************************
@@ -5545,6 +5550,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
        return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5565,6 +5571,8 @@ copyObjectImpl(const void *from)
 
        switch (nodeTag(from))
        {
+#include "copyfuncs.switch.c"
+#ifdef OBSOLETE
                        /*
                         * PLAN NODES
                         */
@@ -6009,6 +6017,7 @@ copyObjectImpl(const void *from)
                case T_BitString:
                        retval = _copyBitString(from);
                        break;
+#endif /*OBSOLETE*/
 
                        /*
                         * LIST NODES
@@ -6026,6 +6035,7 @@ copyObjectImpl(const void *from)
                        retval = list_copy(from);
                        break;
 
+#ifdef OBSOLETE
                        /*
                         * EXTENSIBLE NODES
                         */
@@ -6577,6 +6587,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 fccc0b4a18..7f09ccd978 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
  */
@@ -258,6 +256,7 @@ _equalVar(const Var *a, const Var *b)
 
        return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -280,6 +279,7 @@ _equalConst(const Const *a, const Const *b)
                                                a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -1304,6 +1304,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const 
PlaceHolderInfo *b)
 
        return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -1325,6 +1326,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const 
ExtensibleNode *b)
        return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2815,6 +2817,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
        return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2831,6 +2834,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
        return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3468,6 +3472,7 @@ _equalPartitionCmd(const PartitionCmd *a, const 
PartitionCmd *b)
 
        return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3528,6 +3533,7 @@ _equalList(const List *a, const List *b)
        return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from value.h
  */
@@ -3571,6 +3577,7 @@ _equalBitString(const BitString *a, const BitString *b)
 
        return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * equal
@@ -3601,6 +3608,8 @@ equal(const void *a, const void *b)
 
        switch (nodeTag(a))
        {
+#include "equalfuncs.switch.c"
+#ifdef OBSOLETE
                        /*
                         * PRIMITIVE NODES
                         */
@@ -3821,6 +3830,7 @@ equal(const void *a, const void *b)
                case T_PlaceHolderInfo:
                        retval = _equalPlaceHolderInfo(a, b);
                        break;
+#endif /*OBSOLETE*/
 
                case T_List:
                case T_IntList:
@@ -3828,6 +3838,7 @@ equal(const void *a, const void *b)
                        retval = _equalList(a, b);
                        break;
 
+#ifdef OBSOLETE
                case T_Integer:
                        retval = _equalInteger(a, b);
                        break;
@@ -4430,6 +4441,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..6aaf401a72
--- /dev/null
+++ b/src/backend/nodes/gen_node_support.pl
@@ -0,0 +1,770 @@
+#!/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/equal support for
+my @no_copy_equal;
+# node types we don't want read support for
+my @no_read;
+# 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;
+
+my @abstract_types = qw(Node);
+
+# 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 XidList
+       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);
+# See special treatment in outNode() and nodeRead().
+push @no_read_write, qw(List);
+
+# Nodes with custom copy/equal implementations are skipped from
+# .funcs.c but need case statements in .switch.c.
+my @custom_copy_equal;
+
+# Similarly for custom read/write implementations.
+my @custom_read_write;
+
+# EquivalenceClasses are never moved, so just shallow-copy the pointer
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember*);
+
+# This is a struct, so we can copy it by assignment.  Equal support is
+# currently not required.
+push @scalar_types, qw(QualCost);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy_equal, 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};
+                                                       if 
($node_type_info{$supertype}->{field_attrs}{$sf})
+                                                       {
+                                                               # Copy any 
attributes, adjusting array_size field references
+                                                               my @newa = 
@{$node_type_info{$supertype}->{field_attrs}{$sf}};
+                                                               foreach my $a 
(@newa)
+                                                               {
+                                                                       $a =~ 
s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/;
+                                                               }
+                                                               $fa{$fn} = 
\@newa;
+                                                       }
+                                               }
+                                               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_equal, $in_struct;
+                                               push @no_read_write, $in_struct;
+                                       }
+
+                                       # Propagate some node attributes from 
supertypes
+                                       if ($supertype)
+                                       {
+                                               push @no_copy_equal, $in_struct 
if elem $supertype, @no_copy_equal;
+                                               push @no_read, $in_struct if 
elem $supertype, @no_read;
+                                       }
+                               }
+
+                               # 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 $attrs = $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 '';
+
+                                       my @attrs;
+                                       if ($attrs)
+                                       {
+                                               @attrs = split /,\s*/, $attrs;
+                                               foreach my $attr (@attrs)
+                                               {
+                                                       if ($attr !~ 
/^array_size\(\w+\)$/ &&
+                                                               !elem $attr, 
qw(copy_ignore equal_ignore equal_ignore_if_zero read_write_ignore
+                                                                       
write_only_relids write_only_nondefault_pathtarget write_only_req_outer))
+                                                       {
+                                                               die 
"$infile:$.: unrecognized attribute \"$attr\"\n";
+                                                       }
+                                               }
+                                       }
+
+                                       $type = $type . $array_size if 
$array_size;
+                                       push @my_fields, $name;
+                                       $my_field_types{$name} = $type;
+                                       $my_field_attrs{$name} = \@attrs;
+                               }
+                       }
+                       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*(?:pg_node_attr\(([\w(), ]*)\))?$/ && $1 ne 'Node')
+                       {
+                               $in_struct = $1;
+                               my $node_attrs = $2 || '';
+                               $subline = 0;
+
+                               foreach my $attr (split /,\s*/, $node_attrs)
+                               {
+                                       if ($attr eq 'abstract')
+                                       {
+                                               push @abstract_types, 
$in_struct;
+                                       }
+                                       elsif ($attr eq 'custom_copy_equal')
+                                       {
+                                               push @custom_copy_equal, 
$in_struct;
+                                       }
+                                       elsif ($attr eq 'custom_read_write')
+                                       {
+                                               push @custom_read_write, 
$in_struct;
+                                       }
+                                       elsif ($attr eq 'no_copy_equal')
+                                       {
+                                               push @no_copy_equal, $in_struct;
+                                       }
+                                       elsif ($attr eq 'no_read')
+                                       {
+                                               push @no_read, $in_struct;
+                                       }
+                                       elsif ($attr eq 'special_read_write')
+                                       {
+                                               # This attribute is called
+                                               # "special_read_write" because 
there is
+                                               # special treatment in 
outNode() and
+                                               # nodeRead() for these nodes.  
For this
+                                               # script, it's the same as 
"no_read_write",
+                                               # but calling the attribute 
that externally
+                                               # would probably be confusing, 
since
+                                               # read/write support does in 
fact exist.
+                                               push @no_read_write, $in_struct;
+                                       }
+                                       else
+                                       {
+                                               die "$infile:$.: unrecognized 
attribute \"$attr\"\n";
+                                       }
+                               }
+                       }
+                       # 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;
+
+foreach my $n (@node_types)
+{
+       next if elem $n, @abstract_types;
+       next if elem $n, @no_copy_equal;
+       next if $n eq 'List';
+
+       print $cfs "\t\tcase T_${n}:\n".
+         "\t\t\tretval = _copy${n}(from);\n".
+         "\t\t\tbreak;\n";
+
+       print $efs "\t\tcase T_${n}:\n".
+         "\t\t\tretval = _equal${n}(a, b);\n".
+         "\t\t\tbreak;\n";
+
+       next if elem $n, @custom_copy_equal;
+
+       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 = (elem 'copy_ignore', @a);
+               my $equal_ignore = (elem 'equal_ignore', @a);
+
+               # 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 (elem 'equal_ignore_if_zero', @a)
+                       {
+                               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;
+                       foreach my $a (@a)
+                       {
+                               if ($a =~ /^array_size.([\w.]+)/)
+                               {
+                                       $array_size_field = $1;
+                                       last;
+                               }
+                       }
+                       if (!$array_size_field)
+                       {
+                               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;
+
+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;
+       }
+
+       my $no_read = (elem $n, @no_read);
+
+       # 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_read_write;
+
+       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} };
+               next if (elem 'read_write_ignore', @a);
+
+               # 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;
+                       foreach my $a (@a)
+                       {
+                               if ($a =~ /^array_size.([\w.]+)/)
+                               {
+                                       $array_size_field = $1;
+                                       last;
+                               }
+                       }
+                       if (!$array_size_field)
+                       {
+                               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
+               elsif ($t eq 'RelOptInfo*' && elem 'write_only_relids', @a)
+               {
+                       print $off "\tappendStringInfoString(str, \" 
:parent_relids \");\n".
+                         "\toutBitmapset(str, node->$f->relids);\n";
+               }
+               elsif ($t eq 'PathTarget*' && elem 
'write_only_nondefault_pathtarget', @a)
+               {
+                       (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*' && elem 'write_only_req_outer', 
@a)
+               {
+                       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;
+               }
+               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 4315c53080..37508af94d 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);
 
@@ -306,6 +305,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool 
typbyval)
 }
 
 
+#include "outfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /*
  *     Stuff from plannodes.h
  */
@@ -1155,6 +1157,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)
@@ -1176,6 +1179,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)
 {
@@ -1346,6 +1350,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)
@@ -1374,6 +1379,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
        WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2586,6 +2592,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)
@@ -2613,6 +2620,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)
 {
@@ -2624,6 +2632,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)
@@ -2652,6 +2661,7 @@ _outEquivalenceClass(StringInfo str, const 
EquivalenceClass *node)
        WRITE_UINT_FIELD(ec_max_security);
 }
 
+#ifdef OBSOLETE
 static void
 _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 {
@@ -2836,6 +2846,7 @@ _outPlannerParamItem(StringInfo str, const 
PlannerParamItem *node)
        WRITE_NODE_FIELD(item);
        WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2858,6 +2869,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode 
*node)
        methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *     Stuff from parsenodes.h.
@@ -3191,6 +3203,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)
@@ -3265,6 +3278,7 @@ _outQuery(StringInfo str, const Query *node)
        WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3430,6 +3444,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)
@@ -3510,6 +3525,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry 
*node)
        WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3533,6 +3549,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)
@@ -3651,6 +3668,7 @@ _outBitString(StringInfo str, const BitString *node)
        appendStringInfoString(str, node->bsval);
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3682,6 +3700,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)
@@ -3698,6 +3717,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)
 {
@@ -3842,6 +3862,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)
@@ -3964,6 +3985,7 @@ _outConstraint(StringInfo str, const Constraint *node)
        }
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -4024,6 +4046,7 @@ _outPartitionRangeDatum(StringInfo str, const 
PartitionRangeDatum *node)
        WRITE_NODE_FIELD(value);
        WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -4055,6 +4078,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;
@@ -4766,6 +4791,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 6a05b69415..f427aa05ec 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
  */
@@ -1649,6 +1654,7 @@ _readAppendRelInfo(void)
 /*
  *     Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1744,6 +1750,7 @@ _readRangeTblEntry(void)
        READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2872,6 +2879,7 @@ _readAlternativeSubPlan(void)
 
        READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2903,6 +2911,7 @@ _readExtensibleNode(void)
        READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2937,6 +2946,7 @@ _readPartitionRangeDatum(void)
 
        READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2961,7 +2971,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();
@@ -3235,6 +3249,7 @@ parseNodeString(void)
                return_value = _readJsonTableParent();
        else if (MATCH("JSONTABLESIBLING", 16))
                return_value = _readJsonTableSibling();
+#endif /*OBSOLETE*/
        else
        {
                elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/include/Makefile b/src/include/Makefile
index 5f257a958c..17cfd268b8 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -81,6 +81,7 @@ clean:
        rm -f parser/gram.h storage/lwlocknames.h utils/probes.h
        rm -f catalog/schemapg.h catalog/system_fk_info.h
        rm -f catalog/pg_*_d.h catalog/header-stamp
+       rm -f nodes/nodetags.h nodes/header-stamp
 
 distclean maintainer-clean: clean
        rm -f pg_config.h pg_config_ext.h pg_config_os.h stamp-h stamp-ext-h
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 6306bb6fc6..37c11522ee 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -235,14 +235,14 @@ extern PGDLLIMPORT const TupleTableSlotOps 
TTSOpsBufferHeapTuple;
  * Tuple table slot implementations.
  */
 
-typedef struct VirtualTupleTableSlot
+typedef struct VirtualTupleTableSlot pg_node_attr(abstract)
 {
        TupleTableSlot base;
 
        char       *data;                       /* data for materialized slots 
*/
 } VirtualTupleTableSlot;
 
-typedef struct HeapTupleTableSlot
+typedef struct HeapTupleTableSlot pg_node_attr(abstract)
 {
        TupleTableSlot base;
 
@@ -254,7 +254,7 @@ typedef struct HeapTupleTableSlot
 } HeapTupleTableSlot;
 
 /* heap tuple residing in a buffer */
-typedef struct BufferHeapTupleTableSlot
+typedef struct BufferHeapTupleTableSlot pg_node_attr(abstract)
 {
        HeapTupleTableSlot base;
 
@@ -267,7 +267,7 @@ typedef struct BufferHeapTupleTableSlot
        Buffer          buffer;                 /* tuple's buffer, or 
InvalidBuffer */
 } BufferHeapTupleTableSlot;
 
-typedef struct MinimalTupleTableSlot
+typedef struct MinimalTupleTableSlot pg_node_attr(abstract)
 {
        TupleTableSlot base;
 
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/extensible.h b/src/include/nodes/extensible.h
index 6244c8d961..fab5bf690b 100644
--- a/src/include/nodes/extensible.h
+++ b/src/include/nodes/extensible.h
@@ -29,7 +29,7 @@
  * specific type of node.  extnodename can be looked up to find the
  * ExtensibleNodeMethods for this node type.
  */
-typedef struct ExtensibleNode
+typedef struct ExtensibleNode pg_node_attr(custom_copy_equal, 
custom_read_write)
 {
        NodeTag         type;
        const char *extnodename;        /* identifier of ExtensibleNodeMethods 
*/
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ce1fc4deb..d77aad6473 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)
         */
@@ -563,8 +565,58 @@ typedef enum NodeTag
        T_SupportRequestRows,           /* in nodes/supportnodes.h */
        T_SupportRequestIndexCondition, /* in nodes/supportnodes.h */
        T_SupportRequestWFuncMonotonic  /* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * pg_node_attr() - Used in node definitions to set extra information for
+ * gen_node_support.pl
+ *
+ * Attributes can be attached to a node as a whole (the attribute
+ * specification must be on the same line as "struct") or to a specific field
+ * (must be at the end of the line).  The argument is a comma-separated list
+ * of attributes.  Unrecognized attributes cause an error.
+ *
+ * Valid node attributes:
+ *
+ * - abstract: 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.
+ *
+ * - custom_copy_equal: Has custom implementations in copyfuncs.c and
+ *   equalfuncs.c.
+ *
+ * - custom_read_write: Has custom implementations in outfuncs.c and
+ *   readfuncs.c.
+ *
+ * - no_copy_equal: Does not support copyObject() and equal() at all.
+ *
+ * - no_read: Does not support nodeRead() at all.
+ *
+ * - special_read_write: Has special treatment in outNode() and nodeRead().
+ *
+ * Valid node field attributes:
+ *
+ * - 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.)
+ *
+ * - read_write_ignore: Ignore the field for read/write.
+ *
+ * - write_only_relids, write_only_nondefault_pathtarget, write_only_req_outer:
+ *   Special handling for Path struct; see there.
+ *
+ */
+#define pg_node_attr(...)
+
 /*
  * 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 f93d866548..fb026c6b1f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -115,7 +115,7 @@ typedef uint32 AclMode;                     /* a bitmask of 
privilege bits */
  *       Planning converts a Query tree into a Plan tree headed by a 
PlannedStmt
  *       node --- the Query structure is not used by the executor.
  */
-typedef struct Query
+typedef struct Query pg_node_attr(custom_read_write)
 {
        NodeTag         type;
 
@@ -123,8 +123,11 @@ typedef struct Query
 
        QuerySource querySource;        /* where did I come from? */
 
-       /* query identifier (can be set by plugins) */
-       uint64          queryId;
+       /*
+        * query identifier (can be set by plugins); ignored for equal, might 
not
+        * be set
+        */
+       uint64          queryId pg_node_attr(equal_ignore);
 
        bool            canSetTag;              /* do I set the command result 
tag? */
 
@@ -286,7 +289,7 @@ typedef enum A_Expr_Kind
        AEXPR_NOT_BETWEEN_SYM           /* name must be "NOT BETWEEN SYMMETRIC" 
*/
 } A_Expr_Kind;
 
-typedef struct A_Expr
+typedef struct A_Expr pg_node_attr(custom_read_write, no_read)
 {
        NodeTag         type;
        A_Expr_Kind kind;                       /* see above */
@@ -299,7 +302,7 @@ typedef struct A_Expr
 /*
  * A_Const - a literal constant
  */
-typedef struct A_Const
+typedef struct A_Const pg_node_attr(custom_copy_equal, custom_read_write, 
no_read)
 {
        NodeTag         type;
 
@@ -398,7 +401,7 @@ typedef struct FuncCall
  * This can appear within ColumnRef.fields, A_Indirection.indirection, and
  * ResTarget.indirection lists.
  */
-typedef struct A_Star
+typedef struct A_Star pg_node_attr(no_read)
 {
        NodeTag         type;
 } A_Star;
@@ -1010,7 +1013,7 @@ typedef enum RTEKind
                                                                 * present 
during parsing or rewriting */
 } RTEKind;
 
-typedef struct RangeTblEntry
+typedef struct RangeTblEntry pg_node_attr(custom_read_write)
 {
        NodeTag         type;
 
@@ -2606,7 +2609,7 @@ typedef enum ConstrType                   /* types of 
constraints */
 #define FKCONSTR_MATCH_PARTIAL         'p'
 #define FKCONSTR_MATCH_SIMPLE          's'
 
-typedef struct Constraint
+typedef struct Constraint pg_node_attr(custom_read_write, no_read)
 {
        NodeTag         type;
        ConstrType      contype;                /* see above */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b88cfb8dc0..4212610d5e 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -3,6 +3,8 @@
  * pathnodes.h
  *       Definitions for planner's internal data structures, especially Paths.
  *
+ * We don't support copying RelOptInfo, IndexOptInfo, or Path nodes.
+ * There are some subsidiary structs that are useful to copy, though.
  *
  * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -85,17 +87,20 @@ typedef enum UpperRelationKind
  * PlannerGlobal holds state for an entire planner invocation; this state
  * is shared across all levels of sub-Queries that exist in the command being
  * planned.
+ *
+ * Not all fields are printed.  (In some cases, there is no print support for
+ * the field type.)
  *----------
  */
-typedef struct PlannerGlobal
+typedef struct PlannerGlobal pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
 
-       ParamListInfo boundParams;      /* Param values provided to planner() */
+       ParamListInfo boundParams pg_node_attr(read_write_ignore);      /* 
Param values provided to planner() */
 
        List       *subplans;           /* Plans for SubPlan nodes */
 
-       List       *subroots;           /* PlannerInfos for SubPlan nodes */
+       List       *subroots pg_node_attr(read_write_ignore);           /* 
PlannerInfos for SubPlan nodes */
 
        Bitmapset  *rewindPlanIDs;      /* indices of subplans that require 
REWIND */
 
@@ -129,7 +134,7 @@ typedef struct PlannerGlobal
 
        char            maxParallelHazard;      /* worst PROPARALLEL hazard 
level */
 
-       PartitionDirectory partition_directory; /* partition descriptors */
+       PartitionDirectory partition_directory pg_node_attr(read_write_ignore); 
/* partition descriptors */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -148,6 +153,9 @@ typedef struct PlannerGlobal
  *
  * For reasons explained in optimizer/optimizer.h, we define the typedef
  * either here or in that header, whichever is read first.
+ *
+ * Not all fields are printed.  (In some cases, there is no print support for
+ * the field type.)
  *----------
  */
 #ifndef HAVE_PLANNERINFO_TYPEDEF
@@ -155,7 +163,7 @@ typedef struct PlannerInfo PlannerInfo;
 #define HAVE_PLANNERINFO_TYPEDEF 1
 #endif
 
-struct PlannerInfo
+struct PlannerInfo pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
 
@@ -165,7 +173,7 @@ struct PlannerInfo
 
        Index           query_level;    /* 1 at the outermost Query */
 
-       PlannerInfo *parent_root;       /* NULL at outermost Query */
+       PlannerInfo *parent_root pg_node_attr(read_write_ignore);       /* NULL 
at outermost Query */
 
        /*
         * plan_params contains the expressions that this query level needs to
@@ -183,15 +191,15 @@ struct PlannerInfo
         * does not correspond to a base relation, such as a join RTE or an
         * unreferenced view RTE; or if the RelOptInfo hasn't been made yet.
         */
-       struct RelOptInfo **simple_rel_array;   /* All 1-rel RelOptInfos */
-       int                     simple_rel_array_size;  /* allocated size of 
array */
+       struct RelOptInfo **simple_rel_array pg_node_attr(read_write_ignore);   
/* All 1-rel RelOptInfos */
+       int                     simple_rel_array_size 
pg_node_attr(read_write_ignore);  /* allocated size of array */
 
        /*
         * simple_rte_array is the same length as simple_rel_array and holds
         * pointers to the associated rangetable entries.  Using this is a shade
         * faster than using rt_fetch(), mostly due to fewer indirections.
         */
-       RangeTblEntry **simple_rte_array;       /* rangetable as an array */
+       RangeTblEntry **simple_rte_array pg_node_attr(read_write_ignore);       
/* rangetable as an array */
 
        /*
         * append_rel_array is the same length as the above arrays, and holds
@@ -199,7 +207,7 @@ struct PlannerInfo
         * child_relid, or NULL if the rel is not an appendrel child.  The array
         * itself is not allocated if append_rel_list is empty.
         */
-       struct AppendRelInfo **append_rel_array;
+       struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore);
 
        /*
         * all_baserels is a Relids set of all base relids (but not "other"
@@ -227,7 +235,7 @@ struct PlannerInfo
         * GEQO.
         */
        List       *join_rel_list;
-       struct HTAB *join_rel_hash;
+       struct HTAB *join_rel_hash pg_node_attr(read_write_ignore);
 
        /*
         * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -236,7 +244,7 @@ struct PlannerInfo
         * automatically added to the join_rel_level[join_cur_level] list.
         * join_rel_level is NULL if not in use.
         */
-       List      **join_rel_level; /* lists of join-relation RelOptInfos */
+       List      **join_rel_level pg_node_attr(read_write_ignore); /* lists of 
join-relation RelOptInfos */
        int                     join_cur_level; /* index of list being extended 
*/
 
        List       *init_plans;         /* init SubPlans for query */
@@ -299,16 +307,16 @@ struct PlannerInfo
        List       *distinct_pathkeys;  /* distinctClause pathkeys, if any */
        List       *sort_pathkeys;      /* sortClause pathkeys, if any */
 
-       List       *part_schemes;       /* Canonicalised partition schemes used 
in the
+       List       *part_schemes pg_node_attr(read_write_ignore);       /* 
Canonicalised partition schemes used in the
                                                                 * query. */
 
-       List       *initial_rels;       /* RelOptInfos we are now trying to 
join */
+       List       *initial_rels pg_node_attr(read_write_ignore);       /* 
RelOptInfos we are now trying to join */
 
        /* Use fetch_upper_rel() to get any particular upper rel */
-       List       *upper_rels[UPPERREL_FINAL + 1]; /* upper-rel RelOptInfos */
+       List       *upper_rels[UPPERREL_FINAL + 1] 
pg_node_attr(read_write_ignore); /* upper-rel RelOptInfos */
 
        /* Result tlists chosen by grouping_planner for upper-stage processing 
*/
-       struct PathTarget *upper_targets[UPPERREL_FINAL + 1];
+       struct PathTarget *upper_targets[UPPERREL_FINAL + 1] 
pg_node_attr(read_write_ignore);
 
        /*
         * The fully-processed targetlist is kept here.  It differs from
@@ -333,12 +341,12 @@ struct PlannerInfo
         * Fields filled during create_plan() for use in setrefs.c
         */
        /* for GroupingFunc fixup */
-       AttrNumber *grouping_map;
+       AttrNumber *grouping_map pg_node_attr(array_size(update_colnos), 
read_write_ignore);
        /* List of MinMaxAggInfos */
        List       *minmax_aggs;
 
        /* context holding PlannerInfo */
-       MemoryContext planner_cxt;
+       MemoryContext planner_cxt pg_node_attr(read_write_ignore);
 
        Cardinality total_table_pages;  /* # of pages in all non-dummy tables of
                                                                         * 
query */
@@ -360,11 +368,11 @@ struct PlannerInfo
        /*
         * Information about aggregates. Filled by preprocess_aggrefs().
         */
-       List       *agginfos;           /* AggInfo structs */
-       List       *aggtransinfos;      /* AggTransInfo structs */
-       int                     numOrderedAggs; /* number w/ DISTINCT/ORDER 
BY/WITHIN GROUP */
-       bool            hasNonPartialAggs;      /* does any agg not support 
partial mode? */
-       bool            hasNonSerialAggs;       /* is any partial agg 
non-serializable? */
+       List       *agginfos pg_node_attr(read_write_ignore);           /* 
AggInfo structs */
+       List       *aggtransinfos pg_node_attr(read_write_ignore);      /* 
AggTransInfo structs */
+       int                     numOrderedAggs pg_node_attr(read_write_ignore); 
/* number w/ DISTINCT/ORDER BY/WITHIN GROUP */
+       bool            hasNonPartialAggs pg_node_attr(read_write_ignore);      
/* does any agg not support partial mode? */
+       bool            hasNonSerialAggs pg_node_attr(read_write_ignore);       
/* is any partial agg non-serializable? */
 
        /* These fields are used only when hasRecursion is true: */
        int                     wt_param_id;    /* PARAM_EXEC ID for the work 
table */
@@ -378,11 +386,11 @@ struct PlannerInfo
         * These fields are workspace for setrefs.c.  Each is an array
         * corresponding to glob->subplans.
         */
-       bool       *isAltSubplan;
-       bool       *isUsedSubplan;
+       bool       *isAltSubplan pg_node_attr(read_write_ignore);
+       bool       *isUsedSubplan pg_node_attr(read_write_ignore);
 
        /* optional private data for join_search_hook, e.g., GEQO */
-       void       *join_search_private;
+       void       *join_search_private pg_node_attr(read_write_ignore);
 
        /* Does this query modify any partition key columns? */
        bool            partColsUpdated;
@@ -639,6 +647,9 @@ typedef struct PartitionSchemeData *PartitionScheme;
  * Furthermore, FULL JOINs add extra nullable_partexprs expressions
  * corresponding to COALESCE expressions of the left and right join columns,
  * to simplify matching join clauses to those lists.
+ *
+ * Not all fields are printed.  (In some cases, there is no print support for
+ * the field type.)
  *----------
  */
 
@@ -680,7 +691,7 @@ typedef enum RelOptKind
         (rel)->reloptkind == RELOPT_OTHER_JOINREL || \
         (rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
 
-typedef struct RelOptInfo
+typedef struct RelOptInfo pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
 
@@ -747,9 +758,9 @@ typedef struct RelOptInfo
        /* largest attrno of rel */
        AttrNumber      max_attr;
        /* array indexed [min_attr .. max_attr] */
-       Relids     *attr_needed;
+       Relids     *attr_needed pg_node_attr(read_write_ignore);
        /* array indexed [min_attr .. max_attr] */
-       int32      *attr_widths;
+       int32      *attr_widths pg_node_attr(read_write_ignore);
        /* LATERAL Vars and PHVs referenced by rel */
        List       *lateral_vars;
        /* rels that reference me laterally */
@@ -784,16 +795,18 @@ typedef struct RelOptInfo
        /* join is only valid for current user */
        bool            useridiscurrent;
        /* use "struct FdwRoutine" to avoid including fdwapi.h here */
-       struct FdwRoutine *fdwroutine;
-       void       *fdw_private;
+       struct FdwRoutine *fdwroutine pg_node_attr(read_write_ignore);
+       void       *fdw_private pg_node_attr(read_write_ignore);
 
        /*
         * cache space for remembering if we have proven this relation unique
+        *
+        * can't print unique_for_rels/non_unique_for_rels; BMSes aren't Nodes
         */
        /* known unique for these other relid set(s) */
-       List       *unique_for_rels;
+       List       *unique_for_rels pg_node_attr(read_write_ignore);
        /* known not unique for these set(s) */
-       List       *non_unique_for_rels;
+       List       *non_unique_for_rels pg_node_attr(read_write_ignore);
 
        /*
         * used by various scans and joins:
@@ -821,24 +834,24 @@ typedef struct RelOptInfo
         * used for partitioned relations:
         */
        /* Partitioning scheme */
-       PartitionScheme part_scheme;
+       PartitionScheme part_scheme pg_node_attr(read_write_ignore);
 
        /*
         * Number of partitions; -1 if not yet set; in case of a join relation 0
         * means it's considered unpartitioned
         */
-       int                     nparts;
+       int                     nparts pg_node_attr(read_write_ignore);
        /* Partition bounds */
-       struct PartitionBoundInfoData *boundinfo;
+       struct PartitionBoundInfoData *boundinfo 
pg_node_attr(read_write_ignore);
        /* True if partition bounds were created by partition_bounds_merge() */
        bool            partbounds_merged;
        /* Partition constraint, if not the root */
-       List       *partition_qual;
+       List       *partition_qual pg_node_attr(read_write_ignore);
 
        /*
         * Array of RelOptInfos of partitions, stored in the same order as 
bounds
         */
-       struct RelOptInfo **part_rels;
+       struct RelOptInfo **part_rels pg_node_attr(read_write_ignore);
 
        /*
         * Bitmap with members acting as indexes into the part_rels[] array to
@@ -848,9 +861,9 @@ typedef struct RelOptInfo
        /* Relids set of all partition relids */
        Relids          all_partrels;
        /* Non-nullable partition key expressions */
-       List      **partexprs;
+       List      **partexprs pg_node_attr(read_write_ignore);
        /* Nullable partition key expressions */
-       List      **nullable_partexprs;
+       List      **nullable_partexprs pg_node_attr(read_write_ignore);
 } RelOptInfo;
 
 /*
@@ -909,7 +922,7 @@ typedef struct IndexOptInfo IndexOptInfo;
 #define HAVE_INDEXOPTINFO_TYPEDEF 1
 #endif
 
-struct IndexOptInfo
+struct IndexOptInfo pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
 
@@ -917,8 +930,8 @@ struct IndexOptInfo
        Oid                     indexoid;
        /* tablespace of index (not table) */
        Oid                     reltablespace;
-       /* back-link to index's table */
-       RelOptInfo *rel;
+       /* back-link to index's table; don't print, else infinite recursion */
+       RelOptInfo *rel pg_node_attr(read_write_ignore);
 
        /*
         * index-size statistics (from pg_class and elsewhere)
@@ -938,31 +951,39 @@ struct IndexOptInfo
        /* number of key columns in index */
        int                     nkeycolumns;
 
+       /*
+        * array fields aren't really worth the trouble to print
+        */
+
        /*
         * column numbers of index's attributes both key and included columns, 
or
         * 0
         */
-       int                *indexkeys;
+       int                *indexkeys pg_node_attr(read_write_ignore);
        /* OIDs of collations of index columns */
-       Oid                *indexcollations;
+       Oid                *indexcollations pg_node_attr(read_write_ignore);
        /* OIDs of operator families for columns */
-       Oid                *opfamily;
+       Oid                *opfamily pg_node_attr(read_write_ignore);
        /* OIDs of opclass declared input data types */
-       Oid                *opcintype;
+       Oid                *opcintype pg_node_attr(read_write_ignore);
        /* OIDs of btree opfamilies, if orderable */
-       Oid                *sortopfamily;
+       Oid                *sortopfamily pg_node_attr(read_write_ignore);
        /* is sort order descending? */
-       bool       *reverse_sort;
+       bool       *reverse_sort pg_node_attr(read_write_ignore);
        /* do NULLs come first in the sort order? */
-       bool       *nulls_first;
+       bool       *nulls_first pg_node_attr(read_write_ignore);
        /* opclass-specific options for columns */
-       bytea     **opclassoptions;
+       bytea     **opclassoptions pg_node_attr(read_write_ignore);
        /* which index cols can be returned in an index-only scan? */
-       bool       *canreturn;
+       bool       *canreturn pg_node_attr(read_write_ignore);
        /* OID of the access method (in pg_am) */
        Oid                     relam;
-       /* expressions for non-simple index columns */
-       List       *indexprs;
+
+       /*
+        * expressions for non-simple index columns; redundant to print since we
+        * print indextlist
+        */
+       List       *indexprs pg_node_attr(read_write_ignore);
        /* predicate if a partial index, else NIL */
        List       *indpred;
 
@@ -989,17 +1010,17 @@ struct IndexOptInfo
         * Remaining fields are copied from the index AM's API struct
         * (IndexAmRoutine)
         */
-       bool            amcanorderbyop;
-       bool            amoptionalkey;
-       bool            amsearcharray;
-       bool            amsearchnulls;
+       bool            amcanorderbyop pg_node_attr(read_write_ignore);
+       bool            amoptionalkey pg_node_attr(read_write_ignore);
+       bool            amsearcharray pg_node_attr(read_write_ignore);
+       bool            amsearchnulls pg_node_attr(read_write_ignore);
        /* does AM have amgettuple interface? */
-       bool            amhasgettuple;
+       bool            amhasgettuple pg_node_attr(read_write_ignore);
        /* does AM have amgetbitmap interface? */
-       bool            amhasgetbitmap;
-       bool            amcanparallel;
+       bool            amhasgetbitmap pg_node_attr(read_write_ignore);
+       bool            amcanparallel pg_node_attr(read_write_ignore);
        /* does AM have ammarkpos interface? */
-       bool            amcanmarkpos;
+       bool            amcanmarkpos pg_node_attr(read_write_ignore);
        /* Rather than include amapi.h here, we declare amcostestimate like 
this */
        void            (*amcostestimate) ();   /* AM's cost estimator */
 };
@@ -1012,7 +1033,7 @@ struct IndexOptInfo
  * INDEX_MAX_KEYS columns in a foreign key constraint.  Each array has
  * nkeys valid entries.
  */
-typedef struct ForeignKeyOptInfo
+typedef struct ForeignKeyOptInfo pg_node_attr(custom_read_write, 
no_copy_equal, no_read)
 {
        NodeTag         type;
 
@@ -1027,11 +1048,11 @@ typedef struct ForeignKeyOptInfo
        /* number of columns in the foreign key */
        int                     nkeys;
        /* cols in referencing table */
-       AttrNumber      conkey[INDEX_MAX_KEYS];
+       AttrNumber      conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
        /* cols in referenced table */
-       AttrNumber      confkey[INDEX_MAX_KEYS];
+       AttrNumber      confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
        /* PK = FK operator OIDs */
-       Oid                     conpfeqop[INDEX_MAX_KEYS];
+       Oid                     conpfeqop[INDEX_MAX_KEYS] 
pg_node_attr(array_size(nkeys));
 
        /*
         * Derived info about whether FK's equality conditions match the query:
@@ -1060,7 +1081,7 @@ typedef struct ForeignKeyOptInfo
  * Each pg_statistic_ext row is represented by one or more nodes of this
  * type, or even zero if ANALYZE has not computed them.
  */
-typedef struct StatisticExtInfo
+typedef struct StatisticExtInfo pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
 
@@ -1068,10 +1089,13 @@ typedef struct StatisticExtInfo
        Oid                     statOid;
 
        /* includes child relations */
-       bool            inherit;
+       bool            inherit pg_node_attr(read_write_ignore);
 
-       /* back-link to statistic's table */
-       RelOptInfo *rel;
+       /*
+        * back-link to statistic's table; don't print, infinite recursion on 
plan
+        * tree dump
+        */
+       RelOptInfo *rel pg_node_attr(read_write_ignore);
 
        /* statistics kind of this entry */
        char            kind;
@@ -1123,7 +1147,7 @@ typedef struct StatisticExtInfo
  * NB: if ec_merged isn't NULL, this class has been merged into another, and
  * should be ignored in favor of using the pointed-to class.
  */
-typedef struct EquivalenceClass
+typedef struct EquivalenceClass pg_node_attr(custom_read_write, no_copy_equal, 
no_read)
 {
        NodeTag         type;
 
@@ -1173,7 +1197,7 @@ typedef struct EquivalenceClass
  * anyarray_ops would never work without this.  Use em_datatype when
  * looking up a specific btree operator to work with this expression.
  */
-typedef struct EquivalenceMember
+typedef struct EquivalenceMember pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
 
@@ -1257,7 +1281,7 @@ typedef enum VolatileFunctionStatus
  * deal with sort/group refnos when needed with less expense than including
  * TargetEntry nodes in the exprs list.
  */
-typedef struct PathTarget
+typedef struct PathTarget pg_node_attr(no_copy_equal, no_read)
 {
        NodeTag         type;
 
@@ -1265,7 +1289,7 @@ typedef struct PathTarget
        List       *exprs;
 
        /* corresponding sort/group refnos, or 0 */
-       Index      *sortgrouprefs;
+       Index      *sortgrouprefs pg_node_attr(array_size(exprs));
 
        /* cost of evaluating the expressions */
        QualCost        cost;
@@ -1296,7 +1320,7 @@ typedef struct PathTarget
  * on how the join is formed.  The relevant clauses will appear in each
  * parameterized join path's joinrestrictinfo list, instead.
  */
-typedef struct ParamPathInfo
+typedef struct ParamPathInfo pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
 
@@ -1334,22 +1358,41 @@ typedef struct ParamPathInfo
  *
  * "pathkeys" is a List of PathKey nodes (see above), describing the sort
  * ordering of the path's output rows.
+ *
+ * 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.  We also don't have read support at the moment.
  */
-typedef struct Path
+typedef struct Path pg_node_attr(no_copy_equal, no_read)
 {
        NodeTag         type;
 
        /* tag identifying scan/join method */
        NodeTag         pathtype;
 
-       /* the relation this path can build */
-       RelOptInfo *parent;
+       /*
+        * the relation this path can build
+        *
+        * We do NOT print the parent, else we'd be in infinite recursion.  We 
can
+        * print the parent's relids for identification purposes, though.
+        */
+       RelOptInfo *parent pg_node_attr(write_only_relids);
 
-       /* list of Vars/Exprs, cost, width */
-       PathTarget *pathtarget;
+       /*
+        * list of Vars/Exprs, cost, width
+        *
+        * We print the pathtarget only if it's not the default one for the rel.
+        */
+       PathTarget *pathtarget pg_node_attr(write_only_nondefault_pathtarget);
 
-       /* parameterization info, or NULL if none */
-       ParamPathInfo *param_info;
+       /*
+        * parameterization info, or NULL if none
+        *
+        * We 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.
+        */
+       ParamPathInfo *param_info pg_node_attr(write_only_req_outer);
 
        /* engage parallel-aware logic? */
        bool            parallel_aware;
@@ -1455,7 +1498,7 @@ typedef struct IndexPath
  * column, i.e. the indexcol values must form a nondecreasing sequence.
  * (The order of multiple clauses for the same index column is unspecified.)
  */
-typedef struct IndexClause
+typedef struct IndexClause pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
        struct RestrictInfo *rinfo; /* original restriction or join clause */
@@ -1750,7 +1793,7 @@ typedef struct GatherMergePath
  * All join-type paths share these fields.
  */
 
-typedef struct JoinPath
+typedef struct JoinPath pg_node_attr(abstract)
 {
        Path            path;
 
@@ -1952,14 +1995,14 @@ typedef struct AggPath
  * Various annotations used for grouping sets in the planner.
  */
 
-typedef struct GroupingSetData
+typedef struct GroupingSetData pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
        List       *set;                        /* grouping set as list of 
sortgrouprefs */
        Cardinality numGroups;          /* est. number of result groups */
 } GroupingSetData;
 
-typedef struct RollupData
+typedef struct RollupData pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
        List       *groupClause;        /* applicable subset of 
parse->groupClause */
@@ -2224,6 +2267,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
@@ -2240,22 +2289,22 @@ typedef struct RestrictInfo
        bool            outerjoin_delayed;
 
        /* see comment above */
-       bool            can_join;
+       bool            can_join pg_node_attr(equal_ignore);
 
        /* see comment above */
-       bool            pseudoconstant;
+       bool            pseudoconstant pg_node_attr(equal_ignore);
 
        /* true if known to contain no leaked Vars */
-       bool            leakproof;
+       bool            leakproof pg_node_attr(equal_ignore);
 
        /* to indicate if clause contains any volatile functions. */
-       VolatileFunctionStatus has_volatile;
+       VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);
 
        /* see comment above */
        Index           security_level;
 
        /* 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;
@@ -2270,84 +2319,89 @@ typedef struct RestrictInfo
         * Relids in the left/right side of the clause.  These fields are set 
for
         * any binary opclause.
         */
-       Relids          left_relids;
-       Relids          right_relids;
+       Relids          left_relids pg_node_attr(equal_ignore);
+       Relids          right_relids pg_node_attr(equal_ignore);
 
        /*
         * Modified clause with RestrictInfos.  This field is NULL unless clause
         * is an OR clause.
         */
-       Expr       *orclause;
+       Expr       *orclause pg_node_attr(equal_ignore);
 
        /*
         * Generating EquivalenceClass.  This field is NULL unless clause is
         * potentially redundant.
         */
-       EquivalenceClass *parent_ec;
+       EquivalenceClass *parent_ec pg_node_attr(equal_ignore, 
read_write_ignore);
 
        /*
         * cache space for cost and selectivity
         */
 
        /* eval cost of clause; -1 if not yet set */
-       QualCost        eval_cost;
+       QualCost        eval_cost pg_node_attr(equal_ignore);
 
        /*
         * selectivity for "normal" (JOIN_INNER) semantics; -1 if not yet set; 
>1
         * means a redundant clause
         */
-       Selectivity norm_selec;
+       Selectivity norm_selec pg_node_attr(equal_ignore);
        /* selectivity for outer join semantics; -1 if not yet set */
-       Selectivity outer_selec;
+       Selectivity outer_selec pg_node_attr(equal_ignore);
 
        /*
         * opfamilies containing clause operator; valid if clause is
         * mergejoinable, else NIL
         */
-       List       *mergeopfamilies;
+       List       *mergeopfamilies pg_node_attr(equal_ignore);
 
        /*
         * cache space for mergeclause processing; NULL if not yet set
         */
 
        /* EquivalenceClass containing lefthand */
-       EquivalenceClass *left_ec;
+       EquivalenceClass *left_ec pg_node_attr(equal_ignore, read_write_ignore);
        /* EquivalenceClass containing righthand */
-       EquivalenceClass *right_ec;
+       EquivalenceClass *right_ec pg_node_attr(equal_ignore, 
read_write_ignore);
        /* EquivalenceMember for lefthand */
-       EquivalenceMember *left_em;
+       EquivalenceMember *left_em pg_node_attr(equal_ignore);
        /* EquivalenceMember for righthand */
-       EquivalenceMember *right_em;
-       /* list of MergeScanSelCache structs */
-       List       *scansel_cache;
+       EquivalenceMember *right_em pg_node_attr(equal_ignore);
+
+       /*
+        * 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; 
T =
         * outer var on left, F = on right
         */
-       bool            outer_is_left;
+       bool            outer_is_left pg_node_attr(equal_ignore);
 
        /*
         * copy of clause operator; valid if clause is hashjoinable, else
         * InvalidOid
         */
-       Oid                     hashjoinoperator;
+       Oid                     hashjoinoperator pg_node_attr(equal_ignore);
 
        /*
         * cache space for hashclause processing; -1 if not yet set
         */
        /* avg bucketsize of left side */
-       Selectivity left_bucketsize;
+       Selectivity left_bucketsize pg_node_attr(equal_ignore);
        /* avg bucketsize of right side */
-       Selectivity right_bucketsize;
+       Selectivity right_bucketsize pg_node_attr(equal_ignore);
        /* left side's most common val's freq */
-       Selectivity left_mcvfreq;
+       Selectivity left_mcvfreq pg_node_attr(equal_ignore);
        /* right side's most common val's freq */
-       Selectivity right_mcvfreq;
+       Selectivity right_mcvfreq pg_node_attr(equal_ignore);
 
        /* 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;
 
 /*
@@ -2397,6 +2451,17 @@ 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
@@ -2404,10 +2469,10 @@ typedef struct PlaceHolderVar
        Expr            xpr;
 
        /* the represented expression */
-       Expr       *phexpr;
+       Expr       *phexpr pg_node_attr(equal_ignore);
 
        /* base relids syntactically within expr src */
-       Relids          phrels;
+       Relids          phrels pg_node_attr(equal_ignore);
 
        /* ID for PHV (unique within planner run) */
        Index           phid;
@@ -2572,7 +2637,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;
+       AttrNumber *parent_colnos pg_node_attr(array_size(num_child_cols));
 
        /*
         * We store the parent table's OID here for inheritance, or InvalidOid 
for
@@ -2600,7 +2665,7 @@ typedef struct AppendRelInfo
  * We add such a reference to root->processed_tlist when creating the entry,
  * and it propagates into the plan tree from there.
  */
-typedef struct RowIdentityVarInfo
+typedef struct RowIdentityVarInfo pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
 
@@ -2643,7 +2708,10 @@ typedef struct PlaceHolderInfo
        /* ID for PH (unique within planner run) */
        Index           phid;
 
-       /* copy of PlaceHolderVar tree */
+       /*
+        * copy of PlaceHolderVar tree (should be redundant for comparison, 
could
+        * be ignored)
+        */
        PlaceHolderVar *ph_var;
 
        /* lowest level we can evaluate value at */
@@ -2664,7 +2732,7 @@ typedef struct PlaceHolderInfo
  * function.  MinMaxAggPath contains a list of these, and if we accept that
  * path, the list is stored into root->minmax_aggs for use during setrefs.c.
  */
-typedef struct MinMaxAggInfo
+typedef struct MinMaxAggInfo pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
 
@@ -2677,8 +2745,11 @@ typedef struct MinMaxAggInfo
        /* expression we are aggregating on */
        Expr       *target;
 
-       /* modified "root" for planning the subquery */
-       PlannerInfo *subroot;
+       /*
+        * modified "root" for planning the subquery; not printed, too large, 
not
+        * interesting enough
+        */
+       PlannerInfo *subroot pg_node_attr(read_write_ignore);
 
        /* access path for subquery */
        Path       *path;
@@ -2737,7 +2808,7 @@ typedef struct MinMaxAggInfo
  * Instead, we just record the assignment of the slot number by appending to
  * root->glob->paramExecTypes.
  */
-typedef struct PlannerParamItem
+typedef struct PlannerParamItem pg_node_attr(no_copy_equal)
 {
        NodeTag         type;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d5c0ebe859..19b5ce2ec6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -286,16 +286,16 @@ typedef struct MergeAppend
        int                     numCols;
 
        /* their indexes in the target list */
-       AttrNumber *sortColIdx;
+       AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
        /* OIDs of operators to sort them by */
-       Oid                *sortOperators;
+       Oid                *sortOperators pg_node_attr(array_size(numCols));
 
        /* OIDs of collations */
-       Oid                *collations;
+       Oid                *collations pg_node_attr(array_size(numCols));
 
        /* NULLS FIRST/LAST directions */
-       bool       *nullsFirst;
+       bool       *nullsFirst pg_node_attr(array_size(numCols));
 
        /* Info for run-time subplan pruning; NULL if we're not doing that */
        struct PartitionPruneInfo *part_prune_info;
@@ -322,11 +322,11 @@ typedef struct RecursiveUnion
        int                     numCols;
 
        /* their indexes in the target list */
-       AttrNumber *dupColIdx;
+       AttrNumber *dupColIdx pg_node_attr(array_size(numCols));
 
        /* equality operators to compare with */
-       Oid                *dupOperators;
-       Oid                *dupCollations;
+       Oid                *dupOperators pg_node_attr(array_size(numCols));
+       Oid                *dupCollations pg_node_attr(array_size(numCols));
 
        /* estimated number of groups in input */
        long            numGroups;
@@ -812,16 +812,16 @@ typedef struct MergeJoin
        /* these are arrays, but have the same length as the mergeclauses list: 
*/
 
        /* per-clause OIDs of btree opfamilies */
-       Oid                *mergeFamilies;
+       Oid                *mergeFamilies 
pg_node_attr(array_size(mergeclauses));
 
        /* per-clause OIDs of collations */
-       Oid                *mergeCollations;
+       Oid                *mergeCollations 
pg_node_attr(array_size(mergeclauses));
 
        /* per-clause ordering (ASC or DESC) */
-       int                *mergeStrategies;
+       int                *mergeStrategies 
pg_node_attr(array_size(mergeclauses));
 
        /* per-clause nulls ordering */
-       bool       *mergeNullsFirst;
+       bool       *mergeNullsFirst pg_node_attr(array_size(mergeclauses));
 } MergeJoin;
 
 /* ----------------
@@ -863,10 +863,10 @@ typedef struct Memoize
        int                     numKeys;
 
        /* hash operators for each key */
-       Oid                *hashOperators;
+       Oid                *hashOperators pg_node_attr(array_size(numKeys));
 
        /* collations for each key */
-       Oid                *collations;
+       Oid                *collations pg_node_attr(array_size(numKeys));
 
        /* cache keys in the form of exprs containing parameters */
        List       *param_exprs;
@@ -905,16 +905,16 @@ typedef struct Sort
        int                     numCols;
 
        /* their indexes in the target list */
-       AttrNumber *sortColIdx;
+       AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
        /* OIDs of operators to sort them by */
-       Oid                *sortOperators;
+       Oid                *sortOperators pg_node_attr(array_size(numCols));
 
        /* OIDs of collations */
-       Oid                *collations;
+       Oid                *collations pg_node_attr(array_size(numCols));
 
        /* NULLS FIRST/LAST directions */
-       bool       *nullsFirst;
+       bool       *nullsFirst pg_node_attr(array_size(numCols));
 } Sort;
 
 /* ----------------
@@ -941,11 +941,11 @@ typedef struct Group
        int                     numCols;
 
        /* their indexes in the target list */
-       AttrNumber *grpColIdx;
+       AttrNumber *grpColIdx pg_node_attr(array_size(numCols));
 
        /* equality operators to compare with */
-       Oid                *grpOperators;
-       Oid                *grpCollations;
+       Oid                *grpOperators pg_node_attr(array_size(numCols));
+       Oid                *grpCollations pg_node_attr(array_size(numCols));
 } Group;
 
 /* ---------------
@@ -976,11 +976,11 @@ typedef struct Agg
        int                     numCols;
 
        /* their indexes in the target list */
-       AttrNumber *grpColIdx;
+       AttrNumber *grpColIdx pg_node_attr(array_size(numCols));
 
        /* equality operators to compare with */
-       Oid                *grpOperators;
-       Oid                *grpCollations;
+       Oid                *grpOperators pg_node_attr(array_size(numCols));
+       Oid                *grpCollations pg_node_attr(array_size(numCols));
 
        /* estimated number of groups in input */
        long            numGroups;
@@ -1015,25 +1015,25 @@ typedef struct WindowAgg
        int                     partNumCols;
 
        /* their indexes in the target list */
-       AttrNumber *partColIdx;
+       AttrNumber *partColIdx pg_node_attr(array_size(partNumCols));
 
        /* equality operators for partition columns */
-       Oid                *partOperators;
+       Oid                *partOperators pg_node_attr(array_size(partNumCols));
 
        /* collations for partition columns */
-       Oid                *partCollations;
+       Oid                *partCollations 
pg_node_attr(array_size(partNumCols));
 
        /* number of columns in ordering clause */
        int                     ordNumCols;
 
        /* their indexes in the target list */
-       AttrNumber *ordColIdx;
+       AttrNumber *ordColIdx pg_node_attr(array_size(ordNumCols));
 
        /* equality operators for ordering columns */
-       Oid                *ordOperators;
+       Oid                *ordOperators pg_node_attr(array_size(ordNumCols));
 
        /* collations for ordering columns */
-       Oid                *ordCollations;
+       Oid                *ordCollations pg_node_attr(array_size(ordNumCols));
 
        /* frame_clause options, see WindowDef */
        int                     frameOptions;
@@ -1086,13 +1086,13 @@ typedef struct Unique
        int                     numCols;
 
        /* their indexes in the target list */
-       AttrNumber *uniqColIdx;
+       AttrNumber *uniqColIdx pg_node_attr(array_size(numCols));
 
        /* equality operators to compare with */
-       Oid                *uniqOperators;
+       Oid                *uniqOperators pg_node_attr(array_size(numCols));
 
        /* collations for equality comparisons */
-       Oid                *uniqCollations;
+       Oid                *uniqCollations pg_node_attr(array_size(numCols));
 } Unique;
 
 /* ------------
@@ -1137,16 +1137,16 @@ typedef struct GatherMerge
        int                     numCols;
 
        /* their indexes in the target list */
-       AttrNumber *sortColIdx;
+       AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
        /* OIDs of operators to sort them by */
-       Oid                *sortOperators;
+       Oid                *sortOperators pg_node_attr(array_size(numCols));
 
        /* OIDs of collations */
-       Oid                *collations;
+       Oid                *collations pg_node_attr(array_size(numCols));
 
        /* NULLS FIRST/LAST directions */
-       bool       *nullsFirst;
+       bool       *nullsFirst pg_node_attr(array_size(numCols));
 
        /*
         * param id's of initplans which are referred at gather merge or one of
@@ -1197,11 +1197,11 @@ typedef struct SetOp
        int                     numCols;
 
        /* their indexes in the target list */
-       AttrNumber *dupColIdx;
+       AttrNumber *dupColIdx pg_node_attr(array_size(numCols));
 
        /* equality operators to compare with */
-       Oid                *dupOperators;
-       Oid                *dupCollations;
+       Oid                *dupOperators pg_node_attr(array_size(numCols));
+       Oid                *dupCollations pg_node_attr(array_size(numCols));
 
        /* where is the flag column, if any */
        AttrNumber      flagColIdx;
@@ -1253,13 +1253,13 @@ typedef struct Limit
        int                     uniqNumCols;
 
        /* their indexes in the target list */
-       AttrNumber *uniqColIdx;
+       AttrNumber *uniqColIdx pg_node_attr(array_size(uniqNumCols));
 
        /* equality operators to compare with */
-       Oid                *uniqOperators;
+       Oid                *uniqOperators pg_node_attr(array_size(uniqNumCols));
 
        /* collations for equality comparisons */
-       Oid                *uniqCollations;
+       Oid                *uniqCollations 
pg_node_attr(array_size(uniqNumCols));
 } Limit;
 
 
@@ -1425,13 +1425,13 @@ typedef struct PartitionedRelPruneInfo
        int                     nparts;
 
        /* subplan index by partition index, or -1 */
-       int                *subplan_map;
+       int                *subplan_map pg_node_attr(array_size(nparts));
 
        /* subpart index by partition index, or -1 */
-       int                *subpart_map;
+       int                *subpart_map pg_node_attr(array_size(nparts));
 
        /* relation OID by partition index, or 0 */
-       Oid                *relid_map;
+       Oid                *relid_map pg_node_attr(array_size(nparts));
 
        /*
         * initial_pruning_steps shows how to prune during executor startup 
(i.e.,
@@ -1452,7 +1452,7 @@ typedef struct PartitionedRelPruneInfo
  *
  * step_id is the global identifier of the step within its pruning context.
  */
-typedef struct PartitionPruneStep
+typedef struct PartitionPruneStep pg_node_attr(abstract)
 {
        NodeTag         type;
        int                     step_id;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 732c00c098..9e6b4bdb1d 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -64,8 +64,11 @@ typedef struct RangeVar
 {
        NodeTag         type;
 
-       /* the catalog (database) name, or NULL */
-       char       *catalogname;
+       /*
+        * the catalog (database) name, or NULL; ignored for read/write, since 
it
+        * is presently not semantically meaningful
+        */
+       char       *catalogname pg_node_attr(read_write_ignore);
 
        /* the schema name, or NULL */
        char       *schemaname;
@@ -155,7 +158,7 @@ typedef struct IntoClause
  * contains NodeTag, this is a formality, but it is an easy form of
  * documentation.  See also the ExprState node types in execnodes.h.
  */
-typedef struct Expr
+typedef struct Expr pg_node_attr(abstract)
 {
        NodeTag         type;
 } Expr;
@@ -233,10 +236,15 @@ typedef struct Var
         */
        Index           varlevelsup;
 
+       /*
+        * varnosyn/varattnosyn are ignored for equality, because Vars with
+        * different syntactic identifiers are semantically the same as long as
+        * their varno/varattno match.
+        */
        /* syntactic relation index (0 if unknown) */
-       Index           varnosyn;
+       Index           varnosyn pg_node_attr(equal_ignore);
        /* syntactic attribute number */
-       AttrNumber      varattnosyn;
+       AttrNumber      varattnosyn pg_node_attr(equal_ignore);
 
        /* token location, or -1 if unknown */
        int                     location;
@@ -250,7 +258,7 @@ typedef struct Var
  * references).  This ensures that the Const node is self-contained and makes
  * it more likely that equal() will see logically identical values as equal.
  */
-typedef struct Const
+typedef struct Const pg_node_attr(custom_copy_equal, custom_read_write)
 {
        Expr            xpr;
        Oid                     consttype;              /* pg_type OID of the 
constant's datatype */
@@ -374,8 +382,11 @@ typedef struct Aggref
        /* OID of collation that function should use */
        Oid                     inputcollid;
 
-       /* type Oid of aggregate's transition value */
-       Oid                     aggtranstype;
+       /*
+        * type Oid of aggregate's transition value; ignored for equal since it
+        * might not be set yet
+        */
+       Oid                     aggtranstype pg_node_attr(equal_ignore);
 
        /* type Oids of direct and aggregated args */
        List       *aggargtypes;
@@ -455,10 +466,10 @@ typedef struct GroupingFunc
        List       *args;
 
        /* ressortgrouprefs of arguments */
-       List       *refs;
+       List       *refs pg_node_attr(equal_ignore);
 
        /* actual column positions set by planner */
-       List       *cols;
+       List       *cols pg_node_attr(equal_ignore);
 
        /* same as Aggref.agglevelsup */
        Index           agglevelsup;
@@ -634,7 +645,7 @@ typedef struct OpExpr
        Oid                     opno;
 
        /* PG_PROC OID of underlying function */
-       Oid                     opfuncid;
+       Oid                     opfuncid pg_node_attr(equal_ignore_if_zero);
 
        /* PG_TYPE OID of result value */
        Oid                     opresulttype;
@@ -698,6 +709,10 @@ 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
 {
@@ -707,13 +722,13 @@ typedef struct ScalarArrayOpExpr
        Oid                     opno;
 
        /* PG_PROC OID of comparison function */
-       Oid                     opfuncid;
+       Oid                     opfuncid pg_node_attr(equal_ignore_if_zero);
 
        /* PG_PROC OID of hash func or InvalidOid */
-       Oid                     hashfuncid;
+       Oid                     hashfuncid pg_node_attr(equal_ignore_if_zero);
 
        /* PG_PROC OID of negator of opfuncid function or InvalidOid.  See 
above */
-       Oid                     negfuncid;
+       Oid                     negfuncid pg_node_attr(equal_ignore_if_zero);
 
        /* true for ANY, false for ALL */
        bool            useOr;
@@ -740,7 +755,7 @@ typedef enum BoolExprType
        AND_EXPR, OR_EXPR, NOT_EXPR
 } BoolExprType;
 
-typedef struct BoolExpr
+typedef struct BoolExpr pg_node_attr(custom_read_write)
 {
        Expr            xpr;
        BoolExprType boolop;
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index eaf937051c..6193f51536 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -25,7 +25,7 @@
  * (There used to be a Value node, which encompassed all these different node 
types.  Hence the name of this file.)
  */
 
-typedef struct Integer
+typedef struct Integer pg_node_attr(special_read_write)
 {
        NodeTag         type;
        int                     ival;
@@ -42,25 +42,25 @@ typedef struct Integer
  * Note that an integer-looking string will get lexed as T_Float if the value
  * is too large to fit in an 'int'.
  */
-typedef struct Float
+typedef struct Float pg_node_attr(special_read_write)
 {
        NodeTag         type;
        char       *fval;
 } Float;
 
-typedef struct Boolean
+typedef struct Boolean pg_node_attr(special_read_write)
 {
        NodeTag         type;
        bool            boolval;
 } Boolean;
 
-typedef struct String
+typedef struct String pg_node_attr(special_read_write)
 {
        NodeTag         type;
        char       *sval;
 } String;
 
-typedef struct BitString
+typedef struct BitString pg_node_attr(special_read_write)
 {
        NodeTag         type;
        char       *bsval;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 1896a9a06d..3e6da86688 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -265,7 +265,7 @@ typedef struct RelationData
  * Currently, we mostly cache fields of interest to the planner, but the set
  * of fields has already grown the constraint OID for other uses.
  */
-typedef struct ForeignKeyCacheInfo
+typedef struct ForeignKeyCacheInfo pg_node_attr(no_read)
 {
        NodeTag         type;
        Oid                     conoid;                 /* oid of the 
constraint itself */
@@ -273,9 +273,12 @@ 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 */
+       /* cols in referencing table */
+       AttrNumber      conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
+       /* cols in referenced table */
+       AttrNumber      confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
+       /* PK = FK operator OIDs */
+       Oid                     conpfeqop[INDEX_MAX_KEYS] 
pg_node_attr(array_size(nkeys));
 } ForeignKeyCacheInfo;
 
 
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index d30e8fcb11..286b5810c9 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -841,6 +841,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;

base-commit: b55f62abb2c2e07dfae99e19a2b3d7ca9e58dc1a
-- 
2.36.1

Reply via email to