Re: automatically generating node support functions

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: automatically generating node support functions
Дата
Msg-id 3488143.1657310625@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: automatically generating node support functions  (Peter Eisentraut <peter.eisentraut@enterprisedb.com>)
Ответы Re: automatically generating node support functions  (Tom Lane <tgl@sss.pgh.pa.us>)
Re: automatically generating node support functions  (Peter Eisentraut <peter.eisentraut@enterprisedb.com>)
Список pgsql-hackers
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
> On 08.07.22 15:52, Tom Lane wrote:
>> I'll re-read the patch today, but how open are you to putting the
>> struct attributes at the top?  I'm willing to do the legwork.

> I agree near the top would be preferable.  I think it would even be 
> feasible to parse the whole thing if pgindent split it across lines.  I 
> sort of tried to maintain the consistency with C/C++ attributes like 
> __attribute__ and [[attribute]], hoping that that would confuse other 
> tooling the least.  Feel free to experiment further.

I went through and did that, and I do like this way better.

I did a final round of review, and found a few cosmetic things, as
well as serious bugs in the code I'd contributed for copy_as/read_as:
they did the wrong thing for VALUE of "0" because I should have
written "if (defined $foo)" not "if ($foo)".  Also, read_as did
not generate correct code for the case where we don't have
read_write_ignore; in that case we have to read the value outfuncs.c
wrote and then override it.

0001 attached repeats your v8 (to please the cfbot).

0002 includes some suggestions for the README file as well as
cosmetic and not-so-cosmetic fixes for gen_node_support.pl.

0003 moves the node-level attributes as discussed.

Lastly, I think we ought to apply pgperltidy to the Perl code.
In case you don't have that installed, 0004 is the diffs I got.

I think this is ready to go (don't forget the catversion bump).

            regards, tom lane

From 3d427b2cbe9610c3cc7b00720e8f2def93f19948 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Fri, 8 Jul 2022 14:35:45 +0200
Subject: [PATCH v8] 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.

Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
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/README                 |  74 +-
 src/backend/nodes/copyfuncs.c            |  20 +-
 src/backend/nodes/equalfuncs.c           |  22 +-
 src/backend/nodes/gen_node_support.pl    | 845 +++++++++++++++++++++++
 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                |  67 ++
 src/include/nodes/parsenodes.h           |  19 +-
 src/include/nodes/pathnodes.h            | 331 +++++----
 src/include/nodes/plannodes.h            | 121 ++--
 src/include/nodes/primnodes.h            |  46 +-
 src/include/nodes/value.h                |  10 +-
 src/include/utils/rel.h                  |   8 +-
 src/tools/msvc/Solution.pm               |  46 ++
 src/tools/pgindent/exclude_file_patterns |   5 +
 22 files changed, 1481 insertions(+), 276 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-headerssubmake-utils-headers 
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h
submake-catalog-headerssubmake-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/README b/src/backend/nodes/README
index d066ac5c61..2d6a7bcf7a 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -3,26 +3,33 @@ src/backend/nodes/README
 Node Structures
 ===============

-Andrew Yu (11/94)
-
 Introduction
 ------------

-The current node structures are plain old C structures. "Inheritance" is
-achieved by convention. No additional functions will be generated. Functions
-that manipulate node structures reside in this directory.
+The node structures are plain old C structures with the first field of
+type NodeTag.  "Inheritance" is achieved by convention: The first
+field can alternatively be of another node type.  Functions that
+manipulate node structures reside in this directory.  Some support
+functions are automatically generated by the gen_node_support.pl
+script, other functions are maintained manually.  To control the
+automatic generation of some support functions, node types and node
+fields can be annotated with pg_node_attr() specifications; see
+further documentation in src/include/nodes/nodes.h.


 FILES IN THIS DIRECTORY (src/backend/nodes/)

     General-purpose node manipulation functions:
-    copyfuncs.c    - copy a node tree
-    equalfuncs.c    - compare two node trees
-    outfuncs.c    - convert a node tree to text representation
-    readfuncs.c    - convert text representation back to a node tree
+    copyfuncs.c    - copy a node tree (*)
+    equalfuncs.c    - compare two node trees (*)
+    outfuncs.c    - convert a node tree to text representation (*)
+    readfuncs.c    - convert text representation back to a node tree (*)
     makefuncs.c    - creator functions for some common node types
     nodeFuncs.c    - some other general-purpose manipulation functions

+    (*) - Most functions in these files are generated by
+    gen_node_support.pl and #include'd there.
+
     Specialized manipulation functions:
     bitmapset.c    - Bitmapset support
     list.c        - generic list support
@@ -33,7 +40,7 @@ FILES IN THIS DIRECTORY (src/backend/nodes/)
 FILES IN src/include/nodes/

     Node definitions:
-    nodes.h        - define node tags (NodeTag)
+    nodes.h        - define node tags (NodeTag) (*)
     primnodes.h    - primitive nodes
     parsenodes.h    - parse tree nodes
     pathnodes.h    - path tree nodes and planner internal structures
@@ -42,39 +49,34 @@ FILES IN src/include/nodes/
     memnodes.h    - memory nodes
     pg_list.h    - generic list

+    (*) - Also #include's files generated by gen_node_support.pl.
+

 Steps to Add a Node
 -------------------

 Suppose you want to define a node Foo:

-1. Add a tag (T_Foo) to the enum NodeTag in nodes.h.  (If you insert the
-   tag in a way that moves the numbers associated with existing tags,
-   you'll need to recompile the whole tree after doing this.  It doesn't
-   force initdb though, because the numbers never go to disk.)
-2. Add the structure definition to the appropriate include/nodes/???.h file.
+1. Add the structure definition to the appropriate include/nodes/???.h file.
    If you intend to inherit from, say a Plan node, put Plan as the first field
-   of your struct definition.
-3. If you intend to use copyObject, equal, nodeToString or stringToNode,
-   add an appropriate function to copyfuncs.c, equalfuncs.c, outfuncs.c
-   and readfuncs.c accordingly.  (Except for frequently used nodes, don't
-   bother writing a creator function in makefuncs.c)  The header comments
-   in those files give general rules for whether you need to add support.
-4. Add cases to the functions in nodeFuncs.c as needed.  There are many
+   of your struct definition.  (The T_Foo tag is created automatically.)
+2. Check that the generated support functions in copyfuncs.c, equalfuncs.c,
+   outfuncs.c and readfuncs.c look correct.  Add attributes as necessary to
+   control the outcome.  (Except for frequently used nodes, don't bother
+   writing a creator function in makefuncs.c)
+3. Add cases to the functions in nodeFuncs.c as needed.  There are many
    other places you'll probably also need to teach about your new node
    type.  Best bet is to grep for references to one or two similar existing
    node types to find all the places to touch.
-
-
-Historical Note
----------------
-
-Prior to the current simple C structure definitions, the Node structures
-used a pseudo-inheritance system which automatically generated creator and
-accessor functions. Since every node inherited from LispValue, the whole thing
-was a mess. Here's a little anecdote:
-
-    LispValue definition -- class used to support lisp structures
-    in C.  This is here because we did not want to totally rewrite
-    planner and executor code which depended on lisp structures when
-    we ported postgres V1 from lisp to C. -cim 4/23/90
+4. Consider testing your new code with COPY_PARSE_PLAN_TREES,
+   WRITE_READ_PARSE_PLAN_TREES, and RAW_EXPRESSION_COVERAGE_TEST to ensure
+   support has been added everywhere that it's necessary; see
+   pg_config_manual.h about these.
+
+Adding a new node type moves the numbers associated with existing
+tags, so you'll need to recompile the whole tree after doing this.
+(--enable-depend usually helps.)  It doesn't force initdb though,
+because the numbers never go to disk.  But altering or removing a node
+type should usually be accompanied by an initdb-forcing catalog
+version change, since the interpretation of serialized node trees
+stored in system catalogs is affected by that.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2c834e4d0d..b72c79f2df 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
  * ****************************************************************
@@ -1431,6 +1430,7 @@ _copyVar(const Var *from)

     return newnode;
 }
+#endif                            /* OBSOLETE */

 /*
  * _copyConst
@@ -1470,6 +1470,7 @@ _copyConst(const Const *from)
     return newnode;
 }

+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -3214,6 +3215,7 @@ _copyParamRef(const ParamRef *from)

     return newnode;
 }
+#endif                            /* OBSOLETE */

 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -3254,6 +3256,7 @@ _copyA_Const(const A_Const *from)
     return newnode;
 }

+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -5419,6 +5422,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)

     return newnode;
 }
+#endif                            /* OBSOLETE */

 /* ****************************************************************
  *                    extensible.h copy functions
@@ -5441,6 +5445,7 @@ _copyExtensibleNode(const ExtensibleNode *from)
     return newnode;
 }

+#ifdef OBSOLETE
 /* ****************************************************************
  *                    value.h copy functions
  * ****************************************************************
@@ -5511,6 +5516,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)

     return newnode;
 }
+#endif                            /* OBSOLETE */

 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5531,6 +5537,8 @@ copyObjectImpl(const void *from)

     switch (nodeTag(from))
     {
+#include "copyfuncs.switch.c"
+#ifdef OBSOLETE
             /*
              * PLAN NODES
              */
@@ -5969,6 +5977,7 @@ copyObjectImpl(const void *from)
         case T_BitString:
             retval = _copyBitString(from);
             break;
+#endif                            /* OBSOLETE */

             /*
              * LIST NODES
@@ -5986,6 +5995,8 @@ copyObjectImpl(const void *from)
             retval = list_copy(from);
             break;

+#ifdef OBSOLETE
+
             /*
              * EXTENSIBLE NODES
              */
@@ -6537,6 +6548,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 449352639f..8d18548ade 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..86af4bf032
--- /dev/null
+++ b/src/backend/nodes/gen_node_support.pl
@@ -0,0 +1,845 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/backend/nodes/gen_node_support.pl
+#
+#----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use File::Basename;
+
+use FindBin;
+use lib "$FindBin::RealBin/../catalog";
+
+use Catalog;  # for RenameTempFile
+
+
+# Test whether first argument is element of the list in the second
+# argument
+sub elem
+{
+    my $x = shift;
+    return grep { $_ eq $x } @_;
+}
+
+
+# collect node names
+my @node_types = qw(Node);
+# collect info for each node type
+my %node_type_info;
+
+# node types we don't want copy support for
+my @no_copy;
+# node types we don't want equal support for
+my @no_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 RelFileNumber Selectivity Size StrategyNumber SubTransactionId
TimeLineIDXLogRecPtr 
+);
+
+# 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, qw(CallContext InlineCodeBlock);
+push @no_equal, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause
InlineCodeBlockObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation); 
+push @no_read, qw(A_ArrayExpr A_Indices A_Indirection AlterStatsStmt
+CollateClause ColumnDef ColumnRef CreateForeignTableStmt CreateStatsStmt
+CreateStmt FuncCall ImportForeignSchemaStmt IndexElem IndexStmt
+JsonAggConstructor JsonArgument JsonArrayAgg JsonArrayConstructor
+JsonArrayQueryConstructor JsonCommon JsonFuncExpr JsonKeyValue
+JsonObjectAgg JsonObjectConstructor JsonOutput JsonParseExpr JsonScalarExpr
+JsonSerializeExpr JsonTable JsonTableColumn JsonTablePlan LockingClause
+MultiAssignRef PLAssignStmt ParamRef PartitionElem PartitionSpec
+PlaceHolderVar PublicationObjSpec PublicationTable RangeFunction
+RangeSubselect RangeTableFunc RangeTableFuncCol RangeTableSample RawStmt
+ResTarget ReturnStmt SelectStmt SortBy StatsElem TableLikeClause
+TriggerTransition TypeCast TypeName WindowDef WithClause XmlSerialize);
+
+
+## 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*(?:\Q$in_struct\E\s*)?(?:pg_node_attr\(([\w(), ]*)\))?;$/)
+            {
+                my $node_attrs = $1 || '';
+
+                if ($is_node_struct)
+                {
+                    # This is the end of a node struct definition.
+                    # Save everything we have collected.
+
+                    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')
+                        {
+                            push @no_copy, $in_struct;
+                        }
+                        elsif ($attr eq 'no_equal')
+                        {
+                            push @no_equal, $in_struct;
+                        }
+                        elsif ($attr eq 'no_copy_equal')
+                        {
+                            push @no_copy, $in_struct;
+                            push @no_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";
+                        }
+                    }
+
+                    # 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, $in_struct;
+                        push @no_equal, $in_struct;
+                        push @no_read_write, $in_struct;
+                    }
+
+                    # Propagate some node attributes from supertypes
+                    if ($supertype)
+                    {
+                        push @no_copy, $in_struct if elem $supertype, @no_copy;
+                        push @no_equal, $in_struct if elem $supertype, @no_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+\)$/ &&
+                                $attr !~ /^copy_as\(\w+\)$/ &&
+                                $attr !~ /^read_as\(\w+\)$/ &&
+                                !elem $attr, qw(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+)$/ && $1 ne 'Node')
+            {
+                $in_struct = $1;
+                $subline = 0;
+            }
+            # one node type typedef'ed directly from another
+            elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types)
+            {
+                my $alias_of = $1;
+                my $n = $2;
+
+                # copy everything over
+                push @node_types, $n;
+                my @f = @{$node_type_info{$alias_of}->{fields}};
+                my %ft = %{$node_type_info{$alias_of}->{field_types}};
+                my %fa = %{$node_type_info{$alias_of}->{field_attrs}};
+                $node_type_info{$n}->{fields} = \@f;
+                $node_type_info{$n}->{field_types} = \%ft;
+                $node_type_info{$n}->{field_attrs} = \%fa;
+            }
+            # collect enum names
+            elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/)
+            {
+                push @enum_types, $1;
+            }
+        }
+    }
+
+    if ($in_struct)
+    {
+        die "runaway \"$in_struct\" in file \"$infile\"\n";
+    }
+
+    close $ifh;
+} # for each file
+
+
+## write output
+
+my $tmpext  = ".tmp$$";
+
+# nodetags.h
+
+open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
+
+my $i = 1;
+foreach my $n (@node_types,@extra_tags)
+{
+    next if elem $n, @abstract_types;
+    print $nt "\tT_${n} = $i,\n";
+    $i++;
+}
+
+close $nt;
+
+
+# make #include lines necessary to pull in all the struct definitions
+my $node_includes = '';
+foreach my $infile (sort @ARGV)
+{
+    $infile =~ s!.*src/include/!!;
+    $node_includes .= qq{#include "$infile"\n};
+}
+
+
+# copyfuncs.c, equalfuncs.c
+
+open my $cff, '>', 'copyfuncs.funcs.c' . $tmpext or die $!;
+open my $eff, '>', 'equalfuncs.funcs.c' . $tmpext or die $!;
+open my $cfs, '>', 'copyfuncs.switch.c' . $tmpext or die $!;
+open my $efs, '>', 'equalfuncs.switch.c' . $tmpext or die $!;
+
+# add required #include lines to each file set
+print $cff $node_includes;
+print $eff $node_includes;
+
+foreach my $n (@node_types)
+{
+    next if elem $n, @abstract_types;
+    my $struct_no_copy = (elem $n, @no_copy);
+    my $struct_no_equal = (elem $n, @no_equal);
+    next if $struct_no_copy && $struct_no_equal;
+    next if $n eq 'List';
+
+    print $cfs "\t\tcase T_${n}:\n".
+      "\t\t\tretval = _copy${n}(from);\n".
+      "\t\t\tbreak;\n" unless $struct_no_copy;
+
+    print $efs "\t\tcase T_${n}:\n".
+      "\t\t\tretval = _equal${n}(a, b);\n".
+      "\t\t\tbreak;\n" unless $struct_no_equal;
+
+    next if elem $n, @custom_copy_equal;
+
+    print $cff "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+" unless $struct_no_copy;
+
+    print $eff "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+" unless $struct_no_equal;
+
+    # 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 = $struct_no_copy;
+        my $equal_ignore = $struct_no_equal;
+
+        # extract per-field attributes
+        my $array_size_field;
+        my $copy_as_field;
+        foreach my $a (@a)
+        {
+            if ($a =~ /^array_size.([\w.]+)/)
+            {
+                $array_size_field = $1;
+            }
+            elsif ($a =~ /^copy_as.([\w.]+)/)
+            {
+                $copy_as_field = $1;
+            }
+            elsif ($a eq 'equal_ignore')
+            {
+                $equal_ignore = 1;
+            }
+        }
+
+        # override type-specific copy method if copy_as is specified
+        if ($copy_as_field)
+        {
+            print $cff "\tnewnode->$f = $copy_as_field;\n" unless $copy_ignore;
+            $copy_ignore = 1;
+        }
+
+        # 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;
+            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;
+}
+" unless $struct_no_copy;
+    print $eff "
+\treturn true;
+}
+" unless $struct_no_equal;
+}
+
+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
ImportForeignSchemaStmtIndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt); 
+        next unless elem $n, @keep;
+    }
+
+    my $struct_no_read = (elem $n, @no_read);
+
+    # output format starts with upper case node type name
+    my $N = uc $n;
+
+    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 $struct_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 $struct_no_read;
+
+    # print instructions for each field
+    foreach my $f (@{$node_type_info{$n}->{fields}})
+    {
+        my $t = $node_type_info{$n}->{field_types}{$f};
+        my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
+        my $no_read = $struct_no_read;
+
+        # extract per-field attributes
+        my $read_write_ignore = 0;
+        my $read_as_field;
+        foreach my $a (@a)
+        {
+            if ($a =~ /^read_as.([\w.]+)/)
+            {
+                $read_as_field = $1;
+            }
+            elsif ($a eq 'read_write_ignore')
+            {
+                $read_write_ignore = 1;
+            }
+        }
+
+        # override type-specific read method if read_as is specified
+        if ($read_as_field)
+        {
+            print $rff "\tlocal_node->$f = $read_as_field;\n" unless $no_read;
+            $no_read = 1;
+        }
+
+        # check this after handling read_as
+        if ($read_write_ignore)
+        {
+            next if $no_read;
+            die "$n.$f must not be marked read_write_ignore\n";
+        }
+
+        # 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' || $t eq 'RelFileNumber')
+        {
+            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 "\tappendStringInfoString(str, \" :required_outer \");\n".
+              "\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 $struct_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.coutfuncs.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 77a7a868ca..f26c129d59 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
  */
@@ -1138,6 +1140,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)
@@ -1159,6 +1162,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)
 {
@@ -1329,6 +1333,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)
@@ -1357,6 +1362,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
     WRITE_LOCATION_FIELD(location);
 }

+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2569,6 +2575,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)
@@ -2596,6 +2603,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)
 {
@@ -2607,6 +2615,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)
@@ -2635,6 +2644,7 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
     WRITE_UINT_FIELD(ec_max_security);
 }

+#ifdef OBSOLETE
 static void
 _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 {
@@ -2819,6 +2829,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
     WRITE_NODE_FIELD(item);
     WRITE_INT_FIELD(paramId);
 }
+#endif                            /* OBSOLETE */

 /*****************************************************************************
  *
@@ -2841,6 +2852,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
     methods->nodeOut(str, node);
 }

+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *    Stuff from parsenodes.h.
@@ -3174,6 +3186,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)
@@ -3248,6 +3261,7 @@ _outQuery(StringInfo str, const Query *node)
     WRITE_INT_FIELD(stmt_len);
 }

+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3413,6 +3427,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)
@@ -3493,6 +3508,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
     WRITE_NODE_FIELD(securityQuals);
 }

+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3516,6 +3532,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)
@@ -3634,6 +3651,7 @@ _outBitString(StringInfo str, const BitString *node)
     appendStringInfoString(str, node->bsval);
 }

+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3665,6 +3683,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)
@@ -3681,6 +3700,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)
 {
@@ -3825,6 +3845,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)
@@ -3947,6 +3968,7 @@ _outConstraint(StringInfo str, const Constraint *node)
     }
 }

+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -4007,6 +4029,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
     WRITE_NODE_FIELD(value);
     WRITE_LOCATION_FIELD(location);
 }
+#endif                            /* OBSOLETE */

 /*
  * outNode -
@@ -4038,6 +4061,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;
@@ -4743,6 +4768,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 6b11f0481b..21176cd4c0 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
  */
@@ -2846,6 +2853,7 @@ _readAlternativeSubPlan(void)

     READ_DONE();
 }
+#endif                            /* OBSOLETE */

 /*
  * _readExtensibleNode
@@ -2877,6 +2885,7 @@ _readExtensibleNode(void)
     READ_DONE();
 }

+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2911,6 +2920,7 @@ _readPartitionRangeDatum(void)

     READ_DONE();
 }
+#endif                            /* OBSOLETE */

 /*
  * parseNodeString
@@ -2935,7 +2945,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();
@@ -3205,6 +3219,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..9ead14b651 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -240,7 +240,7 @@ typedef struct VirtualTupleTableSlot
     TupleTableSlot base;

     char       *data;            /* data for materialized slots */
-} VirtualTupleTableSlot;
+} VirtualTupleTableSlot pg_node_attr(abstract);

 typedef struct HeapTupleTableSlot
 {
@@ -251,7 +251,7 @@ typedef struct HeapTupleTableSlot
 #define FIELDNO_HEAPTUPLETABLESLOT_OFF 2
     uint32        off;            /* saved state for slot_deform_heap_tuple */
     HeapTupleData tupdata;        /* optional workspace for storing tuple */
-} HeapTupleTableSlot;
+} HeapTupleTableSlot pg_node_attr(abstract);

 /* heap tuple residing in a buffer */
 typedef struct BufferHeapTupleTableSlot
@@ -265,7 +265,7 @@ typedef struct BufferHeapTupleTableSlot
      * such a case, since presumably tts_tuple is pointing into the buffer.)
      */
     Buffer        buffer;            /* tuple's buffer, or InvalidBuffer */
-} BufferHeapTupleTableSlot;
+} BufferHeapTupleTableSlot pg_node_attr(abstract);

 typedef struct MinimalTupleTableSlot
 {
@@ -284,7 +284,7 @@ typedef struct MinimalTupleTableSlot
     HeapTupleData minhdr;        /* workspace for minimal-tuple-only case */
 #define FIELDNO_MINIMALTUPLETABLESLOT_OFF 4
     uint32        off;            /* saved state for slot_deform_heap_tuple */
-} MinimalTupleTableSlot;
+} MinimalTupleTableSlot pg_node_attr(abstract);

 /*
  * TupIsNull -- is a TupleTableSlot empty?
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..9706828ef4 100644
--- a/src/include/nodes/extensible.h
+++ b/src/include/nodes/extensible.h
@@ -33,7 +33,7 @@ typedef struct ExtensibleNode
 {
     NodeTag        type;
     const char *extnodename;    /* identifier of ExtensibleNodeMethods */
-} ExtensibleNode;
+} ExtensibleNode pg_node_attr(custom_copy_equal, custom_read_write);

 /*
  * node_size is the size of an extensible node of this type in bytes.
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 9255ce467e..83aa6c53bd 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -27,6 +27,9 @@ typedef enum NodeTag
 {
     T_Invalid = 0,

+#include "nodes/nodetags.h"
+#ifdef OBSOLETE
+
     /*
      * TAGS FOR EXECUTOR NODES (execnodes.h)
      */
@@ -561,8 +564,72 @@ 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 at the end of the struct or typedef, just before the
+ * semicolon) 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: Does not support copyObject() at all.
+ *
+ * - no_equal: Does not support equal() at all.
+ *
+ * - no_copy_equal: Shorthand for both no_copy and no_equal.
+ *
+ * - no_read: Does not support nodeRead() at all.
+ *
+ * - special_read_write: Has special treatment in outNode() and nodeRead().
+ *
+ * Node types can be supertypes of other types whether or not they are marked
+ * abstract: if a node struct appears as the first field of another struct
+ * type, then it is the supertype of that type.  The no_copy, no_equal,
+ * no_copy_equal, and no_read node attributes are automatically inherited
+ * from the supertype.
+ *
+ * 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_as(VALUE): In copyObject(), replace the field's value with VALUE.
+ *
+ * - equal_ignore: Ignore the field for equality.
+ *
+ * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
+ *   (Otherwise, compare normally.)
+ *
+ * - read_as(VALUE): In nodeRead(), replace the field's value with VALUE.
+ *
+ * - read_write_ignore: Ignore the field for read/write.  This is only allowed
+ *   if the node type is marked no_read or read_as() is also specified.
+ *
+ * - 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 5f6d65b5c4..8451a51749 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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, read_as(0));

     bool        canSetTag;        /* do I set the command result tag? */

@@ -198,7 +201,7 @@ typedef struct Query
      */
     int            stmt_location;    /* start location, or -1 if unknown */
     int            stmt_len;        /* length in bytes; 0 means "rest of string" */
-} Query;
+} Query        pg_node_attr(custom_read_write);


 /****************************************************************************
@@ -294,7 +297,7 @@ typedef struct A_Expr
     Node       *lexpr;            /* left argument, or NULL if none */
     Node       *rexpr;            /* right argument, or NULL if none */
     int            location;        /* token location, or -1 if unknown */
-} A_Expr;
+} A_Expr    pg_node_attr(custom_read_write, no_read);

 /*
  * A_Const - a literal constant
@@ -318,7 +321,7 @@ typedef struct A_Const
     }            val;
     bool        isnull;            /* SQL NULL constant */
     int            location;        /* token location, or -1 if unknown */
-} A_Const;
+} A_Const    pg_node_attr(custom_copy_equal, custom_read_write, no_read);

 /*
  * TypeCast - a CAST expression
@@ -401,7 +404,7 @@ typedef struct FuncCall
 typedef struct A_Star
 {
     NodeTag        type;
-} A_Star;
+} A_Star    pg_node_attr(no_read);

 /*
  * A_Indices - array subscript or slice bounds ([idx] or [lidx:uidx])
@@ -1171,7 +1174,7 @@ typedef struct RangeTblEntry
     Bitmapset  *updatedCols;    /* columns needing UPDATE permission */
     Bitmapset  *extraUpdatedCols;    /* generated columns being updated */
     List       *securityQuals;    /* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RangeTblEntry pg_node_attr(custom_read_write);

 /*
  * RangeTblFunction -
@@ -2658,7 +2661,7 @@ typedef struct Constraint
     /* Fields used for constraints that allow a NOT VALID specification */
     bool        skip_validation;    /* skip validation of existing rows? */
     bool        initially_valid;    /* mark the new constraint as valid? */
-} Constraint;
+} Constraint pg_node_attr(custom_read_write, no_read);

 /* ----------------------
  *        Create/Drop Table Space Statements
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index a42333cb92..6193126d20 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,6 +87,9 @@ 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
@@ -92,13 +97,13 @@ typedef struct PlannerGlobal
     NodeTag        type;

     /* Param values provided to planner() */
-    ParamListInfo boundParams;
+    ParamListInfo boundParams pg_node_attr(read_write_ignore);

     /* Plans for SubPlan nodes */
     List       *subplans;

     /* PlannerInfos for SubPlan nodes */
-    List       *subroots;
+    List       *subroots pg_node_attr(read_write_ignore);

     /* indices of subplans that require REWIND */
     Bitmapset  *rewindPlanIDs;
@@ -149,8 +154,8 @@ typedef struct PlannerGlobal
     char        maxParallelHazard;

     /* partition descriptors */
-    PartitionDirectory partition_directory;
-} PlannerGlobal;
+    PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
+} PlannerGlobal pg_node_attr(no_copy_equal, no_read);

 /* macro for fetching the Plan associated with a SubPlan node */
 #define planner_subplan_get_plan(root, subplan) \
@@ -168,6 +173,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
@@ -189,7 +197,7 @@ struct PlannerInfo
     Index        query_level;

     /* NULL at outermost Query */
-    PlannerInfo *parent_root;
+    PlannerInfo *parent_root pg_node_attr(read_write_ignore);

     /*
      * plan_params contains the expressions that this query level needs to
@@ -208,16 +216,16 @@ 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;
+    struct RelOptInfo **simple_rel_array pg_node_attr(read_write_ignore);
     /* allocated size of array */
-    int            simple_rel_array_size;
+    int            simple_rel_array_size pg_node_attr(read_write_ignore);

     /*
      * 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;
+    RangeTblEntry **simple_rte_array pg_node_attr(read_write_ignore);

     /*
      * append_rel_array is the same length as the above arrays, and holds
@@ -225,7 +233,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"
@@ -253,7 +261,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]
@@ -263,7 +271,7 @@ struct PlannerInfo
      * join_rel_level is NULL if not in use.
      */
     /* lists of join-relation RelOptInfos */
-    List      **join_rel_level;
+    List      **join_rel_level pg_node_attr(read_write_ignore);
     /* index of list being extended */
     int            join_cur_level;

@@ -355,19 +363,19 @@ struct PlannerInfo
     List       *sort_pathkeys;

     /* Canonicalised partition schemes used in the query. */
-    List       *part_schemes;
+    List       *part_schemes pg_node_attr(read_write_ignore);

     /* RelOptInfos we are now trying to join */
-    List       *initial_rels;
+    List       *initial_rels pg_node_attr(read_write_ignore);

     /*
      * Upper-rel RelOptInfos. Use fetch_upper_rel() to get any particular
      * upper rel.
      */
-    List       *upper_rels[UPPERREL_FINAL + 1];
+    List       *upper_rels[UPPERREL_FINAL + 1] pg_node_attr(read_write_ignore);

     /* 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
@@ -392,12 +400,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);

     /* # of pages in all non-dummy tables of query */
     Cardinality total_table_pages;
@@ -430,15 +438,15 @@ struct PlannerInfo
      * Information about aggregates. Filled by preprocess_aggrefs().
      */
     /* AggInfo structs */
-    List       *agginfos;
+    List       *agginfos pg_node_attr(read_write_ignore);
     /* AggTransInfo structs */
-    List       *aggtransinfos;
+    List       *aggtransinfos pg_node_attr(read_write_ignore);
     /* number w/ DISTINCT/ORDER BY/WITHIN GROUP */
-    int            numOrderedAggs;
+    int            numOrderedAggs pg_node_attr(read_write_ignore);
     /* does any agg not support partial mode? */
-    bool        hasNonPartialAggs;
+    bool        hasNonPartialAggs pg_node_attr(read_write_ignore);
     /* is any partial agg non-serializable? */
-    bool        hasNonSerialAggs;
+    bool        hasNonSerialAggs pg_node_attr(read_write_ignore);

     /*
      * These fields are used only when hasRecursion is true:
@@ -446,7 +454,7 @@ struct PlannerInfo
     /* PARAM_EXEC ID for the work table */
     int            wt_param_id;
     /* a path for non-recursive term */
-    struct Path *non_recursive_path;
+    struct Path *non_recursive_path pg_node_attr(read_write_ignore);

     /*
      * These fields are workspace for createplan.c
@@ -460,15 +468,15 @@ 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;
-};
+}            pg_node_attr(no_copy_equal, no_read);


 /*
@@ -721,6 +729,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.)
  *----------
  */

@@ -829,9 +840,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 */
@@ -866,16 +877,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:
@@ -903,24 +916,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
@@ -930,10 +943,10 @@ 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;
-} RelOptInfo;
+    List      **nullable_partexprs pg_node_attr(read_write_ignore);
+} RelOptInfo pg_node_attr(no_copy_equal, no_read);

 /*
  * Is given relation partitioned?
@@ -999,8 +1012,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)
@@ -1020,31 +1033,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;

@@ -1071,20 +1092,20 @@ 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 */
-};
+}            pg_node_attr(no_copy_equal, no_read);

 /*
  * ForeignKeyOptInfo
@@ -1109,11 +1130,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:
@@ -1133,7 +1154,7 @@ typedef struct ForeignKeyOptInfo
     struct EquivalenceMember *fk_eclass_member[INDEX_MAX_KEYS];
     /* List of non-EC RestrictInfos matching each column's condition */
     List       *rinfos[INDEX_MAX_KEYS];
-} ForeignKeyOptInfo;
+} ForeignKeyOptInfo pg_node_attr(custom_read_write, no_copy_equal, no_read);

 /*
  * StatisticExtInfo
@@ -1150,10 +1171,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;
@@ -1163,7 +1187,7 @@ typedef struct StatisticExtInfo

     /* expressions */
     List       *exprs;
-} StatisticExtInfo;
+} StatisticExtInfo pg_node_attr(no_copy_equal, no_read);

 /*
  * EquivalenceClasses
@@ -1204,6 +1228,10 @@ 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.
+ *
+ * NB: EquivalenceClasses are never copied after creation.  Therefore,
+ * copyObject() copies pointers to them as pointers, and equal() compares
+ * pointers to EquivalenceClasses via pointer equality.
  */
 typedef struct EquivalenceClass
 {
@@ -1224,7 +1252,7 @@ typedef struct EquivalenceClass
     Index        ec_min_security;    /* minimum security_level in ec_sources */
     Index        ec_max_security;    /* maximum security_level in ec_sources */
     struct EquivalenceClass *ec_merged; /* set if merged into another EC */
-} EquivalenceClass;
+} EquivalenceClass pg_node_attr(custom_read_write, no_copy_equal, no_read);

 /*
  * If an EC contains a const and isn't below-outer-join, any PathKey depending
@@ -1265,7 +1293,7 @@ typedef struct EquivalenceMember
     bool        em_is_const;    /* expression is pseudoconstant? */
     bool        em_is_child;    /* derived version for a child relation? */
     Oid            em_datatype;    /* the "nominal type" used by the opfamily */
-} EquivalenceMember;
+} EquivalenceMember pg_node_attr(no_copy_equal, no_read);

 /*
  * PathKeys
@@ -1292,7 +1320,7 @@ typedef struct PathKey
     Oid            pk_opfamily;    /* btree opfamily defining the ordering */
     int            pk_strategy;    /* sort direction (ASC or DESC) */
     bool        pk_nulls_first; /* do NULLs come before normal values? */
-} PathKey;
+} PathKey    pg_node_attr(no_read);

 /*
  * Combines information about pathkeys and the associated clauses.
@@ -1302,7 +1330,7 @@ typedef struct PathKeyInfo
     NodeTag        type;
     List       *pathkeys;
     List       *clauses;
-} PathKeyInfo;
+} PathKeyInfo pg_node_attr(no_read);

 /*
  * VolatileFunctionStatus -- allows nodes to cache their
@@ -1347,7 +1375,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;
@@ -1357,7 +1385,7 @@ typedef struct PathTarget

     /* indicates if exprs contain any volatile functions */
     VolatileFunctionStatus has_volatile_expr;
-} PathTarget;
+} PathTarget pg_node_attr(no_copy_equal, no_read);

 /* Convenience macro to get a sort/group refno from a PathTarget */
 #define get_pathtarget_sortgroupref(target, colno) \
@@ -1385,7 +1413,7 @@ typedef struct ParamPathInfo
     Relids        ppi_req_outer;    /* rels supplying parameters used by path */
     Cardinality ppi_rows;        /* estimated number of result tuples */
     List       *ppi_clauses;    /* join clauses available from outer rels */
-} ParamPathInfo;
+} ParamPathInfo pg_node_attr(no_copy_equal, no_read);


 /*
@@ -1416,6 +1444,10 @@ 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
 {
@@ -1424,14 +1456,29 @@ typedef struct Path
     /* 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;
@@ -1447,7 +1494,7 @@ typedef struct Path

     /* sort ordering of path's output; a List of PathKey nodes; see above */
     List       *pathkeys;
-} Path;
+} Path        pg_node_attr(no_copy_equal, no_read);

 /* Macro for extracting a path's parameterization relids; beware double eval */
 #define PATH_REQ_OUTER(path)  \
@@ -1545,7 +1592,7 @@ typedef struct IndexClause
     bool        lossy;            /* are indexquals a lossy version of clause? */
     AttrNumber    indexcol;        /* index column the clause uses (zero-based) */
     List       *indexcols;        /* multiple index columns, if RowCompare */
-} IndexClause;
+} IndexClause pg_node_attr(no_copy_equal, no_read);

 /*
  * BitmapHeapPath represents one or more indexscans that generate TID bitmaps
@@ -1851,7 +1898,7 @@ typedef struct JoinPath
      * joinrestrictinfo is needed in JoinPath, and can't be merged into the
      * parent RelOptInfo.
      */
-} JoinPath;
+} JoinPath    pg_node_attr(abstract);

 /*
  * A nested-loop path needs no special fields.
@@ -2039,7 +2086,7 @@ typedef struct GroupingSetData
     NodeTag        type;
     List       *set;            /* grouping set as list of sortgrouprefs */
     Cardinality numGroups;        /* est. number of result groups */
-} GroupingSetData;
+} GroupingSetData pg_node_attr(no_copy_equal, no_read);

 typedef struct RollupData
 {
@@ -2050,7 +2097,7 @@ typedef struct RollupData
     Cardinality numGroups;        /* est. number of result groups */
     bool        hashable;        /* can be hashed */
     bool        is_hashed;        /* to be implemented as a hashagg */
-} RollupData;
+} RollupData pg_node_attr(no_copy_equal, no_read);

 /*
  * GroupingSetsPath represents a GROUPING SETS aggregation
@@ -2306,6 +2353,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
@@ -2322,22 +2375,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;
@@ -2352,85 +2405,90 @@ 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; instead replace with NIL.  That has the effect that copying will
+     * just reset the cache.  Likewise, can't compare or print them.
+     */
+    List       *scansel_cache pg_node_attr(copy_as(NIL), equal_ignore, read_write_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;
-} RestrictInfo;
+    Oid            left_hasheqoperator pg_node_attr(equal_ignore);
+    Oid            right_hasheqoperator pg_node_attr(equal_ignore);
+} RestrictInfo pg_node_attr(no_read);

 /*
  * This macro embodies the correct way to test whether a RestrictInfo is
@@ -2479,6 +2537,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
@@ -2486,10 +2555,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;
@@ -2575,7 +2644,7 @@ struct SpecialJoinInfo
     bool        semi_can_hash;    /* true if semi_operators are all hash */
     List       *semi_operators; /* OIDs of equality join operators */
     List       *semi_rhs_exprs; /* righthand-side expressions of these ops */
-};
+}            pg_node_attr(no_read);

 /*
  * Append-relation info.
@@ -2654,7 +2723,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
@@ -2690,7 +2759,7 @@ typedef struct RowIdentityVarInfo
     int32        rowidwidth;        /* estimated average width */
     char       *rowidname;        /* name of the resjunk column */
     Relids        rowidrels;        /* RTE indexes of target rels using this */
-} RowIdentityVarInfo;
+} RowIdentityVarInfo pg_node_attr(no_copy_equal, no_read);

 /*
  * For each distinct placeholder expression generated during planning, we
@@ -2725,7 +2794,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 */
@@ -2739,7 +2811,7 @@ typedef struct PlaceHolderInfo

     /* estimated attribute width */
     int32        ph_width;
-} PlaceHolderInfo;
+} PlaceHolderInfo pg_node_attr(no_read);

 /*
  * This struct describes one potentially index-optimizable MIN/MAX aggregate
@@ -2759,8 +2831,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;
@@ -2770,7 +2845,7 @@ typedef struct MinMaxAggInfo

     /* param for subplan's output */
     Param       *param;
-} MinMaxAggInfo;
+} MinMaxAggInfo pg_node_attr(no_copy_equal, no_read);

 /*
  * At runtime, PARAM_EXEC slots are used to pass values around from one plan
@@ -2825,7 +2900,7 @@ typedef struct PlannerParamItem

     Node       *item;            /* the Var, PlaceHolderVar, or Aggref */
     int            paramId;        /* its assigned PARAM_EXEC slot number */
-} PlannerParamItem;
+} PlannerParamItem pg_node_attr(no_copy_equal, no_read);

 /*
  * When making cost estimates for a SEMI/ANTI/inner_unique join, there are
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d5c0ebe859..846977f443 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -38,6 +38,9 @@
  * nodes; in such cases, commandType == CMD_UTILITY, the statement itself
  * is in the utilityStmt field, and the rest of the struct is mostly dummy.
  * (We do use canSetTag, stmt_location, stmt_len, and possibly queryId.)
+ *
+ * PlannedStmt, as well as all varieties of Plan, do not support equal(),
+ * not because it's not sensible but because we currently have no need.
  * ----------------
  */
 typedef struct PlannedStmt
@@ -89,7 +92,7 @@ typedef struct PlannedStmt
     /* statement location in source string (copied from Query) */
     int            stmt_location;    /* start location, or -1 if unknown */
     int            stmt_len;        /* length in bytes; 0 means "rest of string" */
-} PlannedStmt;
+} PlannedStmt pg_node_attr(no_equal);

 /* macro for fetching the Plan associated with a SubPlan node */
 #define exec_subplan_get_plan(plannedstmt, subplan) \
@@ -159,7 +162,7 @@ typedef struct Plan
      */
     Bitmapset  *extParam;
     Bitmapset  *allParam;
-} Plan;
+} Plan        pg_node_attr(abstract, no_equal);

 /* ----------------
  *    these are defined to avoid confusion problems with "left"
@@ -286,16 +289,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 +325,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;
@@ -725,6 +728,12 @@ typedef struct CustomScan
     List       *custom_private; /* private data for custom code */
     List       *custom_scan_tlist;    /* optional tlist describing scan tuple */
     Bitmapset  *custom_relids;    /* RTIs generated by this scan */
+
+    /*
+     * NOTE: The method field of CustomScan is 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.
+     */
     const struct CustomScanMethods *methods;
 } CustomScan;

@@ -762,7 +771,7 @@ typedef struct Join
     JoinType    jointype;
     bool        inner_unique;
     List       *joinqual;        /* JOIN quals (in addition to plan.qual) */
-} Join;
+} Join        pg_node_attr(abstract);

 /* ----------------
  *        nest loop join node
@@ -786,7 +795,7 @@ typedef struct NestLoopParam
     NodeTag        type;
     int            paramno;        /* number of the PARAM_EXEC Param to set */
     Var           *paramval;        /* outer-relation Var to assign to Param */
-} NestLoopParam;
+} NestLoopParam pg_node_attr(no_equal);

 /* ----------------
  *        merge join node
@@ -812,16 +821,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 +872,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 +914,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 +950,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 +985,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 +1024,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 +1095,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 +1146,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 +1206,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 +1262,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;


@@ -1354,7 +1363,7 @@ typedef struct PlanRowMark
     LockClauseStrength strength;    /* LockingClause's strength, or LCS_NONE */
     LockWaitPolicy waitPolicy;    /* NOWAIT and SKIP LOCKED options */
     bool        isParent;        /* true if this is a "dummy" parent entry */
-} PlanRowMark;
+} PlanRowMark pg_node_attr(no_equal);


 /*
@@ -1392,7 +1401,7 @@ typedef struct PartitionPruneInfo
     NodeTag        type;
     List       *prune_infos;
     Bitmapset  *other_subplans;
-} PartitionPruneInfo;
+} PartitionPruneInfo pg_node_attr(no_equal);

 /*
  * PartitionedRelPruneInfo - Details required to allow the executor to prune
@@ -1425,13 +1434,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.,
@@ -1444,7 +1453,7 @@ typedef struct PartitionedRelPruneInfo

     /* All PARAM_EXEC Param IDs in exec_pruning_steps */
     Bitmapset  *execparamids;
-} PartitionedRelPruneInfo;
+} PartitionedRelPruneInfo pg_node_attr(no_equal);

 /*
  * Abstract Node type for partition pruning steps (there are no concrete
@@ -1456,7 +1465,7 @@ typedef struct PartitionPruneStep
 {
     NodeTag        type;
     int            step_id;
-} PartitionPruneStep;
+} PartitionPruneStep pg_node_attr(abstract);

 /*
  * PartitionPruneStepOp - Information to prune using a set of mutually ANDed
@@ -1493,7 +1502,7 @@ typedef struct PartitionPruneStepOp
     List       *exprs;
     List       *cmpfns;
     Bitmapset  *nullkeys;
-} PartitionPruneStepOp;
+} PartitionPruneStepOp pg_node_attr(no_equal);

 /*
  * PartitionPruneStepCombine - Information to prune using a BoolExpr clause
@@ -1513,7 +1522,7 @@ typedef struct PartitionPruneStepCombine

     PartitionPruneCombineOp combineOp;
     List       *source_stepids;
-} PartitionPruneStepCombine;
+} PartitionPruneStepCombine pg_node_attr(no_equal);


 /*
@@ -1530,7 +1539,7 @@ typedef struct PlanInvalItem
     NodeTag        type;
     int            cacheId;        /* a syscache ID, see utils/syscache.h */
     uint32        hashValue;        /* hash value of object's cache lookup key */
-} PlanInvalItem;
+} PlanInvalItem pg_node_attr(no_equal);

 /*
  * MonotonicFunction
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 732c00c098..fd22fe19b2 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, read_as(NULL));

     /* the schema name, or NULL */
     char       *schemaname;
@@ -158,7 +161,7 @@ typedef struct IntoClause
 typedef struct Expr
 {
     NodeTag        type;
-} Expr;
+} Expr        pg_node_attr(abstract);

 /*
  * Var - expression node representing a variable (ie, a table column)
@@ -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;
@@ -265,7 +273,7 @@ typedef struct Const
                                  * in the Datum. If false, then the Datum
                                  * contains a pointer to the information. */
     int            location;        /* token location, or -1 if unknown */
-} Const;
+} Const        pg_node_attr(custom_copy_equal, custom_read_write);

 /*
  * Param
@@ -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;
@@ -625,6 +636,7 @@ typedef struct NamedArgExpr
  * Note that opfuncid is not necessarily filled in immediately on creation
  * of the node.  The planner makes sure it is valid before passing the node
  * tree to the executor, but during parsing/planning opfuncid can be 0.
+ * Therefore, equal() will accept a zero value as being equal to other values.
  */
 typedef struct OpExpr
 {
@@ -634,7 +646,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 +710,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 +723,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;
@@ -746,7 +762,7 @@ typedef struct BoolExpr
     BoolExprType boolop;
     List       *args;            /* arguments to this expression */
     int            location;        /* token location, or -1 if unknown */
-} BoolExpr;
+} BoolExpr    pg_node_attr(custom_read_write);

 /*
  * SubLink
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index eaf937051c..20347d39dd 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -29,7 +29,7 @@ typedef struct Integer
 {
     NodeTag        type;
     int            ival;
-} Integer;
+} Integer    pg_node_attr(special_read_write);

 /*
  * Float is internally represented as string.  Using T_Float as the node type
@@ -46,25 +46,25 @@ typedef struct Float
 {
     NodeTag        type;
     char       *fval;
-} Float;
+} Float        pg_node_attr(special_read_write);

 typedef struct Boolean
 {
     NodeTag        type;
     bool        boolval;
-} Boolean;
+} Boolean    pg_node_attr(special_read_write);

 typedef struct String
 {
     NodeTag        type;
     char       *sval;
-} String;
+} String    pg_node_attr(special_read_write);

 typedef struct BitString
 {
     NodeTag        type;
     char       *bsval;
-} BitString;
+} BitString pg_node_attr(special_read_write);

 #define intVal(v)        (castNode(Integer, v)->ival)
 #define floatVal(v)        atof(castNode(Float, v)->fval)
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b741105d1e..075a2669fd 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -282,12 +282,12 @@ typedef struct ForeignKeyCacheInfo
      * these arrays each have nkeys valid entries:
      */
     /* 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];
-} ForeignKeyCacheInfo;
+    Oid            conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
+} ForeignKeyCacheInfo pg_node_attr(no_equal, no_read);


 /*
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 52ff56ba83..42ead5f789 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -839,6 +839,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;
diff --git a/src/tools/pgindent/exclude_file_patterns b/src/tools/pgindent/exclude_file_patterns
index f08180b0d0..f5c8857e31 100644
--- a/src/tools/pgindent/exclude_file_patterns
+++ b/src/tools/pgindent/exclude_file_patterns
@@ -7,6 +7,11 @@ src/include/port/atomics/
 # This contains C++ constructs that confuse pgindent.
 src/include/jit/llvmjit\.h$
 #
+# These are generated files with incomplete code fragments that
+# confuse pgindent.
+src/backend/nodes/\w+\.funcs\.c$
+src/backend/nodes/\w+\.switch\.c$
+#
 # This confuses pgindent, and it's a derived file anyway.
 src/backend/utils/fmgrtab\.c$
 #

base-commit: bf1f4a364d6c72cc5c39a6d81d156a0335fdf332
--
2.36.1

diff --git a/src/backend/nodes/README b/src/backend/nodes/README
index 2d6a7bcf7a..b3dc9afaf7 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -6,15 +6,16 @@ Node Structures
 Introduction
 ------------

-The node structures are plain old C structures with the first field of
-type NodeTag.  "Inheritance" is achieved by convention: The first
-field can alternatively be of another node type.  Functions that
-manipulate node structures reside in this directory.  Some support
-functions are automatically generated by the gen_node_support.pl
-script, other functions are maintained manually.  To control the
-automatic generation of some support functions, node types and node
-fields can be annotated with pg_node_attr() specifications; see
-further documentation in src/include/nodes/nodes.h.
+The node structures are plain old C structures with the first field
+being of type NodeTag.  "Inheritance" is achieved by convention:
+the first field can alternatively be of another node type.
+
+Utility functions for manipulating node structures reside in this
+directory.  Some support functions are automatically generated by the
+gen_node_support.pl script, other functions are maintained manually.
+To control the automatic generation of support functions, node types
+and node fields can be annotated with pg_node_attr() specifications;
+see further documentation in src/include/nodes/nodes.h.


 FILES IN THIS DIRECTORY (src/backend/nodes/)
@@ -60,14 +61,17 @@ Suppose you want to define a node Foo:
 1. Add the structure definition to the appropriate include/nodes/???.h file.
    If you intend to inherit from, say a Plan node, put Plan as the first field
    of your struct definition.  (The T_Foo tag is created automatically.)
-2. Check that the generated support functions in copyfuncs.c, equalfuncs.c,
-   outfuncs.c and readfuncs.c look correct.  Add attributes as necessary to
-   control the outcome.  (Except for frequently used nodes, don't bother
-   writing a creator function in makefuncs.c)
+2. Check that the generated support functions in copyfuncs.funcs.c,
+   equalfuncs.funcs.c, outfuncs.funcs.c and readfuncs.funcs.c look
+   correct.  Add attributes as necessary to control the outcome.  (For
+   some classes of node types, you don't need all four support functions.
+   Use node attributes similar to those of related node types.)
 3. Add cases to the functions in nodeFuncs.c as needed.  There are many
    other places you'll probably also need to teach about your new node
    type.  Best bet is to grep for references to one or two similar existing
    node types to find all the places to touch.
+   (Except for frequently-created nodes, don't bother writing a creator
+   function in makefuncs.c.)
 4. Consider testing your new code with COPY_PARSE_PLAN_TREES,
    WRITE_READ_PARSE_PLAN_TREES, and RAW_EXPRESSION_COVERAGE_TEST to ensure
    support has been added everywhere that it's necessary; see
@@ -79,4 +83,6 @@ tags, so you'll need to recompile the whole tree after doing this.
 because the numbers never go to disk.  But altering or removing a node
 type should usually be accompanied by an initdb-forcing catalog
 version change, since the interpretation of serialized node trees
-stored in system catalogs is affected by that.
+stored in system catalogs is affected by that.  (If the node type
+never appears in stored parse trees, as for example Plan nodes do not,
+then a catversion change is not needed to change it.)
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 86af4bf032..78c7f27cda 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -58,10 +58,11 @@ my @scalar_types = qw(
 # collect enum types
 my @enum_types;

+# collect types that are abstract (hence no node tag, no support functions)
 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
+# is not in a header file.  We generate node tags for them, but
 # they otherwise don't participate in node support.
 my @extra_tags = qw(
     IntList OidList XidList
@@ -73,7 +74,9 @@ my @extra_tags = qw(
 # 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().
+# Lists are specially treated in all four support files, too.
+push @no_copy, qw(List);
+push @no_equal, qw(List);
 push @no_read_write, qw(List);

 # Nodes with custom copy/equal implementations are skipped from
@@ -262,8 +265,8 @@ foreach my $infile (@ARGV)
                     $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.
+                    # Nodes from these files don't need support functions,
+                    # just 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))
@@ -424,7 +427,6 @@ foreach my $n (@node_types)
     my $struct_no_copy = (elem $n, @no_copy);
     my $struct_no_equal = (elem $n, @no_equal);
     next if $struct_no_copy && $struct_no_equal;
-    next if $n eq 'List';

     print $cfs "\t\tcase T_${n}:\n".
       "\t\t\tretval = _copy${n}(from);\n".
@@ -463,11 +465,11 @@ _equal${n}(const $n *a, const $n *b)
         my $copy_as_field;
         foreach my $a (@a)
         {
-            if ($a =~ /^array_size.([\w.]+)/)
+            if ($a =~ /^array_size\(([\w.]+)\)$/)
             {
                 $array_size_field = $1;
             }
-            elsif ($a =~ /^copy_as.([\w.]+)/)
+            elsif ($a =~ /^copy_as\(([\w.]+)\)$/)
             {
                 $copy_as_field = $1;
             }
@@ -478,7 +480,7 @@ _equal${n}(const $n *a, const $n *b)
         }

         # override type-specific copy method if copy_as is specified
-        if ($copy_as_field)
+        if (defined $copy_as_field)
         {
             print $cff "\tnewnode->$f = $copy_as_field;\n" unless $copy_ignore;
             $copy_ignore = 1;
@@ -509,6 +511,7 @@ _equal${n}(const $n *a, const $n *b)
             }
             else
             {
+                # All CoercionForm fields are treated as equal_ignore
                 print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
             }
         }
@@ -516,7 +519,7 @@ _equal${n}(const $n *a, const $n *b)
         elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
         {
             my $tt = $1;
-            if (!$array_size_field)
+            if (!defined $array_size_field)
             {
                 die "no array size defined for $n.$f of type $t";
             }
@@ -595,7 +598,7 @@ foreach my $n (@node_types)
         next unless elem $n, @keep;
     }

-    my $struct_no_read = (elem $n, @no_read);
+    my $no_read = (elem $n, @no_read);

     # output format starts with upper case node type name
     my $N = uc $n;
@@ -605,7 +608,7 @@ foreach my $n (@node_types)
       "\t\t\t\tbreak;\n";

     print $rfs "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
-      "\t\treturn_value = _read${n}();\n" unless $struct_no_read;
+      "\t\treturn_value = _read${n}();\n" unless $no_read;

     next if elem $n, @custom_read_write;

@@ -623,21 +626,20 @@ _read${n}(void)
 {
 \tREAD_LOCALS($n);

-" unless $struct_no_read;
+" unless $no_read;

     # print instructions for each field
     foreach my $f (@{$node_type_info{$n}->{fields}})
     {
         my $t = $node_type_info{$n}->{field_types}{$f};
         my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
-        my $no_read = $struct_no_read;

         # extract per-field attributes
         my $read_write_ignore = 0;
         my $read_as_field;
         foreach my $a (@a)
         {
-            if ($a =~ /^read_as.([\w.]+)/)
+            if ($a =~ /^read_as\(([\w.]+)\)$/)
             {
                 $read_as_field = $1;
             }
@@ -647,17 +649,18 @@ _read${n}(void)
             }
         }

-        # override type-specific read method if read_as is specified
-        if ($read_as_field)
-        {
-            print $rff "\tlocal_node->$f = $read_as_field;\n" unless $no_read;
-            $no_read = 1;
-        }
-
-        # check this after handling read_as
         if ($read_write_ignore)
         {
+            # nothing to do if no_read
             next if $no_read;
+            # for read_write_ignore with read_as(), emit the appropriate
+            # assignment on the read side and move on.
+            if (defined $read_as_field)
+            {
+                print $rff "\tlocal_node->$f = $read_as_field;\n";
+                next;
+            }
+            # else, bad specification
             die "$n.$f must not be marked read_write_ignore\n";
         }

@@ -751,13 +754,13 @@ _read${n}(void)
             my $array_size_field;
             foreach my $a (@a)
             {
-                if ($a =~ /^array_size.([\w.]+)/)
+                if ($a =~ /^array_size\(([\w.]+)\)$/)
                 {
                     $array_size_field = $1;
                     last;
                 }
             }
-            if (!$array_size_field)
+            if (!defined $array_size_field)
             {
                 die "no array size defined for $n.$f of type $t";
             }
@@ -822,6 +825,13 @@ _read${n}(void)
         {
             die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
         }
+
+        # for read_as() without read_write_ignore, we have to read the value
+        # that outfuncs.c wrote and then overwrite it.
+        if (defined $read_as_field)
+        {
+            print $rff "\tlocal_node->$f = $read_as_field;\n" unless $no_read;
+        }
     }

     print $off "}
@@ -829,7 +839,7 @@ _read${n}(void)
     print $rff "
 \tREAD_DONE();
 }
-" unless $struct_no_read;
+" unless $no_read;
 }

 close $off;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 83aa6c53bd..bf24248405 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -602,9 +602,8 @@ typedef enum NodeTag
  *
  * Node types can be supertypes of other types whether or not they are marked
  * abstract: if a node struct appears as the first field of another struct
- * type, then it is the supertype of that type.  The no_copy, no_equal,
- * no_copy_equal, and no_read node attributes are automatically inherited
- * from the supertype.
+ * type, then it is the supertype of that type.  The no_copy, no_equal, and
+ * no_read node attributes are automatically inherited from the supertype.
  *
  * Valid node field attributes:
  *
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 78c7f27cda..f6625a763e 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -122,6 +122,7 @@ foreach my $infile (@ARGV)
     my $supertype;
     my $supertype_field;

+    my $node_attrs = '';
     my @my_fields;
     my %my_field_types;
     my %my_field_attrs;
@@ -151,9 +152,18 @@ foreach my $infile (@ARGV)
                 $is_node_struct = 0;
                 $supertype = undef;
                 next if $line eq '{';
-                die;
+                die "$infile:$.: expected opening brace\n";
             }
-            # second line should have node tag or supertype
+            # second line could be node attributes
+            elsif ($subline == 2 &&
+                   $line =~ /^\s*pg_node_attr\(([\w(), ]*)\)$/)
+            {
+                $node_attrs = $1;
+                # hack: don't count the line
+                $subline--;
+                next;
+            }
+            # next line should have node tag or supertype
             elsif ($subline == 2)
             {
                 if ($line =~ /^\s*NodeTag\s+type;/)
@@ -171,10 +181,8 @@ foreach my $infile (@ARGV)
             }

             # end of struct
-            if ($line =~ /^\}\s*(?:\Q$in_struct\E\s*)?(?:pg_node_attr\(([\w(), ]*)\))?;$/)
+            if ($line =~ /^\}\s*(?:\Q$in_struct\E\s*)?;$/)
             {
-                my $node_attrs = $1 || '';
-
                 if ($is_node_struct)
                 {
                     # This is the end of a node struct definition.
@@ -287,6 +295,7 @@ foreach my $infile (@ARGV)

                 # start new cycle
                 $in_struct = undef;
+                $node_attrs = '';
                 @my_fields = ();
                 %my_field_types = ();
                 %my_field_attrs = ();
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 9ead14b651..a001e448ba 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -237,13 +237,17 @@ extern PGDLLIMPORT const TupleTableSlotOps TTSOpsBufferHeapTuple;

 typedef struct VirtualTupleTableSlot
 {
+    pg_node_attr(abstract)
+
     TupleTableSlot base;

     char       *data;            /* data for materialized slots */
-} VirtualTupleTableSlot pg_node_attr(abstract);
+} VirtualTupleTableSlot;

 typedef struct HeapTupleTableSlot
 {
+    pg_node_attr(abstract)
+
     TupleTableSlot base;

 #define FIELDNO_HEAPTUPLETABLESLOT_TUPLE 1
@@ -251,11 +255,13 @@ typedef struct HeapTupleTableSlot
 #define FIELDNO_HEAPTUPLETABLESLOT_OFF 2
     uint32        off;            /* saved state for slot_deform_heap_tuple */
     HeapTupleData tupdata;        /* optional workspace for storing tuple */
-} HeapTupleTableSlot pg_node_attr(abstract);
+} HeapTupleTableSlot;

 /* heap tuple residing in a buffer */
 typedef struct BufferHeapTupleTableSlot
 {
+    pg_node_attr(abstract)
+
     HeapTupleTableSlot base;

     /*
@@ -265,10 +271,12 @@ typedef struct BufferHeapTupleTableSlot
      * such a case, since presumably tts_tuple is pointing into the buffer.)
      */
     Buffer        buffer;            /* tuple's buffer, or InvalidBuffer */
-} BufferHeapTupleTableSlot pg_node_attr(abstract);
+} BufferHeapTupleTableSlot;

 typedef struct MinimalTupleTableSlot
 {
+    pg_node_attr(abstract)
+
     TupleTableSlot base;

     /*
@@ -284,7 +292,7 @@ typedef struct MinimalTupleTableSlot
     HeapTupleData minhdr;        /* workspace for minimal-tuple-only case */
 #define FIELDNO_MINIMALTUPLETABLESLOT_OFF 4
     uint32        off;            /* saved state for slot_deform_heap_tuple */
-} MinimalTupleTableSlot pg_node_attr(abstract);
+} MinimalTupleTableSlot;

 /*
  * TupIsNull -- is a TupleTableSlot empty?
diff --git a/src/include/nodes/extensible.h b/src/include/nodes/extensible.h
index 9706828ef4..34936db894 100644
--- a/src/include/nodes/extensible.h
+++ b/src/include/nodes/extensible.h
@@ -31,9 +31,11 @@
  */
 typedef struct ExtensibleNode
 {
+    pg_node_attr(custom_copy_equal, custom_read_write)
+
     NodeTag        type;
     const char *extnodename;    /* identifier of ExtensibleNodeMethods */
-} ExtensibleNode pg_node_attr(custom_copy_equal, custom_read_write);
+} ExtensibleNode;

 /*
  * node_size is the size of an extensible node of this type in bytes.
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8451a51749..0b6a7bb365 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -117,6 +117,8 @@ typedef uint32 AclMode;            /* a bitmask of privilege bits */
  */
 typedef struct Query
 {
+    pg_node_attr(custom_read_write)
+
     NodeTag        type;

     CmdType        commandType;    /* select|insert|update|delete|merge|utility */
@@ -201,7 +203,7 @@ typedef struct Query
      */
     int            stmt_location;    /* start location, or -1 if unknown */
     int            stmt_len;        /* length in bytes; 0 means "rest of string" */
-} Query        pg_node_attr(custom_read_write);
+} Query;


 /****************************************************************************
@@ -291,19 +293,23 @@ typedef enum A_Expr_Kind

 typedef struct A_Expr
 {
+    pg_node_attr(custom_read_write, no_read)
+
     NodeTag        type;
     A_Expr_Kind kind;            /* see above */
     List       *name;            /* possibly-qualified name of operator */
     Node       *lexpr;            /* left argument, or NULL if none */
     Node       *rexpr;            /* right argument, or NULL if none */
     int            location;        /* token location, or -1 if unknown */
-} A_Expr    pg_node_attr(custom_read_write, no_read);
+} A_Expr;

 /*
  * A_Const - a literal constant
  */
 typedef struct A_Const
 {
+    pg_node_attr(custom_copy_equal, custom_read_write, no_read)
+
     NodeTag        type;

     /*
@@ -321,7 +327,7 @@ typedef struct A_Const
     }            val;
     bool        isnull;            /* SQL NULL constant */
     int            location;        /* token location, or -1 if unknown */
-} A_Const    pg_node_attr(custom_copy_equal, custom_read_write, no_read);
+} A_Const;

 /*
  * TypeCast - a CAST expression
@@ -403,8 +409,10 @@ typedef struct FuncCall
  */
 typedef struct A_Star
 {
+    pg_node_attr(no_read)
+
     NodeTag        type;
-} A_Star    pg_node_attr(no_read);
+} A_Star;

 /*
  * A_Indices - array subscript or slice bounds ([idx] or [lidx:uidx])
@@ -1015,6 +1023,8 @@ typedef enum RTEKind

 typedef struct RangeTblEntry
 {
+    pg_node_attr(custom_read_write)
+
     NodeTag        type;

     RTEKind        rtekind;        /* see above */
@@ -1174,7 +1184,7 @@ typedef struct RangeTblEntry
     Bitmapset  *updatedCols;    /* columns needing UPDATE permission */
     Bitmapset  *extraUpdatedCols;    /* generated columns being updated */
     List       *securityQuals;    /* security barrier quals to apply, if any */
-} RangeTblEntry pg_node_attr(custom_read_write);
+} RangeTblEntry;

 /*
  * RangeTblFunction -
@@ -2611,6 +2621,8 @@ typedef enum ConstrType            /* types of constraints */

 typedef struct Constraint
 {
+    pg_node_attr(custom_read_write, no_read)
+
     NodeTag        type;
     ConstrType    contype;        /* see above */

@@ -2661,7 +2673,7 @@ typedef struct Constraint
     /* Fields used for constraints that allow a NOT VALID specification */
     bool        skip_validation;    /* skip validation of existing rows? */
     bool        initially_valid;    /* mark the new constraint as valid? */
-} Constraint pg_node_attr(custom_read_write, no_read);
+} Constraint;

 /* ----------------------
  *        Create/Drop Table Space Statements
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6193126d20..44ffc73f15 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -94,6 +94,8 @@ typedef enum UpperRelationKind
  */
 typedef struct PlannerGlobal
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     /* Param values provided to planner() */
@@ -155,7 +157,7 @@ typedef struct PlannerGlobal

     /* partition descriptors */
     PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
-} PlannerGlobal pg_node_attr(no_copy_equal, no_read);
+} PlannerGlobal;

 /* macro for fetching the Plan associated with a SubPlan node */
 #define planner_subplan_get_plan(root, subplan) \
@@ -185,6 +187,8 @@ typedef struct PlannerInfo PlannerInfo;

 struct PlannerInfo
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     /* the Query being planned */
@@ -476,7 +480,7 @@ struct PlannerInfo

     /* Does this query modify any partition key columns? */
     bool        partColsUpdated;
-}            pg_node_attr(no_copy_equal, no_read);
+};


 /*
@@ -775,6 +779,8 @@ typedef enum RelOptKind

 typedef struct RelOptInfo
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     RelOptKind    reloptkind;
@@ -946,7 +952,7 @@ typedef struct RelOptInfo
     List      **partexprs pg_node_attr(read_write_ignore);
     /* Nullable partition key expressions */
     List      **nullable_partexprs pg_node_attr(read_write_ignore);
-} RelOptInfo pg_node_attr(no_copy_equal, no_read);
+} RelOptInfo;

 /*
  * Is given relation partitioned?
@@ -1006,6 +1012,8 @@ typedef struct IndexOptInfo IndexOptInfo;

 struct IndexOptInfo
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     /* OID of the index relation */
@@ -1105,7 +1113,7 @@ struct IndexOptInfo
     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 */
-}            pg_node_attr(no_copy_equal, no_read);
+};

 /*
  * ForeignKeyOptInfo
@@ -1117,6 +1125,8 @@ struct IndexOptInfo
  */
 typedef struct ForeignKeyOptInfo
 {
+    pg_node_attr(custom_read_write, no_copy_equal, no_read)
+
     NodeTag        type;

     /*
@@ -1154,7 +1164,7 @@ typedef struct ForeignKeyOptInfo
     struct EquivalenceMember *fk_eclass_member[INDEX_MAX_KEYS];
     /* List of non-EC RestrictInfos matching each column's condition */
     List       *rinfos[INDEX_MAX_KEYS];
-} ForeignKeyOptInfo pg_node_attr(custom_read_write, no_copy_equal, no_read);
+} ForeignKeyOptInfo;

 /*
  * StatisticExtInfo
@@ -1165,6 +1175,8 @@ typedef struct ForeignKeyOptInfo
  */
 typedef struct StatisticExtInfo
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     /* OID of the statistics row */
@@ -1187,7 +1199,7 @@ typedef struct StatisticExtInfo

     /* expressions */
     List       *exprs;
-} StatisticExtInfo pg_node_attr(no_copy_equal, no_read);
+} StatisticExtInfo;

 /*
  * EquivalenceClasses
@@ -1235,6 +1247,8 @@ typedef struct StatisticExtInfo
  */
 typedef struct EquivalenceClass
 {
+    pg_node_attr(custom_read_write, no_copy_equal, no_read)
+
     NodeTag        type;

     List       *ec_opfamilies;    /* btree operator family OIDs */
@@ -1252,7 +1266,7 @@ typedef struct EquivalenceClass
     Index        ec_min_security;    /* minimum security_level in ec_sources */
     Index        ec_max_security;    /* maximum security_level in ec_sources */
     struct EquivalenceClass *ec_merged; /* set if merged into another EC */
-} EquivalenceClass pg_node_attr(custom_read_write, no_copy_equal, no_read);
+} EquivalenceClass;

 /*
  * If an EC contains a const and isn't below-outer-join, any PathKey depending
@@ -1285,6 +1299,8 @@ typedef struct EquivalenceClass
  */
 typedef struct EquivalenceMember
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     Expr       *em_expr;        /* the expression represented */
@@ -1293,7 +1309,7 @@ typedef struct EquivalenceMember
     bool        em_is_const;    /* expression is pseudoconstant? */
     bool        em_is_child;    /* derived version for a child relation? */
     Oid            em_datatype;    /* the "nominal type" used by the opfamily */
-} EquivalenceMember pg_node_attr(no_copy_equal, no_read);
+} EquivalenceMember;

 /*
  * PathKeys
@@ -1314,23 +1330,27 @@ typedef struct EquivalenceMember
  */
 typedef struct PathKey
 {
+    pg_node_attr(no_read)
+
     NodeTag        type;

     EquivalenceClass *pk_eclass;    /* the value that is ordered */
     Oid            pk_opfamily;    /* btree opfamily defining the ordering */
     int            pk_strategy;    /* sort direction (ASC or DESC) */
     bool        pk_nulls_first; /* do NULLs come before normal values? */
-} PathKey    pg_node_attr(no_read);
+} PathKey;

 /*
  * Combines information about pathkeys and the associated clauses.
  */
 typedef struct PathKeyInfo
 {
+    pg_node_attr(no_read)
+
     NodeTag        type;
     List       *pathkeys;
     List       *clauses;
-} PathKeyInfo pg_node_attr(no_read);
+} PathKeyInfo;

 /*
  * VolatileFunctionStatus -- allows nodes to cache their
@@ -1369,6 +1389,8 @@ typedef enum VolatileFunctionStatus
  */
 typedef struct PathTarget
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     /* list of expressions to be computed */
@@ -1385,7 +1407,7 @@ typedef struct PathTarget

     /* indicates if exprs contain any volatile functions */
     VolatileFunctionStatus has_volatile_expr;
-} PathTarget pg_node_attr(no_copy_equal, no_read);
+} PathTarget;

 /* Convenience macro to get a sort/group refno from a PathTarget */
 #define get_pathtarget_sortgroupref(target, colno) \
@@ -1408,12 +1430,14 @@ typedef struct PathTarget
  */
 typedef struct ParamPathInfo
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     Relids        ppi_req_outer;    /* rels supplying parameters used by path */
     Cardinality ppi_rows;        /* estimated number of result tuples */
     List       *ppi_clauses;    /* join clauses available from outer rels */
-} ParamPathInfo pg_node_attr(no_copy_equal, no_read);
+} ParamPathInfo;


 /*
@@ -1451,6 +1475,8 @@ typedef struct ParamPathInfo
  */
 typedef struct Path
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     /* tag identifying scan/join method */
@@ -1494,7 +1520,7 @@ typedef struct Path

     /* sort ordering of path's output; a List of PathKey nodes; see above */
     List       *pathkeys;
-} Path        pg_node_attr(no_copy_equal, no_read);
+} Path;

 /* Macro for extracting a path's parameterization relids; beware double eval */
 #define PATH_REQ_OUTER(path)  \
@@ -1586,13 +1612,15 @@ typedef struct IndexPath
  */
 typedef struct IndexClause
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;
     struct RestrictInfo *rinfo; /* original restriction or join clause */
     List       *indexquals;        /* indexqual(s) derived from it */
     bool        lossy;            /* are indexquals a lossy version of clause? */
     AttrNumber    indexcol;        /* index column the clause uses (zero-based) */
     List       *indexcols;        /* multiple index columns, if RowCompare */
-} IndexClause pg_node_attr(no_copy_equal, no_read);
+} IndexClause;

 /*
  * BitmapHeapPath represents one or more indexscans that generate TID bitmaps
@@ -1881,6 +1909,8 @@ typedef struct GatherMergePath

 typedef struct JoinPath
 {
+    pg_node_attr(abstract)
+
     Path        path;

     JoinType    jointype;
@@ -1898,7 +1928,7 @@ typedef struct JoinPath
      * joinrestrictinfo is needed in JoinPath, and can't be merged into the
      * parent RelOptInfo.
      */
-} JoinPath    pg_node_attr(abstract);
+} JoinPath;

 /*
  * A nested-loop path needs no special fields.
@@ -2083,13 +2113,17 @@ typedef struct AggPath

 typedef struct GroupingSetData
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;
     List       *set;            /* grouping set as list of sortgrouprefs */
     Cardinality numGroups;        /* est. number of result groups */
-} GroupingSetData pg_node_attr(no_copy_equal, no_read);
+} GroupingSetData;

 typedef struct RollupData
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;
     List       *groupClause;    /* applicable subset of parse->groupClause */
     List       *gsets;            /* lists of integer indexes into groupClause */
@@ -2097,7 +2131,7 @@ typedef struct RollupData
     Cardinality numGroups;        /* est. number of result groups */
     bool        hashable;        /* can be hashed */
     bool        is_hashed;        /* to be implemented as a hashagg */
-} RollupData pg_node_attr(no_copy_equal, no_read);
+} RollupData;

 /*
  * GroupingSetsPath represents a GROUPING SETS aggregation
@@ -2363,6 +2397,8 @@ typedef struct LimitPath

 typedef struct RestrictInfo
 {
+    pg_node_attr(no_read)
+
     NodeTag        type;

     /* the represented clause of WHERE or JOIN */
@@ -2488,7 +2524,7 @@ typedef struct RestrictInfo
     /* hash equality operators used for memoize nodes, else InvalidOid */
     Oid            left_hasheqoperator pg_node_attr(equal_ignore);
     Oid            right_hasheqoperator pg_node_attr(equal_ignore);
-} RestrictInfo pg_node_attr(no_read);
+} RestrictInfo;

 /*
  * This macro embodies the correct way to test whether a RestrictInfo is
@@ -2631,6 +2667,8 @@ typedef struct SpecialJoinInfo SpecialJoinInfo;

 struct SpecialJoinInfo
 {
+    pg_node_attr(no_read)
+
     NodeTag        type;
     Relids        min_lefthand;    /* base relids in minimum LHS for join */
     Relids        min_righthand;    /* base relids in minimum RHS for join */
@@ -2644,7 +2682,7 @@ struct SpecialJoinInfo
     bool        semi_can_hash;    /* true if semi_operators are all hash */
     List       *semi_operators; /* OIDs of equality join operators */
     List       *semi_rhs_exprs; /* righthand-side expressions of these ops */
-}            pg_node_attr(no_read);
+};

 /*
  * Append-relation info.
@@ -2753,13 +2791,15 @@ typedef struct AppendRelInfo
  */
 typedef struct RowIdentityVarInfo
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     Var           *rowidvar;        /* Var to be evaluated (but varno=ROWID_VAR) */
     int32        rowidwidth;        /* estimated average width */
     char       *rowidname;        /* name of the resjunk column */
     Relids        rowidrels;        /* RTE indexes of target rels using this */
-} RowIdentityVarInfo pg_node_attr(no_copy_equal, no_read);
+} RowIdentityVarInfo;

 /*
  * For each distinct placeholder expression generated during planning, we
@@ -2789,6 +2829,8 @@ typedef struct RowIdentityVarInfo

 typedef struct PlaceHolderInfo
 {
+    pg_node_attr(no_read)
+
     NodeTag        type;

     /* ID for PH (unique within planner run) */
@@ -2811,7 +2853,7 @@ typedef struct PlaceHolderInfo

     /* estimated attribute width */
     int32        ph_width;
-} PlaceHolderInfo pg_node_attr(no_read);
+} PlaceHolderInfo;

 /*
  * This struct describes one potentially index-optimizable MIN/MAX aggregate
@@ -2820,6 +2862,8 @@ typedef struct PlaceHolderInfo
  */
 typedef struct MinMaxAggInfo
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     /* pg_proc Oid of the aggregate */
@@ -2845,7 +2889,7 @@ typedef struct MinMaxAggInfo

     /* param for subplan's output */
     Param       *param;
-} MinMaxAggInfo pg_node_attr(no_copy_equal, no_read);
+} MinMaxAggInfo;

 /*
  * At runtime, PARAM_EXEC slots are used to pass values around from one plan
@@ -2896,11 +2940,13 @@ typedef struct MinMaxAggInfo
  */
 typedef struct PlannerParamItem
 {
+    pg_node_attr(no_copy_equal, no_read)
+
     NodeTag        type;

     Node       *item;            /* the Var, PlaceHolderVar, or Aggref */
     int            paramId;        /* its assigned PARAM_EXEC slot number */
-} PlannerParamItem pg_node_attr(no_copy_equal, no_read);
+} PlannerParamItem;

 /*
  * When making cost estimates for a SEMI/ANTI/inner_unique join, there are
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 846977f443..6ed765cbe4 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -45,6 +45,8 @@
  */
 typedef struct PlannedStmt
 {
+    pg_node_attr(no_equal)
+
     NodeTag        type;

     CmdType        commandType;    /* select|insert|update|delete|merge|utility */
@@ -92,7 +94,7 @@ typedef struct PlannedStmt
     /* statement location in source string (copied from Query) */
     int            stmt_location;    /* start location, or -1 if unknown */
     int            stmt_len;        /* length in bytes; 0 means "rest of string" */
-} PlannedStmt pg_node_attr(no_equal);
+} PlannedStmt;

 /* macro for fetching the Plan associated with a SubPlan node */
 #define exec_subplan_get_plan(plannedstmt, subplan) \
@@ -113,6 +115,8 @@ typedef struct PlannedStmt
  */
 typedef struct Plan
 {
+    pg_node_attr(abstract, no_equal)
+
     NodeTag        type;

     /*
@@ -162,7 +166,7 @@ typedef struct Plan
      */
     Bitmapset  *extParam;
     Bitmapset  *allParam;
-} Plan        pg_node_attr(abstract, no_equal);
+} Plan;

 /* ----------------
  *    these are defined to avoid confusion problems with "left"
@@ -767,11 +771,13 @@ typedef struct CustomScan
  */
 typedef struct Join
 {
+    pg_node_attr(abstract)
+
     Plan        plan;
     JoinType    jointype;
     bool        inner_unique;
     List       *joinqual;        /* JOIN quals (in addition to plan.qual) */
-} Join        pg_node_attr(abstract);
+} Join;

 /* ----------------
  *        nest loop join node
@@ -792,10 +798,12 @@ typedef struct NestLoop

 typedef struct NestLoopParam
 {
+    pg_node_attr(no_equal)
+
     NodeTag        type;
     int            paramno;        /* number of the PARAM_EXEC Param to set */
     Var           *paramval;        /* outer-relation Var to assign to Param */
-} NestLoopParam pg_node_attr(no_equal);
+} NestLoopParam;

 /* ----------------
  *        merge join node
@@ -1354,6 +1362,8 @@ typedef enum RowMarkType
  */
 typedef struct PlanRowMark
 {
+    pg_node_attr(no_equal)
+
     NodeTag        type;
     Index        rti;            /* range table index of markable relation */
     Index        prti;            /* range table index of parent relation */
@@ -1363,7 +1373,7 @@ typedef struct PlanRowMark
     LockClauseStrength strength;    /* LockingClause's strength, or LCS_NONE */
     LockWaitPolicy waitPolicy;    /* NOWAIT and SKIP LOCKED options */
     bool        isParent;        /* true if this is a "dummy" parent entry */
-} PlanRowMark pg_node_attr(no_equal);
+} PlanRowMark;


 /*
@@ -1398,10 +1408,12 @@ typedef struct PlanRowMark
  */
 typedef struct PartitionPruneInfo
 {
+    pg_node_attr(no_equal)
+
     NodeTag        type;
     List       *prune_infos;
     Bitmapset  *other_subplans;
-} PartitionPruneInfo pg_node_attr(no_equal);
+} PartitionPruneInfo;

 /*
  * PartitionedRelPruneInfo - Details required to allow the executor to prune
@@ -1422,6 +1434,8 @@ typedef struct PartitionPruneInfo
  */
 typedef struct PartitionedRelPruneInfo
 {
+    pg_node_attr(no_equal)
+
     NodeTag        type;

     /* RT index of partition rel for this level */
@@ -1453,7 +1467,7 @@ typedef struct PartitionedRelPruneInfo

     /* All PARAM_EXEC Param IDs in exec_pruning_steps */
     Bitmapset  *execparamids;
-} PartitionedRelPruneInfo pg_node_attr(no_equal);
+} PartitionedRelPruneInfo;

 /*
  * Abstract Node type for partition pruning steps (there are no concrete
@@ -1463,9 +1477,11 @@ typedef struct PartitionedRelPruneInfo
  */
 typedef struct PartitionPruneStep
 {
+    pg_node_attr(abstract, no_equal)
+
     NodeTag        type;
     int            step_id;
-} PartitionPruneStep pg_node_attr(abstract);
+} PartitionPruneStep;

 /*
  * PartitionPruneStepOp - Information to prune using a set of mutually ANDed
@@ -1502,7 +1518,7 @@ typedef struct PartitionPruneStepOp
     List       *exprs;
     List       *cmpfns;
     Bitmapset  *nullkeys;
-} PartitionPruneStepOp pg_node_attr(no_equal);
+} PartitionPruneStepOp;

 /*
  * PartitionPruneStepCombine - Information to prune using a BoolExpr clause
@@ -1522,7 +1538,7 @@ typedef struct PartitionPruneStepCombine

     PartitionPruneCombineOp combineOp;
     List       *source_stepids;
-} PartitionPruneStepCombine pg_node_attr(no_equal);
+} PartitionPruneStepCombine;


 /*
@@ -1536,10 +1552,12 @@ typedef struct PartitionPruneStepCombine
  */
 typedef struct PlanInvalItem
 {
+    pg_node_attr(no_equal)
+
     NodeTag        type;
     int            cacheId;        /* a syscache ID, see utils/syscache.h */
     uint32        hashValue;        /* hash value of object's cache lookup key */
-} PlanInvalItem pg_node_attr(no_equal);
+} PlanInvalItem;

 /*
  * MonotonicFunction
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index fd22fe19b2..1fc2fbffa3 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -160,8 +160,10 @@ typedef struct IntoClause
  */
 typedef struct Expr
 {
+    pg_node_attr(abstract)
+
     NodeTag        type;
-} Expr        pg_node_attr(abstract);
+} Expr;

 /*
  * Var - expression node representing a variable (ie, a table column)
@@ -260,6 +262,8 @@ typedef struct Var
  */
 typedef struct Const
 {
+    pg_node_attr(custom_copy_equal, custom_read_write)
+
     Expr        xpr;
     Oid            consttype;        /* pg_type OID of the constant's datatype */
     int32        consttypmod;    /* typmod value, if any */
@@ -273,7 +277,7 @@ typedef struct Const
                                  * in the Datum. If false, then the Datum
                                  * contains a pointer to the information. */
     int            location;        /* token location, or -1 if unknown */
-} Const        pg_node_attr(custom_copy_equal, custom_read_write);
+} Const;

 /*
  * Param
@@ -758,11 +762,13 @@ typedef enum BoolExprType

 typedef struct BoolExpr
 {
+    pg_node_attr(custom_read_write)
+
     Expr        xpr;
     BoolExprType boolop;
     List       *args;            /* arguments to this expression */
     int            location;        /* token location, or -1 if unknown */
-} BoolExpr    pg_node_attr(custom_read_write);
+} BoolExpr;

 /*
  * SubLink
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index 20347d39dd..5e83b843dc 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -27,9 +27,11 @@

 typedef struct Integer
 {
+    pg_node_attr(special_read_write)
+
     NodeTag        type;
     int            ival;
-} Integer    pg_node_attr(special_read_write);
+} Integer;

 /*
  * Float is internally represented as string.  Using T_Float as the node type
@@ -44,27 +46,35 @@ typedef struct Integer
  */
 typedef struct Float
 {
+    pg_node_attr(special_read_write)
+
     NodeTag        type;
     char       *fval;
-} Float        pg_node_attr(special_read_write);
+} Float;

 typedef struct Boolean
 {
+    pg_node_attr(special_read_write)
+
     NodeTag        type;
     bool        boolval;
-} Boolean    pg_node_attr(special_read_write);
+} Boolean;

 typedef struct String
 {
+    pg_node_attr(special_read_write)
+
     NodeTag        type;
     char       *sval;
-} String    pg_node_attr(special_read_write);
+} String;

 typedef struct BitString
 {
+    pg_node_attr(special_read_write)
+
     NodeTag        type;
     char       *bsval;
-} BitString pg_node_attr(special_read_write);
+} BitString;

 #define intVal(v)        (castNode(Integer, v)->ival)
 #define floatVal(v)        atof(castNode(Float, v)->fval)
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 075a2669fd..2854839ec2 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -268,6 +268,8 @@ typedef struct RelationData
  */
 typedef struct ForeignKeyCacheInfo
 {
+    pg_node_attr(no_equal, no_read)
+
     NodeTag        type;
     /* oid of the constraint itself */
     Oid            conoid;
@@ -287,7 +289,7 @@ typedef struct ForeignKeyCacheInfo
     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 pg_node_attr(no_equal, no_read);
+} ForeignKeyCacheInfo;


 /*
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index f6625a763e..dca5819f95 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -23,7 +23,7 @@ use File::Basename;
 use FindBin;
 use lib "$FindBin::RealBin/../catalog";

-use Catalog;  # for RenameTempFile
+use Catalog;    # for RenameTempFile


 # Test whether first argument is element of the list in the second
@@ -51,8 +51,8 @@ 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 RelFileNumber Selectivity Size StrategyNumber SubTransactionId
TimeLineIDXLogRecPtr 
+  bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+  AclMode AttrNumber Cardinality Cost Index Oid RelFileNumber Selectivity Size StrategyNumber SubTransactionId
TimeLineIDXLogRecPtr 
 );

 # collect enum types
@@ -65,18 +65,18 @@ my @abstract_types = qw(Node);
 # is not in a header file.  We 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
+  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);
 # Lists are specially treated in all four support files, too.
-push @no_copy, qw(List);
-push @no_equal, qw(List);
+push @no_copy,       qw(List);
+push @no_equal,      qw(List);
 push @no_read_write, qw(List);

 # Nodes with custom copy/equal implementations are skipped from
@@ -95,21 +95,22 @@ push @scalar_types, qw(QualCost);

 # XXX various things we are not publishing right now to stay level
 # with the manual system
-push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_copy,  qw(CallContext InlineCodeBlock);
 push @no_equal, qw(CallContext InlineCodeBlock);
-push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause
InlineCodeBlockObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation); 
+push @no_read_write,
+  qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock
ObjectWithArgsOnConflictClause PartitionCmd RoleSpec VacuumRelation); 
 push @no_read, qw(A_ArrayExpr A_Indices A_Indirection AlterStatsStmt
-CollateClause ColumnDef ColumnRef CreateForeignTableStmt CreateStatsStmt
-CreateStmt FuncCall ImportForeignSchemaStmt IndexElem IndexStmt
-JsonAggConstructor JsonArgument JsonArrayAgg JsonArrayConstructor
-JsonArrayQueryConstructor JsonCommon JsonFuncExpr JsonKeyValue
-JsonObjectAgg JsonObjectConstructor JsonOutput JsonParseExpr JsonScalarExpr
-JsonSerializeExpr JsonTable JsonTableColumn JsonTablePlan LockingClause
-MultiAssignRef PLAssignStmt ParamRef PartitionElem PartitionSpec
-PlaceHolderVar PublicationObjSpec PublicationTable RangeFunction
-RangeSubselect RangeTableFunc RangeTableFuncCol RangeTableSample RawStmt
-ResTarget ReturnStmt SelectStmt SortBy StatsElem TableLikeClause
-TriggerTransition TypeCast TypeName WindowDef WithClause XmlSerialize);
+  CollateClause ColumnDef ColumnRef CreateForeignTableStmt CreateStatsStmt
+  CreateStmt FuncCall ImportForeignSchemaStmt IndexElem IndexStmt
+  JsonAggConstructor JsonArgument JsonArrayAgg JsonArrayConstructor
+  JsonArrayQueryConstructor JsonCommon JsonFuncExpr JsonKeyValue
+  JsonObjectAgg JsonObjectConstructor JsonOutput JsonParseExpr JsonScalarExpr
+  JsonSerializeExpr JsonTable JsonTableColumn JsonTablePlan LockingClause
+  MultiAssignRef PLAssignStmt ParamRef PartitionElem PartitionSpec
+  PlaceHolderVar PublicationObjSpec PublicationTable RangeFunction
+  RangeSubselect RangeTableFunc RangeTableFuncCol RangeTableSample RawStmt
+  ResTarget ReturnStmt SelectStmt SortBy StatsElem TableLikeClause
+  TriggerTransition TypeCast TypeName WindowDef WithClause XmlSerialize);


 ## read input
@@ -150,13 +151,13 @@ foreach my $infile (@ARGV)
             if ($subline == 1)
             {
                 $is_node_struct = 0;
-                $supertype = undef;
+                $supertype      = undef;
                 next if $line eq '{';
                 die "$infile:$.: expected opening brace\n";
             }
             # second line could be node attributes
-            elsif ($subline == 2 &&
-                   $line =~ /^\s*pg_node_attr\(([\w(), ]*)\)$/)
+            elsif ($subline == 2
+                && $line =~ /^\s*pg_node_attr\(([\w(), ]*)\)$/)
             {
                 $node_attrs = $1;
                 # hack: don't count the line
@@ -173,8 +174,8 @@ foreach my $infile (@ARGV)
                 }
                 elsif ($line =~ /\s*(\w+)\s+(\w+);/ and elem $1, @node_types)
                 {
-                    $is_node_struct = 1;
-                    $supertype = $1;
+                    $is_node_struct  = 1;
+                    $supertype       = $1;
                     $supertype_field = $2;
                     next;
                 }
@@ -212,7 +213,7 @@ foreach my $infile (@ARGV)
                         }
                         elsif ($attr eq 'no_copy_equal')
                         {
-                            push @no_copy, $in_struct;
+                            push @no_copy,  $in_struct;
                             push @no_equal, $in_struct;
                         }
                         elsif ($attr eq 'no_read')
@@ -234,7 +235,8 @@ foreach my $infile (@ARGV)
                         }
                         else
                         {
-                            die "$infile:$.: unrecognized attribute \"$attr\"\n";
+                            die
+                              "$infile:$.: unrecognized attribute \"$attr\"\n";
                         }
                     }

@@ -242,7 +244,7 @@ foreach my $infile (@ARGV)
                     push @node_types, $in_struct;

                     # field names, types, attributes
-                    my @f = @my_fields;
+                    my @f  = @my_fields;
                     my %ft = %my_field_types;
                     my %fa = %my_field_attrs;

@@ -250,18 +252,23 @@ foreach my $infile (@ARGV)
                     if ($supertype)
                     {
                         my @superfields;
-                        foreach my $sf (@{$node_type_info{$supertype}->{fields}})
+                        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})
+                            $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}};
+                                my @newa = @{ $node_type_info{$supertype}
+                                      ->{field_attrs}{$sf} };
                                 foreach my $a (@newa)
                                 {
-                                    $a =~ s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/;
+                                    $a =~
+                                      s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/;
                                 }
                                 $fa{$fn} = \@newa;
                             }
@@ -269,7 +276,7 @@ foreach my $infile (@ARGV)
                         unshift @f, @superfields;
                     }
                     # save in global info structure
-                    $node_type_info{$in_struct}->{fields} = \@f;
+                    $node_type_info{$in_struct}->{fields}      = \@f;
                     $node_type_info{$in_struct}->{field_types} = \%ft;
                     $node_type_info{$in_struct}->{field_attrs} = \%fa;

@@ -277,38 +284,44 @@ foreach my $infile (@ARGV)
                     # just 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))
+                        tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h)
+                      )
                     {
-                        push @no_copy, $in_struct;
-                        push @no_equal, $in_struct;
+                        push @no_copy,       $in_struct;
+                        push @no_equal,      $in_struct;
                         push @no_read_write, $in_struct;
                     }

                     # Propagate some node attributes from supertypes
                     if ($supertype)
                     {
-                        push @no_copy, $in_struct if elem $supertype, @no_copy;
-                        push @no_equal, $in_struct if elem $supertype, @no_equal;
-                        push @no_read, $in_struct if elem $supertype, @no_read;
+                        push @no_copy, $in_struct
+                          if elem $supertype, @no_copy;
+                        push @no_equal, $in_struct
+                          if elem $supertype, @no_equal;
+                        push @no_read, $in_struct
+                          if elem $supertype, @no_read;
                     }
                 }

                 # start new cycle
-                $in_struct = undef;
-                $node_attrs = '';
-                @my_fields = ();
+                $in_struct      = undef;
+                $node_attrs     = '';
+                @my_fields      = ();
                 %my_field_types = ();
                 %my_field_attrs = ();
             }
             # normal struct field
-            elsif ($line =~ /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w(), ]*)\))?;/)
+            elsif ($line =~
+                /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w(), ]*)\))?;/
+              )
             {
                 if ($is_node_struct)
                 {
-                    my $type = $1;
-                    my $name = $2;
+                    my $type       = $1;
+                    my $name       = $2;
                     my $array_size = $3;
-                    my $attrs = $4;
+                    my $attrs      = $4;

                     # strip "const"
                     $type =~ s/^const\s*//;
@@ -325,13 +338,16 @@ foreach my $infile (@ARGV)
                         @attrs = split /,\s*/, $attrs;
                         foreach my $attr (@attrs)
                         {
-                            if ($attr !~ /^array_size\(\w+\)$/ &&
-                                $attr !~ /^copy_as\(\w+\)$/ &&
-                                $attr !~ /^read_as\(\w+\)$/ &&
-                                !elem $attr, qw(equal_ignore equal_ignore_if_zero read_write_ignore
-                                    write_only_relids write_only_nondefault_pathtarget write_only_req_outer))
+                            if (   $attr !~ /^array_size\(\w+\)$/
+                                && $attr !~ /^copy_as\(\w+\)$/
+                                && $attr !~ /^read_as\(\w+\)$/
+                                && !elem $attr,
+                                qw(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";
+                                die
+                                  "$infile:$.: unrecognized attribute \"$attr\"\n";
                             }
                         }
                     }
@@ -357,20 +373,20 @@ foreach my $infile (@ARGV)
             if ($line =~ /^(?:typedef )?struct (\w+)$/ && $1 ne 'Node')
             {
                 $in_struct = $1;
-                $subline = 0;
+                $subline   = 0;
             }
             # one node type typedef'ed directly from another
             elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types)
             {
                 my $alias_of = $1;
-                my $n = $2;
+                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;
+                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;
             }
@@ -388,19 +404,19 @@ foreach my $infile (@ARGV)
     }

     close $ifh;
-} # for each file
+}    # for each file


 ## write output

-my $tmpext  = ".tmp$$";
+my $tmpext = ".tmp$$";

 # nodetags.h

 open my $nt, '>', 'nodetags.h' . $tmpext or die $!;

 my $i = 1;
-foreach my $n (@node_types,@extra_tags)
+foreach my $n (@node_types, @extra_tags)
 {
     next if elem $n, @abstract_types;
     print $nt "\tT_${n} = $i,\n";
@@ -421,9 +437,9 @@ foreach my $infile (sort @ARGV)

 # 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 $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
@@ -433,17 +449,19 @@ print $eff $node_includes;
 foreach my $n (@node_types)
 {
     next if elem $n, @abstract_types;
-    my $struct_no_copy = (elem $n, @no_copy);
+    my $struct_no_copy  = (elem $n, @no_copy);
     my $struct_no_equal = (elem $n, @no_equal);
     next if $struct_no_copy && $struct_no_equal;

-    print $cfs "\t\tcase T_${n}:\n".
-      "\t\t\tretval = _copy${n}(from);\n".
-      "\t\t\tbreak;\n" unless $struct_no_copy;
+    print $cfs "\t\tcase T_${n}:\n"
+      . "\t\t\tretval = _copy${n}(from);\n"
+      . "\t\t\tbreak;\n"
+      unless $struct_no_copy;

-    print $efs "\t\tcase T_${n}:\n".
-      "\t\t\tretval = _equal${n}(a, b);\n".
-      "\t\t\tbreak;\n" unless $struct_no_equal;
+    print $efs "\t\tcase T_${n}:\n"
+      . "\t\t\tretval = _equal${n}(a, b);\n"
+      . "\t\t\tbreak;\n"
+      unless $struct_no_equal;

     next if elem $n, @custom_copy_equal;

@@ -462,11 +480,11 @@ _equal${n}(const $n *a, const $n *b)
 " unless $struct_no_equal;

     # print instructions for each field
-    foreach my $f (@{$node_type_info{$n}->{fields}})
+    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 = $struct_no_copy;
+        my $t            = $node_type_info{$n}->{field_types}{$f};
+        my @a            = @{ $node_type_info{$n}->{field_attrs}{$f} };
+        my $copy_ignore  = $struct_no_copy;
         my $equal_ignore = $struct_no_equal;

         # extract per-field attributes
@@ -491,24 +509,26 @@ _equal${n}(const $n *a, const $n *b)
         # override type-specific copy method if copy_as is specified
         if (defined $copy_as_field)
         {
-            print $cff "\tnewnode->$f = $copy_as_field;\n" unless $copy_ignore;
+            print $cff "\tnewnode->$f = $copy_as_field;\n"
+              unless $copy_ignore;
             $copy_ignore = 1;
         }

         # select instructions by field type
         if ($t eq 'char*')
         {
-            print $cff "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+            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;
+            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 $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)
@@ -516,12 +536,14 @@ _equal${n}(const $n *a, const $n *b)
             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";
+                print $eff
+                  "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
             }
             else
             {
                 # All CoercionForm fields are treated as equal_ignore
-                print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+                print $eff "\tCOMPARE_SCALAR_FIELD($f);\n"
+                  unless $equal_ignore || $t eq 'CoercionForm';
             }
         }
         # scalar type pointer
@@ -532,35 +554,45 @@ _equal${n}(const $n *a, const $n *b)
             {
                 die "no array size defined for $n.$f of type $t";
             }
-            if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+            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;
+                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;
+                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 $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 $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*')
+        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 $cff "\tCOPY_SCALAR_FIELD($f);\n"    unless $copy_ignore;
             print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
         }
         else
@@ -587,9 +619,9 @@ 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 $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;
@@ -603,7 +635,8 @@ foreach my $n (@node_types)
     # 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
ImportForeignSchemaStmtIndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt); 
+        my @keep =
+          qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt
ImportForeignSchemaStmtIndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt); 
         next unless elem $n, @keep;
     }

@@ -612,12 +645,14 @@ foreach my $n (@node_types)
     # output format starts with upper case node type name
     my $N = uc $n;

-    print $ofs "\t\t\tcase T_${n}:\n".
-      "\t\t\t\t_out${n}(str, obj);\n".
-      "\t\t\t\tbreak;\n";
+    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;
+    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;

@@ -638,7 +673,7 @@ _read${n}(void)
 " unless $no_read;

     # print instructions for each field
-    foreach my $f (@{$node_type_info{$n}->{fields}})
+    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} };
@@ -684,12 +719,20 @@ _read${n}(void)
             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')
+        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')
+        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;
@@ -733,7 +776,7 @@ _read${n}(void)
         {
             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.startup);\n"   unless $no_read;
             print $rff "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
         }
         elsif ($t eq 'Selectivity')
@@ -773,36 +816,46 @@ _read${n}(void)
             {
                 die "no array size defined for $n.$f of type $t";
             }
-            if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+            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;
+                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;
+                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";
+            print $off
+              "\tappendStringInfoString(str, \" :parent_relids \");\n"
+              . "\toutBitmapset(str, node->$f->relids);\n";
         }
-        elsif ($t eq 'PathTarget*' && elem 'write_only_nondefault_pathtarget', @a)
+        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";
+            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 "\tappendStringInfoString(str, \" :required_outer \");\n".
-              "\tif (node->$f)\n".
-              "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
-              "\telse\n".
-              "\t\toutBitmapset(str, NULL);\n";
+            print $off
+              "\tappendStringInfoString(str, \" :required_outer \");\n"
+              . "\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)
@@ -810,7 +863,8 @@ _read${n}(void)
             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*')
+        elsif ($t eq 'struct CustomPathMethods*'
+            || $t eq 'struct CustomScanMethods*')
         {
             print $off q{
     /* CustomName is a key to lookup CustomScanMethods */
@@ -858,7 +912,9 @@ 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.coutfuncs.switch.c readfuncs.funcs.c readfuncs.switch.c)) 
+foreach my $file (
+    qw(nodetags.h copyfuncs.funcs.c copyfuncs.switch.c equalfuncs.funcs.c equalfuncs.switch.c outfuncs.funcs.c
outfuncs.switch.creadfuncs.funcs.c readfuncs.switch.c) 
+  )
 {
     Catalog::RenameTempFile($file, $tmpext);
 }
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 42ead5f789..b8b1728df7 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -839,32 +839,33 @@ EOF
         close($chs);
     }

-    if (IsNewer('src/backend/nodes/node-support-stamp',
-        'src/backend/nodes/gen_node_support.pl'))
+    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
+          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');
@@ -872,7 +873,8 @@ EOF
         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";
+        open(my $f, '>', 'node-support-stamp')
+          || confess "Could not touch node-support-stamp";
         close($f);
         chdir('../../..');
     }

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Erik Rijkers
Дата:
Сообщение: SQL/JSON documentation JSON_TABLE
Следующее
От: Tom Lane
Дата:
Сообщение: Re: automatically generating node support functions