automatically generating node support functions

Started by Peter Eisentrautover 4 years ago89 messages
#1Peter Eisentraut
peter.eisentraut@enterprisedb.com
10 attachment(s)

I wrote 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.

The first eight patches are to clean up various inconsistencies to make
parsing or generation easier.

The interesting stuff is in patch 0009.

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

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. When we do that, some
code comments should probably be preserved elsewhere, so that will need
another pass of consideration.

I have tried to mostly make the coverage of the output match what is
currently there. For example, one could do out/read coverage of utility
statement nodes easily with this, 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.

There are a couple of additional minor issues mentioned in the script
source. But basically, it all seems to work.

Attachments:

v1-0001-Rename-NodeTag-of-ExprState.patchtext/plain; charset=UTF-8; name=v1-0001-Rename-NodeTag-of-ExprState.patch; x-mac-creator=0; x-mac-type=0Download
From c782871d6cc59e2fed232c78c307d63e72cbb3d5 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 15:45:14 +0200
Subject: [PATCH v1 01/10] Rename NodeTag of ExprState

Rename from tag to type, for consistency with all other node structs.
---
 src/backend/executor/execExpr.c | 4 ++--
 src/include/nodes/execnodes.h   | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 8c9f8a6aeb..c6ba11d035 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -363,7 +363,7 @@ ExecBuildProjectionInfo(List *targetList,
 
 	projInfo->pi_exprContext = econtext;
 	/* We embed ExprState into ProjectionInfo instead of doing extra palloc */
-	projInfo->pi_state.tag = T_ExprState;
+	projInfo->pi_state.type = T_ExprState;
 	state = &projInfo->pi_state;
 	state->expr = (Expr *) targetList;
 	state->parent = parent;
@@ -531,7 +531,7 @@ ExecBuildUpdateProjection(List *targetList,
 
 	projInfo->pi_exprContext = econtext;
 	/* We embed ExprState into ProjectionInfo instead of doing extra palloc */
-	projInfo->pi_state.tag = T_ExprState;
+	projInfo->pi_state.type = T_ExprState;
 	state = &projInfo->pi_state;
 	if (evalTargetList)
 		state->expr = (Expr *) targetList;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7795a69490..8fa9c8aff6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -60,7 +60,7 @@ typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression,
 
 typedef struct ExprState
 {
-	NodeTag		tag;
+	NodeTag		type;
 
 	uint8		flags;			/* bitmask of EEO_FLAG_* bits, see above */
 
-- 
2.31.1

v1-0002-Rename-argument-of-_outValue.patchtext/plain; charset=UTF-8; name=v1-0002-Rename-argument-of-_outValue.patch; x-mac-creator=0; x-mac-type=0Download
From 7746ddd4f2a9534322b2b9226007638d3142c0c7 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 15:47:56 +0200
Subject: [PATCH v1 02/10] Rename argument of _outValue()

Rename from value to node, for consistency with similar functions.
---
 src/backend/nodes/outfuncs.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 04696f613c..b54c57d09f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3402,12 +3402,12 @@ _outAExpr(StringInfo str, const A_Expr *node)
 }
 
 static void
-_outValue(StringInfo str, const Value *value)
+_outValue(StringInfo str, const Value *node)
 {
-	switch (value->type)
+	switch (node->type)
 	{
 		case T_Integer:
-			appendStringInfo(str, "%d", value->val.ival);
+			appendStringInfo(str, "%d", node->val.ival);
 			break;
 		case T_Float:
 
@@ -3415,7 +3415,7 @@ _outValue(StringInfo str, const Value *value)
 			 * We assume the value is a valid numeric literal and so does not
 			 * need quoting.
 			 */
-			appendStringInfoString(str, value->val.str);
+			appendStringInfoString(str, node->val.str);
 			break;
 		case T_String:
 
@@ -3424,20 +3424,20 @@ _outValue(StringInfo str, const Value *value)
 			 * but we don't want it to do anything with an empty string.
 			 */
 			appendStringInfoChar(str, '"');
-			if (value->val.str[0] != '\0')
-				outToken(str, value->val.str);
+			if (node->val.str[0] != '\0')
+				outToken(str, node->val.str);
 			appendStringInfoChar(str, '"');
 			break;
 		case T_BitString:
 			/* internal representation already has leading 'b' */
-			appendStringInfoString(str, value->val.str);
+			appendStringInfoString(str, node->val.str);
 			break;
 		case T_Null:
 			/* this is seen only within A_Const, not in transformed trees */
 			appendStringInfoString(str, "NULL");
 			break;
 		default:
-			elog(ERROR, "unrecognized node type: %d", (int) value->type);
+			elog(ERROR, "unrecognized node type: %d", (int) node->type);
 			break;
 	}
 }
-- 
2.31.1

v1-0003-Rename-some-node-support-functions-for-consistenc.patchtext/plain; charset=UTF-8; name=v1-0003-Rename-some-node-support-functions-for-consistenc.patch; x-mac-creator=0; x-mac-type=0Download
From 7282e926a6492046e3189f8ddc0ccbbbab8a470b Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 15:51:37 +0200
Subject: [PATCH v1 03/10] Rename some node support functions for consistency

Some node function names didn't match their node type names exactly.
Fix those for consistency.
---
 src/backend/nodes/copyfuncs.c  | 16 ++++++++--------
 src/backend/nodes/equalfuncs.c | 16 ++++++++--------
 src/backend/nodes/outfuncs.c   |  8 ++++----
 3 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 621f7ce068..35a85df442 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2700,7 +2700,7 @@ _copyCommonTableExpr(const CommonTableExpr *from)
 }
 
 static A_Expr *
-_copyAExpr(const A_Expr *from)
+_copyA_Expr(const A_Expr *from)
 {
 	A_Expr	   *newnode = makeNode(A_Expr);
 
@@ -2736,7 +2736,7 @@ _copyParamRef(const ParamRef *from)
 }
 
 static A_Const *
-_copyAConst(const A_Const *from)
+_copyA_Const(const A_Const *from)
 {
 	A_Const    *newnode = makeNode(A_Const);
 
@@ -2787,7 +2787,7 @@ _copyFuncCall(const FuncCall *from)
 }
 
 static A_Star *
-_copyAStar(const A_Star *from)
+_copyA_Star(const A_Star *from)
 {
 	A_Star	   *newnode = makeNode(A_Star);
 
@@ -2795,7 +2795,7 @@ _copyAStar(const A_Star *from)
 }
 
 static A_Indices *
-_copyAIndices(const A_Indices *from)
+_copyA_Indices(const A_Indices *from)
 {
 	A_Indices  *newnode = makeNode(A_Indices);
 
@@ -5711,7 +5711,7 @@ copyObjectImpl(const void *from)
 			retval = _copyDropSubscriptionStmt(from);
 			break;
 		case T_A_Expr:
-			retval = _copyAExpr(from);
+			retval = _copyA_Expr(from);
 			break;
 		case T_ColumnRef:
 			retval = _copyColumnRef(from);
@@ -5720,16 +5720,16 @@ copyObjectImpl(const void *from)
 			retval = _copyParamRef(from);
 			break;
 		case T_A_Const:
-			retval = _copyAConst(from);
+			retval = _copyA_Const(from);
 			break;
 		case T_FuncCall:
 			retval = _copyFuncCall(from);
 			break;
 		case T_A_Star:
-			retval = _copyAStar(from);
+			retval = _copyA_Star(from);
 			break;
 		case T_A_Indices:
-			retval = _copyAIndices(from);
+			retval = _copyA_Indices(from);
 			break;
 		case T_A_Indirection:
 			retval = _copyA_Indirection(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3033c1934c..e04ec41904 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2369,7 +2369,7 @@ _equalAlterPolicyStmt(const AlterPolicyStmt *a, const AlterPolicyStmt *b)
 }
 
 static bool
-_equalAExpr(const A_Expr *a, const A_Expr *b)
+_equalA_Expr(const A_Expr *a, const A_Expr *b)
 {
 	COMPARE_SCALAR_FIELD(kind);
 	COMPARE_NODE_FIELD(name);
@@ -2399,7 +2399,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 }
 
 static bool
-_equalAConst(const A_Const *a, const A_Const *b)
+_equalA_Const(const A_Const *a, const A_Const *b)
 {
 	if (!equal(&a->val, &b->val))	/* hack for in-line Value field */
 		return false;
@@ -2427,13 +2427,13 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
 }
 
 static bool
-_equalAStar(const A_Star *a, const A_Star *b)
+_equalA_Star(const A_Star *a, const A_Star *b)
 {
 	return true;
 }
 
 static bool
-_equalAIndices(const A_Indices *a, const A_Indices *b)
+_equalA_Indices(const A_Indices *a, const A_Indices *b)
 {
 	COMPARE_SCALAR_FIELD(is_slice);
 	COMPARE_NODE_FIELD(lidx);
@@ -3702,7 +3702,7 @@ equal(const void *a, const void *b)
 			retval = _equalDropSubscriptionStmt(a, b);
 			break;
 		case T_A_Expr:
-			retval = _equalAExpr(a, b);
+			retval = _equalA_Expr(a, b);
 			break;
 		case T_ColumnRef:
 			retval = _equalColumnRef(a, b);
@@ -3711,16 +3711,16 @@ equal(const void *a, const void *b)
 			retval = _equalParamRef(a, b);
 			break;
 		case T_A_Const:
-			retval = _equalAConst(a, b);
+			retval = _equalA_Const(a, b);
 			break;
 		case T_FuncCall:
 			retval = _equalFuncCall(a, b);
 			break;
 		case T_A_Star:
-			retval = _equalAStar(a, b);
+			retval = _equalA_Star(a, b);
 			break;
 		case T_A_Indices:
-			retval = _equalAIndices(a, b);
+			retval = _equalA_Indices(a, b);
 			break;
 		case T_A_Indirection:
 			retval = _equalA_Indirection(a, b);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b54c57d09f..ed08f5acf4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3327,7 +3327,7 @@ _outTableSampleClause(StringInfo str, const TableSampleClause *node)
 }
 
 static void
-_outAExpr(StringInfo str, const A_Expr *node)
+_outA_Expr(StringInfo str, const A_Expr *node)
 {
 	WRITE_NODE_TYPE("AEXPR");
 
@@ -3475,7 +3475,7 @@ _outRawStmt(StringInfo str, const RawStmt *node)
 }
 
 static void
-_outAConst(StringInfo str, const A_Const *node)
+_outA_Const(StringInfo str, const A_Const *node)
 {
 	WRITE_NODE_TYPE("A_CONST");
 
@@ -4416,7 +4416,7 @@ outNode(StringInfo str, const void *obj)
 				_outTableSampleClause(str, obj);
 				break;
 			case T_A_Expr:
-				_outAExpr(str, obj);
+				_outA_Expr(str, obj);
 				break;
 			case T_ColumnRef:
 				_outColumnRef(str, obj);
@@ -4428,7 +4428,7 @@ outNode(StringInfo str, const void *obj)
 				_outRawStmt(str, obj);
 				break;
 			case T_A_Const:
-				_outAConst(str, obj);
+				_outA_Const(str, obj);
 				break;
 			case T_A_Star:
 				_outA_Star(str, obj);
-- 
2.31.1

v1-0004-Change-SeqScan-node-to-contain-Scan-node.patchtext/plain; charset=UTF-8; name=v1-0004-Change-SeqScan-node-to-contain-Scan-node.patch; x-mac-creator=0; x-mac-type=0Download
From 06d24333eabc3c9c5ef4df8ef100dbaaacd3dc16 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 15:55:40 +0200
Subject: [PATCH v1 04/10] Change SeqScan node to contain Scan node

This makes the structure of all Scan-derived nodes the same,
independent of whether they have additional fields.
---
 src/backend/executor/nodeSeqscan.c      |  4 ++--
 src/backend/nodes/readfuncs.c           |  2 +-
 src/backend/optimizer/plan/createplan.c |  6 +++---
 src/backend/optimizer/plan/setrefs.c    | 10 +++++-----
 src/include/nodes/plannodes.h           |  5 ++++-
 5 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 066f9ae37e..4d2bf16a6f 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -151,7 +151,7 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
 	 */
 	scanstate->ss.ss_currentRelation =
 		ExecOpenScanRelation(estate,
-							 node->scanrelid,
+							 node->scan.scanrelid,
 							 eflags);
 
 	/* and create slot with the appropriate rowtype */
@@ -169,7 +169,7 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->plan.qual, (PlanState *) scanstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
 
 	return scanstate;
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index f0b34ecfac..fef4de94e6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1832,7 +1832,7 @@ _readSeqScan(void)
 {
 	READ_LOCALS_NO_FIELDS(SeqScan);
 
-	ReadCommonScan(local_node);
+	ReadCommonScan(&local_node->scan);
 
 	READ_DONE();
 }
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 439e6b6426..769b5a0cf7 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -2858,7 +2858,7 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
 							 scan_clauses,
 							 scan_relid);
 
-	copy_generic_path_info(&scan_plan->plan, best_path);
+	copy_generic_path_info(&scan_plan->scan.plan, best_path);
 
 	return scan_plan;
 }
@@ -5372,13 +5372,13 @@ make_seqscan(List *qptlist,
 			 Index scanrelid)
 {
 	SeqScan    *node = makeNode(SeqScan);
-	Plan	   *plan = &node->plan;
+	Plan	   *plan = &node->scan.plan;
 
 	plan->targetlist = qptlist;
 	plan->qual = qpqual;
 	plan->lefttree = NULL;
 	plan->righttree = NULL;
-	node->scanrelid = scanrelid;
+	node->scan.scanrelid = scanrelid;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 61ccfd300b..6cb2d731c8 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -516,12 +516,12 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			{
 				SeqScan    *splan = (SeqScan *) plan;
 
-				splan->scanrelid += rtoffset;
-				splan->plan.targetlist =
-					fix_scan_list(root, splan->plan.targetlist,
+				splan->scan.scanrelid += rtoffset;
+				splan->scan.plan.targetlist =
+					fix_scan_list(root, splan->scan.plan.targetlist,
 								  rtoffset, NUM_EXEC_TLIST(plan));
-				splan->plan.qual =
-					fix_scan_list(root, splan->plan.qual,
+				splan->scan.plan.qual =
+					fix_scan_list(root, splan->scan.plan.qual,
 								  rtoffset, NUM_EXEC_QUAL(plan));
 			}
 			break;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaa3b65d04..35ff94fbb6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -348,7 +348,10 @@ typedef struct Scan
  *		sequential scan node
  * ----------------
  */
-typedef Scan SeqScan;
+typedef struct SeqScan
+{
+	Scan		scan;
+} SeqScan;
 
 /* ----------------
  *		table sample scan node
-- 
2.31.1

v1-0005-Change-NestPath-node-to-contain-JoinPath-node.patchtext/plain; charset=UTF-8; name=v1-0005-Change-NestPath-node-to-contain-JoinPath-node.patch; x-mac-creator=0; x-mac-type=0Download
From d3dc7a83e23fe89a2767716a51c3d182f0719148 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 16:00:31 +0200
Subject: [PATCH v1 05/10] Change NestPath node to contain JoinPath node

This makes the structure of all JoinPath-derived nodes the same,
independent of whether they have additional fields.
---
 src/backend/optimizer/path/costsize.c   | 35 +++++++++++++------------
 src/backend/optimizer/plan/createplan.c | 24 ++++++++---------
 src/backend/optimizer/util/pathnode.c   | 32 +++++++++++-----------
 src/include/nodes/pathnodes.h           |  5 +++-
 4 files changed, 51 insertions(+), 45 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 8577c7b138..64891342fc 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -167,7 +167,7 @@ static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
 static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
 									  ParamPathInfo *param_info,
 									  QualCost *qpqual_cost);
-static bool has_indexed_join_quals(NestPath *joinpath);
+static bool has_indexed_join_quals(NestPath *path);
 static double approx_tuple_count(PlannerInfo *root, JoinPath *path,
 								 List *quals);
 static double calc_joinrel_size_estimate(PlannerInfo *root,
@@ -2978,8 +2978,8 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
 					JoinCostWorkspace *workspace,
 					JoinPathExtraData *extra)
 {
-	Path	   *outer_path = path->outerjoinpath;
-	Path	   *inner_path = path->innerjoinpath;
+	Path	   *outer_path = path->jpath.outerjoinpath;
+	Path	   *inner_path = path->jpath.innerjoinpath;
 	double		outer_path_rows = outer_path->rows;
 	double		inner_path_rows = inner_path->rows;
 	Cost		startup_cost = workspace->startup_cost;
@@ -2994,18 +2994,18 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
 	if (inner_path_rows <= 0)
 		inner_path_rows = 1;
 	/* Mark the path with the correct row estimate */
-	if (path->path.param_info)
-		path->path.rows = path->path.param_info->ppi_rows;
+	if (path->jpath.path.param_info)
+		path->jpath.path.rows = path->jpath.path.param_info->ppi_rows;
 	else
-		path->path.rows = path->path.parent->rows;
+		path->jpath.path.rows = path->jpath.path.parent->rows;
 
 	/* For partial paths, scale row estimate. */
-	if (path->path.parallel_workers > 0)
+	if (path->jpath.path.parallel_workers > 0)
 	{
-		double		parallel_divisor = get_parallel_divisor(&path->path);
+		double		parallel_divisor = get_parallel_divisor(&path->jpath.path);
 
-		path->path.rows =
-			clamp_row_est(path->path.rows / parallel_divisor);
+		path->jpath.path.rows =
+			clamp_row_est(path->jpath.path.rows / parallel_divisor);
 	}
 
 	/*
@@ -3018,7 +3018,7 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
 
 	/* cost of inner-relation source data (we already dealt with outer rel) */
 
-	if (path->jointype == JOIN_SEMI || path->jointype == JOIN_ANTI ||
+	if (path->jpath.jointype == JOIN_SEMI || path->jpath.jointype == JOIN_ANTI ||
 		extra->inner_unique)
 	{
 		/*
@@ -3136,17 +3136,17 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
 	}
 
 	/* CPU costs */
-	cost_qual_eval(&restrict_qual_cost, path->joinrestrictinfo, root);
+	cost_qual_eval(&restrict_qual_cost, path->jpath.joinrestrictinfo, root);
 	startup_cost += restrict_qual_cost.startup;
 	cpu_per_tuple = cpu_tuple_cost + restrict_qual_cost.per_tuple;
 	run_cost += cpu_per_tuple * ntuples;
 
 	/* tlist eval costs are paid per output row, not per tuple scanned */
-	startup_cost += path->path.pathtarget->cost.startup;
-	run_cost += path->path.pathtarget->cost.per_tuple * path->path.rows;
+	startup_cost += path->jpath.path.pathtarget->cost.startup;
+	run_cost += path->jpath.path.pathtarget->cost.per_tuple * path->jpath.path.rows;
 
-	path->path.startup_cost = startup_cost;
-	path->path.total_cost = startup_cost + run_cost;
+	path->jpath.path.startup_cost = startup_cost;
+	path->jpath.path.total_cost = startup_cost + run_cost;
 }
 
 /*
@@ -4774,8 +4774,9 @@ compute_semi_anti_join_factors(PlannerInfo *root,
  * expensive.
  */
 static bool
-has_indexed_join_quals(NestPath *joinpath)
+has_indexed_join_quals(NestPath *path)
 {
+	JoinPath   *joinpath = &path->jpath;
 	Relids		joinrelids = joinpath->path.parent->relids;
 	Path	   *innerpath = joinpath->innerjoinpath;
 	List	   *indexclauses;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 769b5a0cf7..06a8588f4e 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4224,8 +4224,8 @@ create_nestloop_plan(PlannerInfo *root,
 	NestLoop   *join_plan;
 	Plan	   *outer_plan;
 	Plan	   *inner_plan;
-	List	   *tlist = build_path_tlist(root, &best_path->path);
-	List	   *joinrestrictclauses = best_path->joinrestrictinfo;
+	List	   *tlist = build_path_tlist(root, &best_path->jpath.path);
+	List	   *joinrestrictclauses = best_path->jpath.joinrestrictinfo;
 	List	   *joinclauses;
 	List	   *otherclauses;
 	Relids		outerrelids;
@@ -4233,13 +4233,13 @@ create_nestloop_plan(PlannerInfo *root,
 	Relids		saveOuterRels = root->curOuterRels;
 
 	/* NestLoop can project, so no need to be picky about child tlists */
-	outer_plan = create_plan_recurse(root, best_path->outerjoinpath, 0);
+	outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath, 0);
 
 	/* For a nestloop, include outer relids in curOuterRels for inner side */
 	root->curOuterRels = bms_union(root->curOuterRels,
-								   best_path->outerjoinpath->parent->relids);
+								   best_path->jpath.outerjoinpath->parent->relids);
 
-	inner_plan = create_plan_recurse(root, best_path->innerjoinpath, 0);
+	inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath, 0);
 
 	/* Restore curOuterRels */
 	bms_free(root->curOuterRels);
@@ -4250,10 +4250,10 @@ create_nestloop_plan(PlannerInfo *root,
 
 	/* Get the join qual clauses (in plain expression form) */
 	/* Any pseudoconstant clauses are ignored here */
-	if (IS_OUTER_JOIN(best_path->jointype))
+	if (IS_OUTER_JOIN(best_path->jpath.jointype))
 	{
 		extract_actual_join_clauses(joinrestrictclauses,
-									best_path->path.parent->relids,
+									best_path->jpath.path.parent->relids,
 									&joinclauses, &otherclauses);
 	}
 	else
@@ -4264,7 +4264,7 @@ create_nestloop_plan(PlannerInfo *root,
 	}
 
 	/* Replace any outer-relation variables with nestloop params */
-	if (best_path->path.param_info)
+	if (best_path->jpath.path.param_info)
 	{
 		joinclauses = (List *)
 			replace_nestloop_params(root, (Node *) joinclauses);
@@ -4276,7 +4276,7 @@ create_nestloop_plan(PlannerInfo *root,
 	 * Identify any nestloop parameters that should be supplied by this join
 	 * node, and remove them from root->curOuterParams.
 	 */
-	outerrelids = best_path->outerjoinpath->parent->relids;
+	outerrelids = best_path->jpath.outerjoinpath->parent->relids;
 	nestParams = identify_current_nestloop_params(root, outerrelids);
 
 	join_plan = make_nestloop(tlist,
@@ -4285,10 +4285,10 @@ create_nestloop_plan(PlannerInfo *root,
 							  nestParams,
 							  outer_plan,
 							  inner_plan,
-							  best_path->jointype,
-							  best_path->inner_unique);
+							  best_path->jpath.jointype,
+							  best_path->jpath.inner_unique);
 
-	copy_generic_path_info(&join_plan->join.plan, &best_path->path);
+	copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
 
 	return join_plan;
 }
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 9ce5f95e3b..cd6ef1bb32 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2446,10 +2446,10 @@ create_nestloop_path(PlannerInfo *root,
 		restrict_clauses = jclauses;
 	}
 
-	pathnode->path.pathtype = T_NestLoop;
-	pathnode->path.parent = joinrel;
-	pathnode->path.pathtarget = joinrel->reltarget;
-	pathnode->path.param_info =
+	pathnode->jpath.path.pathtype = T_NestLoop;
+	pathnode->jpath.path.parent = joinrel;
+	pathnode->jpath.path.pathtarget = joinrel->reltarget;
+	pathnode->jpath.path.param_info =
 		get_joinrel_parampathinfo(root,
 								  joinrel,
 								  outer_path,
@@ -2457,17 +2457,17 @@ create_nestloop_path(PlannerInfo *root,
 								  extra->sjinfo,
 								  required_outer,
 								  &restrict_clauses);
-	pathnode->path.parallel_aware = false;
-	pathnode->path.parallel_safe = joinrel->consider_parallel &&
+	pathnode->jpath.path.parallel_aware = false;
+	pathnode->jpath.path.parallel_safe = joinrel->consider_parallel &&
 		outer_path->parallel_safe && inner_path->parallel_safe;
 	/* This is a foolish way to estimate parallel_workers, but for now... */
-	pathnode->path.parallel_workers = outer_path->parallel_workers;
-	pathnode->path.pathkeys = pathkeys;
-	pathnode->jointype = jointype;
-	pathnode->inner_unique = extra->inner_unique;
-	pathnode->outerjoinpath = outer_path;
-	pathnode->innerjoinpath = inner_path;
-	pathnode->joinrestrictinfo = restrict_clauses;
+	pathnode->jpath.path.parallel_workers = outer_path->parallel_workers;
+	pathnode->jpath.path.pathkeys = pathkeys;
+	pathnode->jpath.jointype = jointype;
+	pathnode->jpath.inner_unique = extra->inner_unique;
+	pathnode->jpath.outerjoinpath = outer_path;
+	pathnode->jpath.innerjoinpath = inner_path;
+	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 
 	final_cost_nestloop(root, pathnode, workspace, extra);
 
@@ -4113,13 +4113,15 @@ do { \
 		case T_NestPath:
 			{
 				JoinPath   *jpath;
+				NestPath   *npath;
 
-				FLAT_COPY_PATH(jpath, path, NestPath);
+				FLAT_COPY_PATH(npath, path, NestPath);
 
+				jpath = (JoinPath *) npath;
 				REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath);
 				REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
 				ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
-				new_path = (Path *) jpath;
+				new_path = (Path *) npath;
 			}
 			break;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b7b2817a5d..f8ddb9e910 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1595,7 +1595,10 @@ typedef struct JoinPath
  * A nested-loop path needs no special fields.
  */
 
-typedef JoinPath NestPath;
+typedef struct NestPath
+{
+	JoinPath	jpath;
+} NestPath;
 
 /*
  * A mergejoin path has these fields.
-- 
2.31.1

v1-0006-Add-missing-enum-tags-in-enums-used-in-nodes.patchtext/plain; charset=UTF-8; name=v1-0006-Add-missing-enum-tags-in-enums-used-in-nodes.patch; x-mac-creator=0; x-mac-type=0Download
From 3d66bc6725594d5d187fbd9a023a0ed91bf22eaa Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 16:03:07 +0200
Subject: [PATCH v1 06/10] Add missing enum tags in enums used in nodes

---
 src/include/nodes/parsenodes.h | 4 ++--
 src/include/nodes/pathnodes.h  | 2 +-
 src/include/nodes/primnodes.h  | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342019..cd9115dbb5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1337,7 +1337,7 @@ typedef struct SortGroupClause
  *
  * SETS( SIMPLE(1,2), CUBE( SIMPLE(3), SIMPLE(4,5) ) )
  */
-typedef enum
+typedef enum GroupingSetKind
 {
 	GROUPING_SET_EMPTY,
 	GROUPING_SET_SIMPLE,
@@ -2113,7 +2113,7 @@ typedef struct CopyStmt
  * preserve the distinction in VariableSetKind for CreateCommandTag().
  * ----------------------
  */
-typedef enum
+typedef enum VariableSetKind
 {
 	VAR_SET_VALUE,				/* SET var = value */
 	VAR_SET_DEFAULT,			/* SET var TO DEFAULT */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index f8ddb9e910..03c80c1620 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1525,7 +1525,7 @@ typedef struct ResultCachePath
  * it's convenient to have a UniquePath in the path tree to signal upper-level
  * routines that the input is known distinct.)
  */
-typedef enum
+typedef enum UniquePathMethod
 {
 	UNIQUE_PATH_NOOP,			/* input is known unique already */
 	UNIQUE_PATH_HASH,			/* use hashing */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d847..3418e23873 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1205,7 +1205,7 @@ typedef enum XmlExprOp
 	IS_DOCUMENT					/* xmlval IS DOCUMENT */
 } XmlExprOp;
 
-typedef enum
+typedef enum XmlOptionType
 {
 	XMLOPTION_DOCUMENT,
 	XMLOPTION_CONTENT
-- 
2.31.1

v1-0007-Check-the-size-in-COPY_POINTER_FIELD.patchtext/plain; charset=UTF-8; name=v1-0007-Check-the-size-in-COPY_POINTER_FIELD.patch; x-mac-creator=0; x-mac-type=0Download
From bccacca061746724ae7687be8169b9d74b09acf1 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 16:04:12 +0200
Subject: [PATCH v1 07/10] Check the size in COPY_POINTER_FIELD

instead of making each caller do it.
---
 src/backend/nodes/copyfuncs.c | 54 ++++++++++++++---------------------
 1 file changed, 21 insertions(+), 33 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 35a85df442..dde2d47338 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -57,8 +57,11 @@
 #define COPY_POINTER_FIELD(fldname, sz) \
 	do { \
 		Size	_size = (sz); \
-		newnode->fldname = palloc(_size); \
-		memcpy(newnode->fldname, from->fldname, _size); \
+		if (_size > 0) \
+		{ \
+			newnode->fldname = palloc(_size); \
+			memcpy(newnode->fldname, from->fldname, _size); \
+		} \
 	} while (0)
 
 /* Copy a parse location field (for Copy, this is same as scalar case) */
@@ -296,12 +299,9 @@ _copyRecursiveUnion(const RecursiveUnion *from)
 	 */
 	COPY_SCALAR_FIELD(wtParam);
 	COPY_SCALAR_FIELD(numCols);
-	if (from->numCols > 0)
-	{
-		COPY_POINTER_FIELD(dupColIdx, from->numCols * sizeof(AttrNumber));
-		COPY_POINTER_FIELD(dupOperators, from->numCols * sizeof(Oid));
-		COPY_POINTER_FIELD(dupCollations, from->numCols * sizeof(Oid));
-	}
+	COPY_POINTER_FIELD(dupColIdx, from->numCols * sizeof(AttrNumber));
+	COPY_POINTER_FIELD(dupOperators, from->numCols * sizeof(Oid));
+	COPY_POINTER_FIELD(dupCollations, from->numCols * sizeof(Oid));
 	COPY_SCALAR_FIELD(numGroups);
 
 	return newnode;
@@ -896,13 +896,10 @@ _copyMergeJoin(const MergeJoin *from)
 	COPY_SCALAR_FIELD(skip_mark_restore);
 	COPY_NODE_FIELD(mergeclauses);
 	numCols = list_length(from->mergeclauses);
-	if (numCols > 0)
-	{
-		COPY_POINTER_FIELD(mergeFamilies, numCols * sizeof(Oid));
-		COPY_POINTER_FIELD(mergeCollations, numCols * sizeof(Oid));
-		COPY_POINTER_FIELD(mergeStrategies, numCols * sizeof(int));
-		COPY_POINTER_FIELD(mergeNullsFirst, numCols * sizeof(bool));
-	}
+	COPY_POINTER_FIELD(mergeFamilies, numCols * sizeof(Oid));
+	COPY_POINTER_FIELD(mergeCollations, numCols * sizeof(Oid));
+	COPY_POINTER_FIELD(mergeStrategies, numCols * sizeof(int));
+	COPY_POINTER_FIELD(mergeNullsFirst, numCols * sizeof(bool));
 
 	return newnode;
 }
@@ -1064,12 +1061,9 @@ _copyAgg(const Agg *from)
 	COPY_SCALAR_FIELD(aggstrategy);
 	COPY_SCALAR_FIELD(aggsplit);
 	COPY_SCALAR_FIELD(numCols);
-	if (from->numCols > 0)
-	{
-		COPY_POINTER_FIELD(grpColIdx, from->numCols * sizeof(AttrNumber));
-		COPY_POINTER_FIELD(grpOperators, from->numCols * sizeof(Oid));
-		COPY_POINTER_FIELD(grpCollations, from->numCols * sizeof(Oid));
-	}
+	COPY_POINTER_FIELD(grpColIdx, from->numCols * sizeof(AttrNumber));
+	COPY_POINTER_FIELD(grpOperators, from->numCols * sizeof(Oid));
+	COPY_POINTER_FIELD(grpCollations, from->numCols * sizeof(Oid));
 	COPY_SCALAR_FIELD(numGroups);
 	COPY_SCALAR_FIELD(transitionSpace);
 	COPY_BITMAPSET_FIELD(aggParams);
@@ -1091,19 +1085,13 @@ _copyWindowAgg(const WindowAgg *from)
 
 	COPY_SCALAR_FIELD(winref);
 	COPY_SCALAR_FIELD(partNumCols);
-	if (from->partNumCols > 0)
-	{
-		COPY_POINTER_FIELD(partColIdx, from->partNumCols * sizeof(AttrNumber));
-		COPY_POINTER_FIELD(partOperators, from->partNumCols * sizeof(Oid));
-		COPY_POINTER_FIELD(partCollations, from->partNumCols * sizeof(Oid));
-	}
+	COPY_POINTER_FIELD(partColIdx, from->partNumCols * sizeof(AttrNumber));
+	COPY_POINTER_FIELD(partOperators, from->partNumCols * sizeof(Oid));
+	COPY_POINTER_FIELD(partCollations, from->partNumCols * sizeof(Oid));
 	COPY_SCALAR_FIELD(ordNumCols);
-	if (from->ordNumCols > 0)
-	{
-		COPY_POINTER_FIELD(ordColIdx, from->ordNumCols * sizeof(AttrNumber));
-		COPY_POINTER_FIELD(ordOperators, from->ordNumCols * sizeof(Oid));
-		COPY_POINTER_FIELD(ordCollations, from->ordNumCols * sizeof(Oid));
-	}
+	COPY_POINTER_FIELD(ordColIdx, from->ordNumCols * sizeof(AttrNumber));
+	COPY_POINTER_FIELD(ordOperators, from->ordNumCols * sizeof(Oid));
+	COPY_POINTER_FIELD(ordCollations, from->ordNumCols * sizeof(Oid));
 	COPY_SCALAR_FIELD(frameOptions);
 	COPY_NODE_FIELD(startOffset);
 	COPY_NODE_FIELD(endOffset);
-- 
2.31.1

v1-0008-Remove-T_MemoryContext.patchtext/plain; charset=UTF-8; name=v1-0008-Remove-T_MemoryContext.patch; x-mac-creator=0; x-mac-type=0Download
From 00fa8e67a141c90e38fcbec45e1cc2135c4fda5a Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 16:05:16 +0200
Subject: [PATCH v1 08/10] Remove T_MemoryContext

This is an abstract node that shouldn't have a node tag defined.
---
 src/include/nodes/nodes.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..5e049a67e1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -284,7 +284,6 @@ typedef enum NodeTag
 	/*
 	 * TAGS FOR MEMORY NODES (memnodes.h)
 	 */
-	T_MemoryContext,
 	T_AllocSetContext,
 	T_SlabContext,
 	T_GenerationContext,
-- 
2.31.1

v1-0009-Add-script-to-generate-node-support-functions.patchtext/plain; charset=UTF-8; name=v1-0009-Add-script-to-generate-node-support-functions.patch; x-mac-creator=0; x-mac-type=0Download
From 79da68dba05e3ea3b506c4ebf6ea1a1c1e2de6f3 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 16:08:54 +0200
Subject: [PATCH v1 09/10] Add script to generate node support functions

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

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 0da848b1fd..a33db1ae01 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -143,11 +143,15 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
 submake-catalog-headers:
 	$(MAKE) -C catalog distprep generated-header-symlinks
 
+# run this unconditionally to avoid needing to know its dependencies here:
+submake-nodes-headers:
+	$(MAKE) -C nodes distprep generated-header-symlinks
+
 # run this unconditionally to avoid needing to know its dependencies here:
 submake-utils-headers:
 	$(MAKE) -C utils distprep generated-header-symlinks
 
-.PHONY: submake-catalog-headers submake-utils-headers
+.PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 # Make symlinks for these headers in the include directory. That way
 # we can cut down on the -I options. Also, a symlink is automatically
@@ -162,7 +166,7 @@ submake-utils-headers:
 
 .PHONY: generated-headers
 
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..232c6e1817
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,3 @@
+/node-stuff-stamp
+/nodetags.h
+/*funcs.inc?.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..eb93166e9e 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,58 @@ OBJS = \
 	value.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+node_headers = \
+	nodes/nodes.h \
+	nodes/execnodes.h \
+	nodes/plannodes.h \
+	nodes/primnodes.h \
+	nodes/pathnodes.h \
+	nodes/memnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.h \
+	commands/trigger.h \
+	commands/event_trigger.h \
+	foreign/fdwapi.h \
+	access/amapi.h \
+	access/tableam.h \
+	access/tsmapi.h \
+	utils/rel.h \
+	nodes/supportnodes.h \
+	executor/tuptable.h \
+	nodes/lockoptions.h \
+	access/sdir.h
+
+node_sources = \
+	executor/nodeWindowAgg.c \
+	nodes/tidbitmap.c \
+	utils/mmgr/aset.c \
+	utils/mmgr/generation.c \
+	utils/mmgr/slab.c
+
+node_files = $(addprefix $(top_srcdir)/src/include/,$(node_headers)) $(addprefix $(top_srcdir)/src/backend/,$(node_sources))
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-stuff-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+node-stuff-stamp: gen_node_stuff.pl
+	$(PERL) $< $(node_files)
+	touch $@
+
+$(top_builddir)/src/include/nodes/header-stamp: node-stuff-stamp
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	cd '$(dir $@)' && for file in nodetags.h; do \
+	  rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \
+	done
+	touch $@
+
+maintainer-clean: clean
+	rm -f node-stuff-stamp *funcs.inc?.c nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index dde2d47338..4f57923e9e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -69,6 +69,9 @@
 	(newnode->fldname = from->fldname)
 
 
+#include "copyfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /* ****************************************************************
  *					 plannodes.h copy functions
  * ****************************************************************
@@ -1450,6 +1453,7 @@ _copyVar(const Var *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * _copyConst
@@ -1489,6 +1493,7 @@ _copyConst(const Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -2722,6 +2727,7 @@ _copyParamRef(const ParamRef *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -2754,6 +2760,7 @@ _copyA_Const(const A_Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -4863,6 +4870,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *					extensible.h copy functions
@@ -4919,6 +4927,7 @@ _copyValue(const Value *from)
 }
 
 
+#ifdef OBSOLETE
 static ForeignKeyCacheInfo *
 _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 {
@@ -4935,6 +4944,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 
 /*
@@ -4956,6 +4966,8 @@ copyObjectImpl(const void *from)
 
 	switch (nodeTag(from))
 	{
+#include "copyfuncs.inc2.c"
+#ifdef OBSOLETE
 			/*
 			 * PLAN NODES
 			 */
@@ -5297,6 +5309,7 @@ copyObjectImpl(const void *from)
 		case T_PlaceHolderInfo:
 			retval = _copyPlaceHolderInfo(from);
 			break;
+#endif /*OBSOLETE*/
 
 			/*
 			 * VALUE NODES
@@ -5325,6 +5338,7 @@ copyObjectImpl(const void *from)
 			retval = list_copy(from);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -5858,6 +5872,7 @@ copyObjectImpl(const void *from)
 		case T_ForeignKeyCacheInfo:
 			retval = _copyForeignKeyCacheInfo(from);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e04ec41904..6757337062 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -10,9 +10,6 @@
  * because the circular linkages between RelOptInfo and Path nodes can't
  * be handled easily in a simple depth-first traversal.
  *
- * Currently, in fact, equal() doesn't know how to compare Plan trees
- * either.  This might need to be fixed someday.
- *
  * NOTE: it is intentional that parse location fields (in nodes that have
  * one) are not compared.  This is because we want, for example, a variable
  * "x" to be considered equal() to another reference to "x" in the query.
@@ -33,6 +30,7 @@
 #include "nodes/extensible.h"
 #include "nodes/pathnodes.h"
 #include "utils/datum.h"
+#include "utils/rel.h"
 
 
 /*
@@ -90,6 +88,9 @@
 	((void) 0)
 
 
+#include "equalfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from primnodes.h
  */
@@ -178,6 +179,7 @@ _equalVar(const Var *a, const Var *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -200,6 +202,7 @@ _equalConst(const Const *a, const Const *b)
 						a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -933,6 +936,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -954,6 +958,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const ExtensibleNode *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2397,6 +2402,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2408,6 +2414,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3016,6 +3023,7 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3135,6 +3143,8 @@ equal(const void *a, const void *b)
 
 	switch (nodeTag(a))
 	{
+#include "equalfuncs.inc2.c"
+#ifdef OBSOLETE
 			/*
 			 * PRIMITIVE NODES
 			 */
@@ -3313,6 +3323,7 @@ equal(const void *a, const void *b)
 		case T_PlaceHolderInfo:
 			retval = _equalPlaceHolderInfo(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		case T_List:
 		case T_IntList:
@@ -3328,6 +3339,7 @@ equal(const void *a, const void *b)
 			retval = _equalValue(a, b);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -3854,6 +3866,7 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/gen_node_stuff.pl b/src/backend/nodes/gen_node_stuff.pl
new file mode 100644
index 0000000000..aafe320e0a
--- /dev/null
+++ b/src/backend/nodes/gen_node_stuff.pl
@@ -0,0 +1,641 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# src/backend/nodes/gen_node_stuff.pl
+#
+#----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use experimental 'smartmatch';
+
+use File::Basename;
+
+use FindBin;
+use lib "$FindBin::RealBin/../catalog";
+
+use Catalog;  # for RenameTempFile
+
+
+my @node_types = qw(Node);
+my %node_type_info;
+
+my @no_copy;
+my @no_read_write;
+
+my @scalar_types = qw(
+	bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+	AclMode AttrNumber Cost Index Oid Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr
+);
+
+my @enum_types;
+
+# For abstract types we track their fields, so that subtypes can use
+# them, but we don't emit a node tag, so you can't instantiate them.
+my @abstract_types = qw(
+	Node
+	BufferHeapTupleTableSlot HeapTupleTableSlot MinimalTupleTableSlot VirtualTupleTableSlot
+	JoinPath
+	MemoryContextData
+	PartitionPruneStep
+);
+
+# These are additional node tags that don't have their own struct.
+my @extra_tags = qw(IntList OidList Integer Float String BitString Null);
+
+# These are regular nodes, but we skip parsing them from their header
+# files since we won't use their internal structure here anyway.
+push @node_types, qw(List Value);
+
+# XXX maybe this should be abstract?
+push @no_copy, qw(Expr);
+push @no_read_write, qw(Expr);
+
+# pathnodes.h exceptions
+push @no_copy, qw(
+	RelOptInfo IndexOptInfo Path PlannerGlobal EquivalenceClass EquivalenceMember ForeignKeyOptInfo
+	GroupingSetData IncrementalSortPath IndexClause MinMaxAggInfo PathTarget PlannerInfo PlannerParamItem
+	ParamPathInfo RollupData RowIdentityVarInfo StatisticExtInfo
+);
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember* QualCost);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation);
+
+
+## read input
+
+foreach my $infile (@ARGV)
+{
+	my $in_struct;
+	my $subline;
+	my $is_node_struct;
+	my $supertype;
+	my $supertype_field;
+
+	my @my_fields;
+	my %my_field_types;
+	my %my_field_attrs;
+
+	open my $ifh, '<', $infile or die "could not open \"$infile\": $!";
+
+	while (my $line = <$ifh>)
+	{
+		chomp $line;
+		$line =~ s!/\*.*$!!;
+		$line =~ s/\s*$//;
+		next if $line eq '';
+		next if $line =~ m!^\s*\*.*$!;  # line starts with *, probably comment continuation
+		next if $line =~ /^#(define|ifdef|endif)/;
+
+		if ($in_struct)
+		{
+			$subline++;
+
+			# first line should have opening brace
+			if ($subline == 1)
+			{
+				$is_node_struct = 0;
+				$supertype = undef;
+				next if $line eq '{';
+				die;
+			}
+			# second line should have node tag or supertype
+			elsif ($subline == 2)
+			{
+				if ($line =~ /^\s*NodeTag\s+type;/)
+				{
+					$is_node_struct = 1;
+					next;
+				}
+				elsif ($line =~ /\s*(\w+)\s+(\w+);/ && $1 ~~ @node_types)
+				{
+					$is_node_struct = 1;
+					$supertype = $1;
+					$supertype_field = $2;
+					next;
+				}
+			}
+
+			# end of struct
+			if ($line =~ /^\}\s*$in_struct;$/ || $line =~ /^\};$/)
+			{
+				if ($is_node_struct)
+				{
+					push @node_types, $in_struct;
+					my @f = @my_fields;
+					my %ft = %my_field_types;
+					my %fa = %my_field_attrs;
+					if ($supertype)
+					{
+						my @superfields;
+						foreach my $sf (@{$node_type_info{$supertype}->{fields}})
+						{
+							my $fn = "${supertype_field}.$sf";
+							push @superfields, $fn;
+							$ft{$fn} = $node_type_info{$supertype}->{field_types}{$sf};
+							$fa{$fn} = $node_type_info{$supertype}->{field_attrs}{$sf};
+						}
+						unshift @f, @superfields;
+					}
+					$node_type_info{$in_struct}->{fields} = \@f;
+					$node_type_info{$in_struct}->{field_types} = \%ft;
+					$node_type_info{$in_struct}->{field_attrs} = \%fa;
+
+					if (basename($infile) eq 'execnodes.h' ||
+						basename($infile) eq 'trigger.h' ||
+						basename($infile) eq 'event_trigger.h' ||
+						basename($infile) eq 'amapi.h' ||
+						basename($infile) eq 'tableam.h' ||
+						basename($infile) eq 'tsmapi.h' ||
+						basename($infile) eq 'fdwapi.h' ||
+						basename($infile) eq 'tuptable.h' ||
+						basename($infile) eq 'replnodes.h' ||
+						basename($infile) eq 'supportnodes.h' ||
+						$infile =~ /\.c$/
+					)
+					{
+						push @no_copy, $in_struct;
+						push @no_read_write, $in_struct;
+					}
+
+					if ($supertype && ($supertype eq 'Path' || $supertype eq 'JoinPath'))
+					{
+						push @no_copy, $in_struct;
+					}
+				}
+
+				# start new cycle
+				$in_struct = undef;
+				@my_fields = ();
+				%my_field_types = ();
+				%my_field_attrs = ();
+			}
+			# normal struct field
+			elsif ($line =~ /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w ]*)\))?;/)
+			{
+				if ($is_node_struct)
+				{
+					my $type = $1;
+					my $name = $2;
+					my $array_size = $3;
+					my $attr = $4;
+
+					$type =~ s/^const\s*//;
+					$type =~ s/\s*$//;
+					$type =~ s/\s+\*$/*/;
+					die if $type eq '';
+					$type = $type . $array_size if $array_size;
+					push @my_fields, $name;
+					$my_field_types{$name} = $type;
+					$my_field_attrs{$name} = $attr;
+				}
+			}
+			else
+			{
+				if ($is_node_struct)
+				{
+					#warn "$infile:$.: could not parse \"$line\"\n";
+				}
+			}
+		}
+		# not in a struct
+		else
+		{
+			# start of a struct?
+			if ($line =~ /^(?:typedef )?struct (\w+)(\s*\/\*.*)?$/ && $1 ne 'Node')
+			{
+				$in_struct = $1;
+				$subline = 0;
+			}
+			# one node type typedef'ed directly from another
+			elsif ($line =~ /^typedef (\w+) (\w+);$/ && $1 ~~ @node_types)
+			{
+				my $alias_of = $1;
+				my $n = $2;
+
+				push @node_types, $n;
+				my @f = @{$node_type_info{$alias_of}->{fields}};
+				my %ft = %{$node_type_info{$alias_of}->{field_types}};
+				my %fa = %{$node_type_info{$alias_of}->{field_attrs}};
+				$node_type_info{$n}->{fields} = \@f;
+				$node_type_info{$n}->{field_types} = \%ft;
+				$node_type_info{$n}->{field_attrs} = \%fa;
+			}
+			# collect enum names
+			elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/)
+			{
+				push @enum_types, $1;
+			}
+		}
+	}
+
+	if ($in_struct)
+	{
+		die "runaway \"$in_struct\" in file \"$infile\"\n";
+	}
+
+	close $ifh;
+} # for each file
+
+
+## write output
+
+my $tmpext  = ".tmp$$";
+
+# nodetags.h
+
+open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
+
+my $i = 1;
+foreach my $n (@node_types,@extra_tags)
+{
+	next if $n ~~ @abstract_types;
+	print $nt "\tT_${n} = $i,\n";
+	$i++;
+}
+
+close $nt;
+
+
+# copyfuncs.c, equalfuncs.c
+
+open my $cf, '>', 'copyfuncs.inc1.c' . $tmpext or die $!;
+open my $ef, '>', 'equalfuncs.inc1.c' . $tmpext or die $!;
+open my $cf2, '>', 'copyfuncs.inc2.c' . $tmpext or die $!;
+open my $ef2, '>', 'equalfuncs.inc2.c' . $tmpext or die $!;
+
+my @custom_copy = qw(A_Const Const ExtensibleNode);
+
+foreach my $n (@node_types)
+{
+	next if $n ~~ @abstract_types;
+	next if $n ~~ @no_copy;
+	next if $n eq 'List';
+	next if $n eq 'Value';
+
+	print $cf2 "
+\t\tcase T_${n}:
+\t\t\tretval = _copy${n}(from);
+\t\t\tbreak;";
+
+	print $ef2 "
+\t\tcase T_${n}:
+\t\t\tretval = _equal${n}(a, b);
+\t\t\tbreak;";
+
+	next if $n ~~ @custom_copy;
+
+	print $cf "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+";
+
+	print $ef "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+";
+
+	my $last_array_size_field;
+
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $copy_ignore = ($a =~ /\bcopy_ignore\b/);
+		my $equal_ignore = ($a =~ /\bequal_ignore\b/);
+		if ($t eq 'char*')
+		{
+			print $cf "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_STRING_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $cf "\tCOPY_BITMAPSET_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_BITMAPSET_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $cf "\tCOPY_LOCATION_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_LOCATION_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t ~~ @scalar_types || $t ~~ @enum_types)
+		{
+			print $cf "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			if ($a =~ /\bequal_ignore_if_zero\b/)
+			{
+				print $ef "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
+			}
+			else
+			{
+				print $ef "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+			}
+			$last_array_size_field = "from->$f";
+		}
+		elsif ($t =~ /(\w+)\*/ && $1 ~~ @scalar_types)
+		{
+			my $tt = $1;
+			print $cf "\tCOPY_POINTER_FIELD($f, $last_array_size_field * sizeof($tt));\n" unless $copy_ignore;
+			(my $l2 = $last_array_size_field) =~ s/from/a/;
+			print $ef "\tCOMPARE_POINTER_FIELD($f, $l2 * sizeof($tt));\n" unless $equal_ignore;
+		}
+		elsif ($t =~ /(\w+)\*/ && $1 ~~ @node_types)
+		{
+			print $cf "\tCOPY_NODE_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
+			$last_array_size_field = "list_length(from->$f)" if $t eq 'List*';
+		}
+		elsif ($t =~ /\w+\[/)
+		{
+			# COPY_SCALAR_FIELD might work for these, but let's not assume that
+			print $cf "\tmemcpy(newnode->$f, from->$f, sizeof(newnode->$f));\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_POINTER_FIELD($f, sizeof(a->$f));\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $cf "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $cf "
+\treturn newnode;
+}
+";
+	print $ef "
+\treturn true;
+}
+";
+}
+
+close $cf;
+close $ef;
+close $cf2;
+close $ef2;
+
+
+# outfuncs.c, readfuncs.c
+
+open my $of, '>', 'outfuncs.inc1.c' . $tmpext or die $!;
+open my $rf, '>', 'readfuncs.inc1.c' . $tmpext or die $!;
+open my $of2, '>', 'outfuncs.inc2.c' . $tmpext or die $!;
+open my $rf2, '>', 'readfuncs.inc2.c' . $tmpext or die $!;
+
+my %name_map = (
+	'ARRAYEXPR' => 'ARRAY',
+	'CASEEXPR' => 'CASE',
+	'CASEWHEN' => 'WHEN',
+	'COALESCEEXPR' => 'COALESCE',
+	'COLLATEEXPR' => 'COLLATE',
+	'DECLARECURSORSTMT' => 'DECLARECURSOR',
+	'MINMAXEXPR' => 'MINMAX',
+	'NOTIFYSTMT' => 'NOTIFY',
+	'RANGETBLENTRY' => 'RTE',
+	'ROWCOMPAREEXPR' => 'ROWCOMPARE',
+	'ROWEXPR' => 'ROW',
+);
+
+my @custom_readwrite = qw(A_Const A_Expr BoolExpr Const Constraint ExtensibleNode Query RangeTblEntry);
+
+foreach my $n (@node_types)
+{
+	next if $n ~~ @abstract_types;
+	next if $n ~~ @no_read_write;
+	next if $n eq 'List';
+	next if $n eq 'Value';
+
+	# XXX For now, skip all "Stmt"s except that ones that were there before.
+	if ($n =~ /Stmt$/)
+	{
+		my @keep = qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt ImportForeignSchemaStmt IndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt);
+		next unless $n ~~ @keep;
+	}
+
+	# XXX Also skip read support for those that didn't have it before.
+	my $no_read = ($n eq 'A_Star' || $n eq 'A_Const' || $n eq 'A_Expr' || $n eq 'Constraint' || $n =~ /Path$/ || $n eq 'ForeignKeyCacheInfo' || $n eq 'ForeignKeyOptInfo' || $n eq 'PathTarget');
+
+	my $N = uc $n;
+	$N =~ s/_//g;
+	$N = $name_map{$N} if $name_map{$N};
+
+	print $of2 "\t\t\tcase T_${n}:\n".
+	  "\t\t\t\t_out${n}(str, obj);\n".
+	  "\t\t\t\tbreak;\n";
+
+	print $rf2 "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+
+	next if $n ~~ @custom_readwrite;
+
+	print $of "
+static void
+_out${n}(StringInfo str, const $n *node)
+{
+\tWRITE_NODE_TYPE(\"$N\");
+
+";
+
+	print $rf "
+static $n *
+_read${n}(void)
+{
+\tREAD_LOCALS($n);
+
+" unless $no_read;
+
+	my $last_array_size_field;
+
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $readwrite_ignore = ($a =~ /\breadwrite_ignore\b/);
+		next if $readwrite_ignore;
+
+		# XXX Previously, for subtyping, only the leaf field name is
+		# used. Ponder whether we want to keep it that way.
+
+		if ($t eq 'bool')
+		{
+			print $of "\tWRITE_BOOL_FIELD($f);\n";
+			print $rf "\tREAD_BOOL_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $of "\tWRITE_LOCATION_FIELD($f);\n";
+			print $rf "\tREAD_LOCATION_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' || $t eq 'int32' || $t eq 'AttrNumber' || $t eq 'StrategyNumber')
+		{
+			print $of "\tWRITE_INT_FIELD($f);\n";
+			print $rf "\tREAD_INT_FIELD($f);\n" unless $no_read;
+			$last_array_size_field = "node->$f" if $t eq 'int';
+		}
+		elsif ($t eq 'uint32' || $t eq 'bits32' || $t eq 'AclMode' || $t eq 'BlockNumber' || $t eq 'Index' || $t eq 'SubTransactionId')
+		{
+			print $of "\tWRITE_UINT_FIELD($f);\n";
+			print $rf "\tREAD_UINT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint64')
+		{
+			print $of "\tWRITE_UINT64_FIELD($f);\n";
+			print $rf "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Oid')
+		{
+			print $of "\tWRITE_OID_FIELD($f);\n";
+			print $rf "\tREAD_OID_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'long')
+		{
+			print $of "\tWRITE_LONG_FIELD($f);\n";
+			print $rf "\tREAD_LONG_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char')
+		{
+			print $of "\tWRITE_CHAR_FIELD($f);\n";
+			print $rf "\tREAD_CHAR_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'double')
+		{
+			# XXX We out to split these into separate types, like Cost
+			# etc.
+			if ($f eq 'allvisfrac')
+			{
+				print $of "\tWRITE_FLOAT_FIELD($f, \"%.6f\");\n";
+			}
+			else
+			{
+				print $of "\tWRITE_FLOAT_FIELD($f, \"%.0f\");\n";
+			}
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cost')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.2f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'QualCost')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f.startup, \"%.2f\");\n";
+			print $of "\tWRITE_FLOAT_FIELD($f.per_tuple, \"%.2f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read;
+			print $rf "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
+		}
+		elsif ($t eq 'Selectivity')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.4f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $of "\tWRITE_STRING_FIELD($f);\n";
+			print $rf "\tREAD_STRING_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $of "\tWRITE_BITMAPSET_FIELD($f);\n";
+			print $rf "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t ~~ @enum_types)
+		{
+			print $of "\tWRITE_ENUM_FIELD($f, $t);\n";
+			print $rf "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read;
+		}
+		elsif ($t =~ /(\w+)(\*|\[)/ && $1 ~~ @scalar_types)
+		{
+			warn "$t $n.$f" unless $last_array_size_field;
+			my $tt = uc $1;
+			print $of "\tWRITE_${tt}_ARRAY($f, $last_array_size_field);\n";
+			(my $l2 = $last_array_size_field) =~ s/node/local_node/;
+			print $rf "\tREAD_${tt}_ARRAY($f, $l2);\n" unless $no_read;
+		}
+		elsif ($t eq 'RelOptInfo*' && $a eq 'path_hack1')
+		{
+			print $of "\tappendStringInfoString(str, \" :parent_relids \");\n".
+			  "\toutBitmapset(str, node->$f->relids);\n";
+		}
+		elsif ($t eq 'PathTarget*' && $a eq 'path_hack2')
+		{
+			(my $f2 = $f) =~ s/pathtarget/parent/;
+			print $of "\tif (node->$f != node->$f2->reltarget)\n".
+			  "\t\tWRITE_NODE_FIELD($f);\n";
+		}
+		elsif ($t eq 'ParamPathInfo*' && $a eq 'path_hack3')
+		{
+			print $of "\tif (node->$f)\n".
+			  "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
+			  "\telse\n".
+			  "\t\toutBitmapset(str, NULL);\n";
+		}
+		elsif ($t =~ /(\w+)\*/ && $1 ~~ @node_types)
+		{
+			print $of "\tWRITE_NODE_FIELD($f);\n";
+			print $rf "\tREAD_NODE_FIELD($f);\n" unless $no_read;
+			$last_array_size_field = "list_length(node->$f)" if $t eq 'List*';
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $of q{
+	appendStringInfoString(str, " :methods ");
+	outToken(str, node->methods->CustomName);
+};
+			print $rf q!
+	{
+		/* Lookup CustomScanMethods by CustomName */
+		char	   *custom_name;
+		const CustomScanMethods *methods;
+		token = pg_strtok(&length); /* skip methods: */
+		token = pg_strtok(&length); /* CustomName */
+		custom_name = nullable_string(token, length);
+		methods = GetCustomScanMethods(custom_name, false);
+		local_node->methods = methods;
+	}
+! unless $no_read;
+		}
+		elsif ($t eq 'ParamListInfo' || $t =~ /PartitionBoundInfoData/ || $t eq 'PartitionDirectory' || $t eq 'PartitionScheme' || $t eq 'void*' || $t =~ /\*\*$/)
+		{
+			# ignore
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $of "}
+";
+	print $rf "
+\tREAD_DONE();
+}
+" unless $no_read;
+}
+
+close $of;
+close $rf;
+close $of2;
+close $rf2;
+
+
+foreach my $file (qw(nodetags.h copyfuncs.inc1.c copyfuncs.inc2.c equalfuncs.inc1.c equalfuncs.inc2.c outfuncs.inc1.c outfuncs.inc2.c readfuncs.inc1.c readfuncs.inc2.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ed08f5acf4..0978bc4e81 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -124,6 +124,8 @@ static void outChar(StringInfo str, char c);
 			appendStringInfo(str, " %u", node->fldname[i]); \
 	} while(0)
 
+#define WRITE_INDEX_ARRAY(fldname, len) WRITE_OID_ARRAY(fldname, len)
+
 #define WRITE_INT_ARRAY(fldname, len) \
 	do { \
 		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
@@ -288,6 +290,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
 }
 
 
+#include "outfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from plannodes.h
  */
@@ -1126,6 +1131,7 @@ _outVar(StringInfo str, const Var *node)
 	WRITE_INT_FIELD(varattnosyn);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outConst(StringInfo str, const Const *node)
@@ -1147,6 +1153,7 @@ _outConst(StringInfo str, const Const *node)
 		outDatum(str, node->constvalue, node->constlen, node->constbyval);
 }
 
+#ifdef OBSOLETE
 static void
 _outParam(StringInfo str, const Param *node)
 {
@@ -1316,6 +1323,7 @@ _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 	WRITE_NODE_FIELD(args);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outBoolExpr(StringInfo str, const BoolExpr *node)
@@ -1344,6 +1352,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2658,6 +2667,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
 	WRITE_NODE_FIELD(item);
 	WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2680,6 +2690,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -3012,6 +3023,7 @@ _outStatsElem(StringInfo str, const StatsElem *node)
 	WRITE_STRING_FIELD(name);
 	WRITE_NODE_FIELD(expr);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outQuery(StringInfo str, const Query *node)
@@ -3084,6 +3096,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3222,6 +3235,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_NODE_FIELD(colCollations);
 	WRITE_NODE_FIELD(groupClauses);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
@@ -3302,6 +3316,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3325,6 +3340,7 @@ _outTableSampleClause(StringInfo str, const TableSampleClause *node)
 	WRITE_NODE_FIELD(args);
 	WRITE_NODE_FIELD(repeatable);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outA_Expr(StringInfo str, const A_Expr *node)
@@ -3442,6 +3458,7 @@ _outValue(StringInfo str, const Value *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3473,6 +3490,7 @@ _outRawStmt(StringInfo str, const RawStmt *node)
 	WRITE_LOCATION_FIELD(stmt_location);
 	WRITE_INT_FIELD(stmt_len);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outA_Const(StringInfo str, const A_Const *node)
@@ -3484,6 +3502,7 @@ _outA_Const(StringInfo str, const A_Const *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outA_Star(StringInfo str, const A_Star *node)
 {
@@ -3628,6 +3647,7 @@ _outRangeTableFuncCol(StringInfo str, const RangeTableFuncCol *node)
 	WRITE_NODE_FIELD(coldefexpr);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outConstraint(StringInfo str, const Constraint *node)
@@ -3748,6 +3768,7 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -3808,6 +3829,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -3836,6 +3858,8 @@ outNode(StringInfo str, const void *obj)
 		appendStringInfoChar(str, '{');
 		switch (nodeTag(obj))
 		{
+#include "outfuncs.inc2.c"
+#ifdef OBSOLETE
 			case T_PlannedStmt:
 				_outPlannedStmt(str, obj);
 				break;
@@ -4505,6 +4529,7 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionRangeDatum:
 				_outPartitionRangeDatum(str, obj);
 				break;
+#endif /*OBSOLETE*/
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index fef4de94e6..b4b9379d9d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -240,6 +240,8 @@ readBitmapset(void)
 	return _readBitmapset();
 }
 
+#include "readfuncs.inc1.c"
+
 /*
  * _readQuery
  */
@@ -291,6 +293,7 @@ _readQuery(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readNotifyStmt
  */
@@ -589,6 +592,7 @@ _readVar(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readConst
@@ -615,6 +619,7 @@ _readConst(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readParam
  */
@@ -839,6 +844,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readBoolExpr
@@ -866,6 +872,7 @@ _readBoolExpr(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readSubLink
  */
@@ -1420,6 +1427,7 @@ _readAppendRelInfo(void)
 /*
  *	Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1515,6 +1523,7 @@ _readRangeTblEntry(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2635,6 +2644,7 @@ _readAlternativeSubPlan(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2666,6 +2676,7 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2700,6 +2711,7 @@ _readPartitionRangeDatum(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2724,7 +2736,11 @@ parseNodeString(void)
 #define MATCH(tokname, namelen) \
 	(length == namelen && memcmp(token, tokname, namelen) == 0)
 
-	if (MATCH("QUERY", 5))
+	if (false)
+		;
+#include "readfuncs.inc2.c"
+#ifdef OBSOLETE
+	else if (MATCH("QUERY", 5))
 		return_value = _readQuery();
 	else if (MATCH("WITHCHECKOPTION", 15))
 		return_value = _readWithCheckOption();
@@ -2972,6 +2988,7 @@ parseNodeString(void)
 		return_value = _readPartitionBoundSpec();
 	else if (MATCH("PARTITIONRANGEDATUM", 19))
 		return_value = _readPartitionRangeDatum();
+#endif /*OBSOLETE*/
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/include/nodes/.gitignore b/src/include/nodes/.gitignore
new file mode 100644
index 0000000000..99fb1d3787
--- /dev/null
+++ b/src/include/nodes/.gitignore
@@ -0,0 +1,2 @@
+/nodetags.h
+/header-stamp
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5e049a67e1..cb140a3b93 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -27,6 +27,8 @@ typedef enum NodeTag
 {
 	T_Invalid = 0,
 
+#include "nodes/nodetags.h"
+#ifdef OBSOLETE
 	/*
 	 * TAGS FOR EXECUTOR NODES (execnodes.h)
 	 */
@@ -527,8 +529,14 @@ typedef enum NodeTag
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * used in node definitions to set extra information for gen_node_stuff.pl
+ */
+#define pg_node_attr(x)
+
 /*
  * The first field of a node of any type is guaranteed to be the NodeTag.
  * Hence the type of any node can be gotten by casting it to Node. Declaring
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cd9115dbb5..b35d1414de 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -121,7 +121,7 @@ typedef struct Query
 
 	QuerySource querySource;	/* where did I come from? */
 
-	uint64		queryId;		/* query identifier (can be set by plugins) */
+	uint64		queryId pg_node_attr(equal_ignore);		/* query identifier (can be set by plugins) */
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 03c80c1620..b98d1958fc 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -226,7 +226,7 @@ struct PlannerInfo
 	 * GEQO.
 	 */
 	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
-	struct HTAB *join_rel_hash; /* optional hashtable for join relations */
+	struct HTAB *join_rel_hash pg_node_attr(readwrite_ignore); /* optional hashtable for join relations */
 
 	/*
 	 * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -331,7 +331,7 @@ struct PlannerInfo
 	AttrNumber *grouping_map;	/* for GroupingFunc fixup */
 	List	   *minmax_aggs;	/* List of MinMaxAggInfos */
 
-	MemoryContext planner_cxt;	/* context holding PlannerInfo */
+	MemoryContext planner_cxt pg_node_attr(readwrite_ignore);	/* context holding PlannerInfo */
 
 	double		total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -706,8 +706,8 @@ typedef struct RelOptInfo
 	RTEKind		rtekind;		/* RELATION, SUBQUERY, FUNCTION, etc */
 	AttrNumber	min_attr;		/* smallest attrno of rel (often <0) */
 	AttrNumber	max_attr;		/* largest attrno of rel */
-	Relids	   *attr_needed;	/* array indexed [min_attr .. max_attr] */
-	int32	   *attr_widths;	/* array indexed [min_attr .. max_attr] */
+	Relids	   *attr_needed pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
+	int32	   *attr_widths pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
 	List	   *lateral_vars;	/* LATERAL Vars and PHVs referenced by rel */
 	Relids		lateral_referencers;	/* rels that reference me laterally */
 	List	   *indexlist;		/* list of IndexOptInfo */
@@ -728,13 +728,13 @@ typedef struct RelOptInfo
 	Oid			userid;			/* identifies user to check access as */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
-	struct FdwRoutine *fdwroutine;
+	struct FdwRoutine *fdwroutine pg_node_attr(readwrite_ignore);
 	void	   *fdw_private;
 
 	/* cache space for remembering if we have proven this relation unique */
-	List	   *unique_for_rels;	/* known unique for these other relid
+	List	   *unique_for_rels pg_node_attr(readwrite_ignore);	/* known unique for these other relid
 									 * set(s) */
-	List	   *non_unique_for_rels;	/* known not unique for these set(s) */
+	List	   *non_unique_for_rels pg_node_attr(readwrite_ignore);	/* known not unique for these set(s) */
 
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;	/* RestrictInfo structures (if base rel) */
@@ -829,7 +829,7 @@ struct IndexOptInfo
 
 	Oid			indexoid;		/* OID of the index relation */
 	Oid			reltablespace;	/* tablespace of index (not table) */
-	RelOptInfo *rel;			/* back-link to index's table */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to index's table */
 
 	/* index-size statistics (from pg_class and elsewhere) */
 	BlockNumber pages;			/* number of disk pages in index */
@@ -839,20 +839,20 @@ struct IndexOptInfo
 	/* index descriptor information */
 	int			ncolumns;		/* number of columns in index */
 	int			nkeycolumns;	/* number of key columns in index */
-	int		   *indexkeys;		/* column numbers of index's attributes both
+	int		   *indexkeys pg_node_attr(readwrite_ignore);		/* column numbers of index's attributes both
 								 * key and included columns, or 0 */
-	Oid		   *indexcollations;	/* OIDs of collations of index columns */
-	Oid		   *opfamily;		/* OIDs of operator families for columns */
-	Oid		   *opcintype;		/* OIDs of opclass declared input data types */
-	Oid		   *sortopfamily;	/* OIDs of btree opfamilies, if orderable */
-	bool	   *reverse_sort;	/* is sort order descending? */
-	bool	   *nulls_first;	/* do NULLs come first in the sort order? */
-	bytea	  **opclassoptions; /* opclass-specific options for columns */
-	bool	   *canreturn;		/* which index cols can be returned in an
+	Oid		   *indexcollations pg_node_attr(readwrite_ignore);	/* OIDs of collations of index columns */
+	Oid		   *opfamily pg_node_attr(readwrite_ignore);		/* OIDs of operator families for columns */
+	Oid		   *opcintype pg_node_attr(readwrite_ignore);		/* OIDs of opclass declared input data types */
+	Oid		   *sortopfamily pg_node_attr(readwrite_ignore);	/* OIDs of btree opfamilies, if orderable */
+	bool	   *reverse_sort pg_node_attr(readwrite_ignore);	/* is sort order descending? */
+	bool	   *nulls_first pg_node_attr(readwrite_ignore);	/* do NULLs come first in the sort order? */
+	bytea	  **opclassoptions pg_node_attr(readwrite_ignore); /* opclass-specific options for columns */
+	bool	   *canreturn pg_node_attr(readwrite_ignore);		/* which index cols can be returned in an
 								 * index-only scan? */
 	Oid			relam;			/* OID of the access method (in pg_am) */
 
-	List	   *indexprs;		/* expressions for non-simple index columns */
+	List	   *indexprs pg_node_attr(readwrite_ignore);		/* expressions for non-simple index columns */
 	List	   *indpred;		/* predicate if a partial index, else NIL */
 
 	List	   *indextlist;		/* targetlist representing index columns */
@@ -869,14 +869,14 @@ struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
-	bool		amoptionalkey;	/* can query omit key for the first column? */
-	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
-	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
-	bool		amhasgettuple;	/* does AM have amgettuple interface? */
-	bool		amhasgetbitmap; /* does AM have amgetbitmap interface? */
-	bool		amcanparallel;	/* does AM support parallel scan? */
-	bool		amcanmarkpos;	/* does AM support mark/restore? */
+	bool		amcanorderbyop pg_node_attr(readwrite_ignore); /* does AM support order by operator result? */
+	bool		amoptionalkey pg_node_attr(readwrite_ignore);	/* can query omit key for the first column? */
+	bool		amsearcharray pg_node_attr(readwrite_ignore);	/* can AM handle ScalarArrayOpExpr quals? */
+	bool		amsearchnulls pg_node_attr(readwrite_ignore);	/* can AM search for NULL/NOT NULL entries? */
+	bool		amhasgettuple pg_node_attr(readwrite_ignore);	/* does AM have amgettuple interface? */
+	bool		amhasgetbitmap pg_node_attr(readwrite_ignore); /* does AM have amgetbitmap interface? */
+	bool		amcanparallel pg_node_attr(readwrite_ignore);	/* does AM support parallel scan? */
+	bool		amcanmarkpos pg_node_attr(readwrite_ignore);	/* does AM support mark/restore? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
 };
@@ -926,7 +926,7 @@ typedef struct StatisticExtInfo
 	NodeTag		type;
 
 	Oid			statOid;		/* OID of the statistics row */
-	RelOptInfo *rel;			/* back-link to statistic's table */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to statistic's table */
 	char		kind;			/* statistics kind of this entry */
 	Bitmapset  *keys;			/* attnums of the columns covered */
 	List	   *exprs;			/* expressions */
@@ -1171,10 +1171,10 @@ typedef struct Path
 
 	NodeTag		pathtype;		/* tag identifying scan/join method */
 
-	RelOptInfo *parent;			/* the relation this path can build */
-	PathTarget *pathtarget;		/* list of Vars/Exprs, cost, width */
+	RelOptInfo *parent pg_node_attr(path_hack1);			/* the relation this path can build */
+	PathTarget *pathtarget pg_node_attr(path_hack2);		/* list of Vars/Exprs, cost, width */
 
-	ParamPathInfo *param_info;	/* parameterization info, or NULL if none */
+	ParamPathInfo *param_info pg_node_attr(path_hack3);	/* parameterization info, or NULL if none */
 
 	bool		parallel_aware; /* engage parallel-aware logic? */
 	bool		parallel_safe;	/* OK to use as part of parallel plan? */
@@ -2051,19 +2051,19 @@ typedef struct RestrictInfo
 
 	bool		outerjoin_delayed;	/* true if delayed by lower outer join */
 
-	bool		can_join;		/* see comment above */
+	bool		can_join pg_node_attr(equal_ignore);		/* see comment above */
 
-	bool		pseudoconstant; /* see comment above */
+	bool		pseudoconstant pg_node_attr(equal_ignore); /* see comment above */
 
-	bool		leakproof;		/* true if known to contain no leaked Vars */
+	bool		leakproof pg_node_attr(equal_ignore);		/* true if known to contain no leaked Vars */
 
-	VolatileFunctionStatus has_volatile;	/* to indicate if clause contains
+	VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);	/* to indicate if clause contains
 											 * any volatile functions. */
 
 	Index		security_level; /* see comment above */
 
 	/* The set of relids (varnos) actually referenced in the clause: */
-	Relids		clause_relids;
+	Relids		clause_relids pg_node_attr(equal_ignore);
 
 	/* The set of relids required to evaluate the clause: */
 	Relids		required_relids;
@@ -2075,47 +2075,47 @@ typedef struct RestrictInfo
 	Relids		nullable_relids;
 
 	/* These fields are set for any binary opclause: */
-	Relids		left_relids;	/* relids in left side of clause */
-	Relids		right_relids;	/* relids in right side of clause */
+	Relids		left_relids pg_node_attr(equal_ignore);	/* relids in left side of clause */
+	Relids		right_relids pg_node_attr(equal_ignore);	/* relids in right side of clause */
 
 	/* This field is NULL unless clause is an OR clause: */
-	Expr	   *orclause;		/* modified clause with RestrictInfos */
+	Expr	   *orclause pg_node_attr(equal_ignore);		/* modified clause with RestrictInfos */
 
 	/* This field is NULL unless clause is potentially redundant: */
-	EquivalenceClass *parent_ec;	/* generating EquivalenceClass */
+	EquivalenceClass *parent_ec pg_node_attr(equal_ignore readwrite_ignore);	/* generating EquivalenceClass */
 
 	/* cache space for cost and selectivity */
-	QualCost	eval_cost;		/* eval cost of clause; -1 if not yet set */
-	Selectivity norm_selec;		/* selectivity for "normal" (JOIN_INNER)
+	QualCost	eval_cost pg_node_attr(equal_ignore);		/* eval cost of clause; -1 if not yet set */
+	Selectivity norm_selec pg_node_attr(equal_ignore);		/* selectivity for "normal" (JOIN_INNER)
 								 * semantics; -1 if not yet set; >1 means a
 								 * redundant clause */
-	Selectivity outer_selec;	/* selectivity for outer join semantics; -1 if
+	Selectivity outer_selec pg_node_attr(equal_ignore);	/* selectivity for outer join semantics; -1 if
 								 * not yet set */
 
 	/* valid if clause is mergejoinable, else NIL */
-	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *mergeopfamilies pg_node_attr(equal_ignore);	/* opfamilies containing clause operator */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
-	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
-	EquivalenceClass *right_ec; /* EquivalenceClass containing righthand */
-	EquivalenceMember *left_em; /* EquivalenceMember for lefthand */
-	EquivalenceMember *right_em;	/* EquivalenceMember for righthand */
-	List	   *scansel_cache;	/* list of MergeScanSelCache structs */
+	EquivalenceClass *left_ec pg_node_attr(equal_ignore readwrite_ignore);	/* EquivalenceClass containing lefthand */
+	EquivalenceClass *right_ec pg_node_attr(equal_ignore readwrite_ignore); /* EquivalenceClass containing righthand */
+	EquivalenceMember *left_em pg_node_attr(equal_ignore); /* EquivalenceMember for lefthand */
+	EquivalenceMember *right_em pg_node_attr(equal_ignore);	/* EquivalenceMember for righthand */
+	List	   *scansel_cache pg_node_attr(copy_ignore equal_ignore);	/* list of MergeScanSelCache structs */
 
 	/* transient workspace for use while considering a specific join path */
-	bool		outer_is_left;	/* T = outer var on left, F = on right */
+	bool		outer_is_left pg_node_attr(equal_ignore);	/* T = outer var on left, F = on right */
 
 	/* valid if clause is hashjoinable, else InvalidOid: */
-	Oid			hashjoinoperator;	/* copy of clause operator */
+	Oid			hashjoinoperator pg_node_attr(equal_ignore);	/* copy of clause operator */
 
 	/* cache space for hashclause processing; -1 if not yet set */
-	Selectivity left_bucketsize;	/* avg bucketsize of left side */
-	Selectivity right_bucketsize;	/* avg bucketsize of right side */
-	Selectivity left_mcvfreq;	/* left side's most common val's freq */
-	Selectivity right_mcvfreq;	/* right side's most common val's freq */
+	Selectivity left_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of left side */
+	Selectivity right_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of right side */
+	Selectivity left_mcvfreq pg_node_attr(equal_ignore);	/* left side's most common val's freq */
+	Selectivity right_mcvfreq pg_node_attr(equal_ignore);	/* right side's most common val's freq */
 
 	/* hash equality operator used for result cache, else InvalidOid */
-	Oid			hasheqoperator;
+	Oid			hasheqoperator pg_node_attr(equal_ignore);
 } RestrictInfo;
 
 /*
@@ -2170,8 +2170,8 @@ typedef struct MergeScanSelCache
 typedef struct PlaceHolderVar
 {
 	Expr		xpr;
-	Expr	   *phexpr;			/* the represented expression */
-	Relids		phrels;			/* base relids syntactically within expr src */
+	Expr	   *phexpr pg_node_attr(equal_ignore);			/* the represented expression */
+	Relids		phrels pg_node_attr(equal_ignore);			/* base relids syntactically within expr src */
 	Index		phid;			/* ID for PHV (unique within planner run) */
 	Index		phlevelsup;		/* > 0 if PHV belongs to outer query */
 } PlaceHolderVar;
@@ -2420,7 +2420,7 @@ typedef struct MinMaxAggInfo
 	Oid			aggfnoid;		/* pg_proc Oid of the aggregate */
 	Oid			aggsortop;		/* Oid of its sort operator */
 	Expr	   *target;			/* expression we are aggregating on */
-	PlannerInfo *subroot;		/* modified "root" for planning the subquery */
+	PlannerInfo *subroot pg_node_attr(readwrite_ignore);		/* modified "root" for planning the subquery */
 	Path	   *path;			/* access path for subquery */
 	Cost		pathcost;		/* estimated cost to fetch first row */
 	Param	   *param;			/* param for subplan's output */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3418e23873..62f6413684 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -63,7 +63,7 @@ typedef enum OnCommitAction
 typedef struct RangeVar
 {
 	NodeTag		type;
-	char	   *catalogname;	/* the catalog (database) name, or NULL */
+	char	   *catalogname pg_node_attr(readwrite_ignore);	/* the catalog (database) name, or NULL */
 	char	   *schemaname;		/* the schema name, or NULL */
 	char	   *relname;		/* the relation/sequence name */
 	bool		inh;			/* expand rel by inheritance? recursively act
@@ -196,8 +196,8 @@ typedef struct Var
 	Index		varlevelsup;	/* for subquery variables referencing outer
 								 * relations; 0 in a normal var, >0 means N
 								 * levels up */
-	Index		varnosyn;		/* syntactic relation index (0 if unknown) */
-	AttrNumber	varattnosyn;	/* syntactic attribute number */
+	Index		varnosyn pg_node_attr(equal_ignore);		/* syntactic relation index (0 if unknown) */
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore);	/* syntactic attribute number */
 	int			location;		/* token location, or -1 if unknown */
 } Var;
 
@@ -324,7 +324,7 @@ typedef struct Aggref
 	Oid			aggtype;		/* type Oid of result of the aggregate */
 	Oid			aggcollid;		/* OID of collation of result */
 	Oid			inputcollid;	/* OID of collation that function should use */
-	Oid			aggtranstype;	/* type Oid of aggregate's transition value */
+	Oid			aggtranstype pg_node_attr(equal_ignore);	/* type Oid of aggregate's transition value */
 	List	   *aggargtypes;	/* type Oids of direct and aggregated args */
 	List	   *aggdirectargs;	/* direct arguments, if an ordered-set agg */
 	List	   *args;			/* aggregated arguments and sort expressions */
@@ -371,8 +371,8 @@ typedef struct GroupingFunc
 	Expr		xpr;
 	List	   *args;			/* arguments, not evaluated but kept for
 								 * benefit of EXPLAIN etc. */
-	List	   *refs;			/* ressortgrouprefs of arguments */
-	List	   *cols;			/* actual column positions set by planner */
+	List	   *refs pg_node_attr(equal_ignore);			/* ressortgrouprefs of arguments */
+	List	   *cols pg_node_attr(equal_ignore);			/* actual column positions set by planner */
 	Index		agglevelsup;	/* same as Aggref.agglevelsup */
 	int			location;		/* token location */
 } GroupingFunc;
@@ -540,7 +540,7 @@ typedef struct OpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of underlying function */
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of underlying function */
 	Oid			opresulttype;	/* PG_TYPE OID of result value */
 	bool		opretset;		/* true if operator returns set */
 	Oid			opcollid;		/* OID of collation of result */
@@ -589,8 +589,8 @@ typedef struct ScalarArrayOpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of comparison function */
-	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of comparison function */
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of hash func or InvalidOid */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
 	List	   *args;			/* the scalar and array operands */
-- 
2.31.1

v1-0010-XXX-Debugging-support.patchtext/plain; charset=UTF-8; name=v1-0010-XXX-Debugging-support.patch; x-mac-creator=0; x-mac-type=0Download
From b9cd1fd53089c833efd39c76c0e4813a974f9a1e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 16:09:26 +0200
Subject: [PATCH v1 10/10] XXX Debugging support

---
 src/include/pg_config_manual.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 27da86e5e0..1c1152fe14 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -365,14 +365,14 @@
  * copyObject(), to facilitate catching errors and omissions in
  * copyObject().
  */
-/* #define COPY_PARSE_PLAN_TREES */
+#define COPY_PARSE_PLAN_TREES
 
 /*
  * Define this to force all parse and plan trees to be passed through
  * outfuncs.c/readfuncs.c, to facilitate catching errors and omissions in
  * those modules.
  */
-/* #define WRITE_READ_PARSE_PLAN_TREES */
+#define WRITE_READ_PARSE_PLAN_TREES
 
 /*
  * Define this to force all raw parse trees for DML statements to be scanned
-- 
2.31.1

#2David Rowley
dgrowleyml@gmail.com
In reply to: Peter Eisentraut (#1)
Re: automatically generating node support functions

On Tue, 8 Jun 2021 at 08:28, Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:

I wrote 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.

Thanks for working on this. I agree that it would be nice to see
improvements in this area.

It's almost 2 years ago now, but I'm wondering if you saw what Andres
proposed in [1]/messages/by-id/20190828234136.fk2ndqtld3onfrrp@alap3.anarazel.de? The idea was basically to make a metadata array of
the node structs so that, instead of having to output large amounts of
.c code to do read/write/copy/equals, instead just have small
functions that loop over the elements in the array for the given
struct and perform the required operation based on the type.

There were still quite a lot of unsolved problems, for example, how to
determine the length of arrays so that we know how many bytes to
compare in equal funcs. I had a quick look at what you've got and
see you've got a solution for that by looking at the last "int" field
before the array and using that. (I wonder if you'd be better to use
something more along the lines of your pg_node_attr() for that?)

There's quite a few advantages having the metadata array rather than
the current approach:

1. We don't need to compile 4 huge .c files and link them into the
postgres binary. I imagine this will make the binary a decent amount
smaller.
2. We can easily add more operations on nodes. e.g serialize nodes
for sending plans to parallel workers. or generating a hash value so
we can store node types in a hash table.

One disadvantage would be what Andres mentioned in [2]/messages/by-id/20190920051857.2fhnvhvx4qdddviz@alap3.anarazel.de. He found
around a 5% performance regression. However, looking at the
NodeTypeComponents struct in [1]/messages/by-id/20190828234136.fk2ndqtld3onfrrp@alap3.anarazel.de, we might be able to speed it up
further by shrinking that struct down a bit and just storing an uint16
position into a giant char array which contains all of the field
names. I imagine they wouldn't take more than 64k. fieldtype could see
a similar change. That would take the NodeTypeComponents struct from
26 bytes down to 14 bytes, which means about double the number of
field metadata we could fit on a cache line.

Do you have any thoughts about that approach instead?

David

[1]: /messages/by-id/20190828234136.fk2ndqtld3onfrrp@alap3.anarazel.de
[2]: /messages/by-id/20190920051857.2fhnvhvx4qdddviz@alap3.anarazel.de

#3Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: David Rowley (#2)
Re: automatically generating node support functions

On 08.06.21 15:40, David Rowley wrote:

It's almost 2 years ago now, but I'm wondering if you saw what Andres
proposed in [1]? The idea was basically to make a metadata array of
the node structs so that, instead of having to output large amounts of
.c code to do read/write/copy/equals, instead just have small
functions that loop over the elements in the array for the given
struct and perform the required operation based on the type.

That project was technologically impressive, but it seemed to have
significant hurdles to overcome before it can be useful. My proposal is
usable and useful today. And it doesn't prevent anyone from working on
a more sophisticated solution.

There were still quite a lot of unsolved problems, for example, how to
determine the length of arrays so that we know how many bytes to
compare in equal funcs. I had a quick look at what you've got and
see you've got a solution for that by looking at the last "int" field
before the array and using that. (I wonder if you'd be better to use
something more along the lines of your pg_node_attr() for that?)

I considered that, but since the convention seemed to work everywhere, I
left it. But it wouldn't be hard to change.

#4Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#3)
Re: automatically generating node support functions

Hi,

On 2021-06-08 19:45:58 +0200, Peter Eisentraut wrote:

On 08.06.21 15:40, David Rowley wrote:

It's almost 2 years ago now, but I'm wondering if you saw what Andres
proposed in [1]? The idea was basically to make a metadata array of
the node structs so that, instead of having to output large amounts of
.c code to do read/write/copy/equals, instead just have small
functions that loop over the elements in the array for the given
struct and perform the required operation based on the type.

That project was technologically impressive, but it seemed to have
significant hurdles to overcome before it can be useful. My proposal is
usable and useful today. And it doesn't prevent anyone from working on a
more sophisticated solution.

I think it's short-sighted to further and further go down the path of
parsing "kind of C" without just using a proper C parser. But leaving
that aside, a big part of the promise of the approach in that thread
isn't actually tied to the specific way the type information is
collected: The perl script could output something like the "node type
metadata" I generated in that patchset, and then we don't need the large
amount of generated code and can much more economically add additional
operations handling node types.

Greetings,

Andres Freund

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#4)
Re: automatically generating node support functions

Andres Freund <andres@anarazel.de> writes:

On 2021-06-08 19:45:58 +0200, Peter Eisentraut wrote:

On 08.06.21 15:40, David Rowley wrote:

It's almost 2 years ago now, but I'm wondering if you saw what Andres
proposed in [1]?

That project was technologically impressive, but it seemed to have
significant hurdles to overcome before it can be useful. My proposal is
usable and useful today. And it doesn't prevent anyone from working on a
more sophisticated solution.

I think it's short-sighted to further and further go down the path of
parsing "kind of C" without just using a proper C parser. But leaving
that aside, a big part of the promise of the approach in that thread
isn't actually tied to the specific way the type information is
collected: The perl script could output something like the "node type
metadata" I generated in that patchset, and then we don't need the large
amount of generated code and can much more economically add additional
operations handling node types.

I think the main reason that the previous patch went nowhere was general
resistance to making developers install something as complicated as
libclang --- that could be a big lift on non-mainstream platforms.
So IMO it's a feature not a bug that Peter's approach just uses a perl
script. OTOH, the downstream aspects of your patch did seem appealing.
So I'd like to see a merger of the two approaches, using perl for the
data extraction and then something like what you'd done. Maybe that's
the same thing you're saying.

I also see Peter's point that committing what he has here might be
a reasonable first step on that road. Getting the data extraction
right is a big chunk of the job, and what we do with it afterward
could be improved later.

regards, tom lane

#6Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#5)
Re: automatically generating node support functions

Hi,

On 2021-07-14 17:42:10 -0400, Tom Lane wrote:

I think the main reason that the previous patch went nowhere was general
resistance to making developers install something as complicated as
libclang --- that could be a big lift on non-mainstream platforms.

I'm still not particularly convinced it's and issue - I was suggesting
we commit the resulting metadata, so libclang would only be needed when
modifying node types. And even in case one needs to desperately modify
node types on a system without access to libclang, for an occasionally
small change one could just modify the committed metadata structs
manually.

So IMO it's a feature not a bug that Peter's approach just uses a perl
script. OTOH, the downstream aspects of your patch did seem appealing.
So I'd like to see a merger of the two approaches, using perl for the
data extraction and then something like what you'd done. Maybe that's
the same thing you're saying.

Yes, that's what I was trying to say. I'm still doubtful it's a great
idea to go further down the "weird subset of C parsed by regexes" road,
but I can live with it. If Peter could generate something roughly like
the metadata I emitted, I'd rebase my node functions ontop of that.

I also see Peter's point that committing what he has here might be
a reasonable first step on that road. Getting the data extraction
right is a big chunk of the job, and what we do with it afterward
could be improved later.

To me that seems likely to just cause churn without saving much
effort. The needed information isn't really the same between generating
the node functions as text and collecting the metadata for "generic node
functions", and none of the output is the same.

Greetings,

Andres Freund

#7Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Peter Eisentraut (#1)
Re: automatically generating node support functions

On 07.06.21 22:27, Peter Eisentraut wrote:

I wrote 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.

The first eight patches are to clean up various inconsistencies to make
parsing or generation easier.

Are there any concerns about the patches 0001 through 0008? Otherwise,
maybe we could get those out of the way.

#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#7)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

The first eight patches are to clean up various inconsistencies to make
parsing or generation easier.

Are there any concerns about the patches 0001 through 0008? Otherwise,
maybe we could get those out of the way.

I looked through those and don't have any complaints (though I just
eyeballed them, I didn't see what a compiler would say). I see
you pushed a couple of them already.

regards, tom lane

#9Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Peter Eisentraut (#7)
5 attachment(s)
Re: automatically generating node support functions

Here is another set of preparatory patches that clean up various special
cases and similar in the node support.

0001-Remove-T_Expr.patch

Removes unneeded T_Expr.

0002-Add-COPY_ARRAY_FIELD-and-COMPARE_ARRAY_FIELD.patch
0003-Add-WRITE_INDEX_ARRAY.patch

These add macros to handle a few cases that were previously hand-coded.

0004-Make-node-output-prefix-match-node-structure-name.patch

Some nodes' output/read functions use a label that is slightly different
from their node name, e.g., "NOTIFY" instead of "NOTIFYSTMT". This
cleans that up so that an automated approach doesn't have to deal with
these special cases.

0005-Add-Cardinality-typedef.patch

Adds a typedef Cardinality for double fields that store an estimated row
or other count. Works alongside Cost and Selectivity.

This is useful because it appears that the serialization format for
these float fields depends on their intent: Cardinality => %.0f, Cost =>
%.2f, Selectivity => %.4f. The only remaining exception is allvisfrac,
which uses %.6f. Maybe that could also be a Selectivity, but I left it
as is. I think this improves the clarity in this area.

Attachments:

0001-Remove-T_Expr.patchtext/plain; charset=UTF-8; name=0001-Remove-T_Expr.patch; x-mac-creator=0; x-mac-type=0Download
From 4a9cc84d667f64bdcdffb1df8c89c4e73b02c1fb Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 17 Aug 2021 15:51:45 +0200
Subject: [PATCH 1/5] Remove T_Expr

This is an abstract node that shouldn't have a node tag defined.
---
 src/include/nodes/nodes.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6a4d82f0a8..5ac1996738 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -153,7 +153,6 @@ typedef enum NodeTag
 	T_Alias,
 	T_RangeVar,
 	T_TableFunc,
-	T_Expr,
 	T_Var,
 	T_Const,
 	T_Param,
-- 
2.32.0

0002-Add-COPY_ARRAY_FIELD-and-COMPARE_ARRAY_FIELD.patchtext/plain; charset=UTF-8; name=0002-Add-COPY_ARRAY_FIELD-and-COMPARE_ARRAY_FIELD.patch; x-mac-creator=0; x-mac-type=0Download
From a6f0e9031071ce695153fea600b5cb07a6507e11 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 17 Aug 2021 15:51:45 +0200
Subject: [PATCH 2/5] Add COPY_ARRAY_FIELD and COMPARE_ARRAY_FIELD

These handle node fields that are inline arrays (as opposed to
dynamically allocated arrays handled by COPY_POINTER_FIELD and
COMPARE_POINTER_FIELD).  These cases were hand-coded until now.
---
 src/backend/nodes/copyfuncs.c  | 11 +++++++----
 src/backend/nodes/equalfuncs.c |  7 +++++++
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..05c8ca080c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -53,6 +53,10 @@
 #define COPY_STRING_FIELD(fldname) \
 	(newnode->fldname = from->fldname ? pstrdup(from->fldname) : (char *) NULL)
 
+/* Copy a field that is an inline array */
+#define COPY_ARRAY_FIELD(fldname) \
+	memcpy(newnode->fldname, from->fldname, sizeof(newnode->fldname))
+
 /* Copy a field that is a pointer to a simple palloc'd object of size sz */
 #define COPY_POINTER_FIELD(fldname, sz) \
 	do { \
@@ -4931,10 +4935,9 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	COPY_SCALAR_FIELD(conrelid);
 	COPY_SCALAR_FIELD(confrelid);
 	COPY_SCALAR_FIELD(nkeys);
-	/* COPY_SCALAR_FIELD might work for these, but let's not assume that */
-	memcpy(newnode->conkey, from->conkey, sizeof(newnode->conkey));
-	memcpy(newnode->confkey, from->confkey, sizeof(newnode->confkey));
-	memcpy(newnode->conpfeqop, from->conpfeqop, sizeof(newnode->conpfeqop));
+	COPY_ARRAY_FIELD(conkey);
+	COPY_ARRAY_FIELD(confkey);
+	COPY_ARRAY_FIELD(conpfeqop);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..6f656fab97 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -74,6 +74,13 @@
 #define equalstr(a, b)	\
 	(((a) != NULL && (b) != NULL) ? (strcmp(a, b) == 0) : (a) == (b))
 
+/* Compare a field that is an inline array */
+#define COMPARE_ARRAY_FIELD(fldname) \
+	do { \
+		if (memcmp(a->fldname, b->fldname, sizeof(a->fldname)) != 0) \
+			return false; \
+	} while (0)
+
 /* Compare a field that is a pointer to a simple palloc'd object of size sz */
 #define COMPARE_POINTER_FIELD(fldname, sz) \
 	do { \
-- 
2.32.0

0003-Add-WRITE_INDEX_ARRAY.patchtext/plain; charset=UTF-8; name=0003-Add-WRITE_INDEX_ARRAY.patch; x-mac-creator=0; x-mac-type=0Download
From 7d1d7d6697d03b0a4ad3baaf37f315252f2b70c4 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 17 Aug 2021 15:51:45 +0200
Subject: [PATCH 3/5] Add WRITE_INDEX_ARRAY

We have a few WRITE_{name of type}_ARRAY macros, but the one case
using the Index type was hand-coded.  Wrap it into a macro as well.

This also changes the behavior slightly: Before, the field name was
skipped if the length was zero.  Now it prints the field name even in
that case.  This is more consistent with how other array fields are
handled.
---
 src/backend/nodes/outfuncs.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 87561cbb6f..50ed59bb4a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -124,6 +124,13 @@ static void outChar(StringInfo str, char c);
 			appendStringInfo(str, " %u", node->fldname[i]); \
 	} while(0)
 
+#define WRITE_INDEX_ARRAY(fldname, len) \
+	do { \
+		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
+		for (int i = 0; i < len; i++) \
+			appendStringInfo(str, " %u", node->fldname[i]); \
+	} while(0)
+
 #define WRITE_INT_ARRAY(fldname, len) \
 	do { \
 		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
@@ -2510,14 +2517,7 @@ _outPathTarget(StringInfo str, const PathTarget *node)
 	WRITE_NODE_TYPE("PATHTARGET");
 
 	WRITE_NODE_FIELD(exprs);
-	if (node->sortgrouprefs)
-	{
-		int			i;
-
-		appendStringInfoString(str, " :sortgrouprefs");
-		for (i = 0; i < list_length(node->exprs); i++)
-			appendStringInfo(str, " %u", node->sortgrouprefs[i]);
-	}
+	WRITE_INDEX_ARRAY(sortgrouprefs, list_length(node->exprs));
 	WRITE_FLOAT_FIELD(cost.startup, "%.2f");
 	WRITE_FLOAT_FIELD(cost.per_tuple, "%.2f");
 	WRITE_INT_FIELD(width);
-- 
2.32.0

0004-Make-node-output-prefix-match-node-structure-name.patchtext/plain; charset=UTF-8; name=0004-Make-node-output-prefix-match-node-structure-name.patch; x-mac-creator=0; x-mac-type=0Download
From a65730f24753c759cd14f828aed9d5a3137cd531 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 17 Aug 2021 15:51:45 +0200
Subject: [PATCH 4/5] Make node output prefix match node structure name

In most cases, the prefix string in a node output is the upper case of
the node structure name, e.g., MergeAppend -> MERGEAPPEND.  There were
a few exceptions that for either no apparent reason or perhaps minor
aesthetic reasons deviated from this.  In order to simplify this and
perhaps allow automatic generation without having to deal with
exception cases, make them all match.

Regularize node type serialization in some cases
---
 src/backend/nodes/outfuncs.c  | 22 +++++++++++-----------
 src/backend/nodes/readfuncs.c | 22 +++++++++++-----------
 2 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 50ed59bb4a..09cfce05b1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1472,7 +1472,7 @@ _outConvertRowtypeExpr(StringInfo str, const ConvertRowtypeExpr *node)
 static void
 _outCollateExpr(StringInfo str, const CollateExpr *node)
 {
-	WRITE_NODE_TYPE("COLLATE");
+	WRITE_NODE_TYPE("COLLATEEXPR");
 
 	WRITE_NODE_FIELD(arg);
 	WRITE_OID_FIELD(collOid);
@@ -1482,7 +1482,7 @@ _outCollateExpr(StringInfo str, const CollateExpr *node)
 static void
 _outCaseExpr(StringInfo str, const CaseExpr *node)
 {
-	WRITE_NODE_TYPE("CASE");
+	WRITE_NODE_TYPE("CASEEXPR");
 
 	WRITE_OID_FIELD(casetype);
 	WRITE_OID_FIELD(casecollid);
@@ -1495,7 +1495,7 @@ _outCaseExpr(StringInfo str, const CaseExpr *node)
 static void
 _outCaseWhen(StringInfo str, const CaseWhen *node)
 {
-	WRITE_NODE_TYPE("WHEN");
+	WRITE_NODE_TYPE("CASEWHEN");
 
 	WRITE_NODE_FIELD(expr);
 	WRITE_NODE_FIELD(result);
@@ -1515,7 +1515,7 @@ _outCaseTestExpr(StringInfo str, const CaseTestExpr *node)
 static void
 _outArrayExpr(StringInfo str, const ArrayExpr *node)
 {
-	WRITE_NODE_TYPE("ARRAY");
+	WRITE_NODE_TYPE("ARRAYEXPR");
 
 	WRITE_OID_FIELD(array_typeid);
 	WRITE_OID_FIELD(array_collid);
@@ -1528,7 +1528,7 @@ _outArrayExpr(StringInfo str, const ArrayExpr *node)
 static void
 _outRowExpr(StringInfo str, const RowExpr *node)
 {
-	WRITE_NODE_TYPE("ROW");
+	WRITE_NODE_TYPE("ROWEXPR");
 
 	WRITE_NODE_FIELD(args);
 	WRITE_OID_FIELD(row_typeid);
@@ -1540,7 +1540,7 @@ _outRowExpr(StringInfo str, const RowExpr *node)
 static void
 _outRowCompareExpr(StringInfo str, const RowCompareExpr *node)
 {
-	WRITE_NODE_TYPE("ROWCOMPARE");
+	WRITE_NODE_TYPE("ROWCOMPAREEXPR");
 
 	WRITE_ENUM_FIELD(rctype, RowCompareType);
 	WRITE_NODE_FIELD(opnos);
@@ -1553,7 +1553,7 @@ _outRowCompareExpr(StringInfo str, const RowCompareExpr *node)
 static void
 _outCoalesceExpr(StringInfo str, const CoalesceExpr *node)
 {
-	WRITE_NODE_TYPE("COALESCE");
+	WRITE_NODE_TYPE("COALESCEEXPR");
 
 	WRITE_OID_FIELD(coalescetype);
 	WRITE_OID_FIELD(coalescecollid);
@@ -1564,7 +1564,7 @@ _outCoalesceExpr(StringInfo str, const CoalesceExpr *node)
 static void
 _outMinMaxExpr(StringInfo str, const MinMaxExpr *node)
 {
-	WRITE_NODE_TYPE("MINMAX");
+	WRITE_NODE_TYPE("MINMAXEXPR");
 
 	WRITE_OID_FIELD(minmaxtype);
 	WRITE_OID_FIELD(minmaxcollid);
@@ -2807,7 +2807,7 @@ _outAlterStatsStmt(StringInfo str, const AlterStatsStmt *node)
 static void
 _outNotifyStmt(StringInfo str, const NotifyStmt *node)
 {
-	WRITE_NODE_TYPE("NOTIFY");
+	WRITE_NODE_TYPE("NOTIFYSTMT");
 
 	WRITE_STRING_FIELD(conditionname);
 	WRITE_STRING_FIELD(payload);
@@ -2816,7 +2816,7 @@ _outNotifyStmt(StringInfo str, const NotifyStmt *node)
 static void
 _outDeclareCursorStmt(StringInfo str, const DeclareCursorStmt *node)
 {
-	WRITE_NODE_TYPE("DECLARECURSOR");
+	WRITE_NODE_TYPE("DECLARECURSORSTMT");
 
 	WRITE_STRING_FIELD(portalname);
 	WRITE_INT_FIELD(options);
@@ -3238,7 +3238,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 static void
 _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 {
-	WRITE_NODE_TYPE("RTE");
+	WRITE_NODE_TYPE("RANGETBLENTRY");
 
 	/* put alias + eref first to make dump more legible */
 	WRITE_NODE_FIELD(alias);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 0dd1ad7dfc..01aee85bd4 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2795,23 +2795,23 @@ parseNodeString(void)
 		return_value = _readArrayCoerceExpr();
 	else if (MATCH("CONVERTROWTYPEEXPR", 18))
 		return_value = _readConvertRowtypeExpr();
-	else if (MATCH("COLLATE", 7))
+	else if (MATCH("COLLATEEXPR", 11))
 		return_value = _readCollateExpr();
-	else if (MATCH("CASE", 4))
+	else if (MATCH("CASEEXPR", 8))
 		return_value = _readCaseExpr();
-	else if (MATCH("WHEN", 4))
+	else if (MATCH("CASEWHEN", 8))
 		return_value = _readCaseWhen();
 	else if (MATCH("CASETESTEXPR", 12))
 		return_value = _readCaseTestExpr();
-	else if (MATCH("ARRAY", 5))
+	else if (MATCH("ARRAYEXPR", 9))
 		return_value = _readArrayExpr();
-	else if (MATCH("ROW", 3))
+	else if (MATCH("ROWEXPR", 7))
 		return_value = _readRowExpr();
-	else if (MATCH("ROWCOMPARE", 10))
+	else if (MATCH("ROWCOMPAREEXPR", 14))
 		return_value = _readRowCompareExpr();
-	else if (MATCH("COALESCE", 8))
+	else if (MATCH("COALESCEEXPR", 12))
 		return_value = _readCoalesceExpr();
-	else if (MATCH("MINMAX", 6))
+	else if (MATCH("MINMAXEXPR", 10))
 		return_value = _readMinMaxExpr();
 	else if (MATCH("SQLVALUEFUNCTION", 16))
 		return_value = _readSQLValueFunction();
@@ -2845,17 +2845,17 @@ parseNodeString(void)
 		return_value = _readOnConflictExpr();
 	else if (MATCH("APPENDRELINFO", 13))
 		return_value = _readAppendRelInfo();
-	else if (MATCH("RTE", 3))
+	else if (MATCH("RANGETBLENTRY", 13))
 		return_value = _readRangeTblEntry();
 	else if (MATCH("RANGETBLFUNCTION", 16))
 		return_value = _readRangeTblFunction();
 	else if (MATCH("TABLESAMPLECLAUSE", 17))
 		return_value = _readTableSampleClause();
-	else if (MATCH("NOTIFY", 6))
+	else if (MATCH("NOTIFYSTMT", 10))
 		return_value = _readNotifyStmt();
 	else if (MATCH("DEFELEM", 7))
 		return_value = _readDefElem();
-	else if (MATCH("DECLARECURSOR", 13))
+	else if (MATCH("DECLARECURSORSTMT", 17))
 		return_value = _readDeclareCursorStmt();
 	else if (MATCH("PLANNEDSTMT", 11))
 		return_value = _readPlannedStmt();
-- 
2.32.0

0005-Add-Cardinality-typedef.patchtext/plain; charset=UTF-8; name=0005-Add-Cardinality-typedef.patch; x-mac-creator=0; x-mac-type=0Download
From 1e941046d1bcb0622e3e7daad3be9234181eeed9 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 17 Aug 2021 15:51:45 +0200
Subject: [PATCH 5/5] Add Cardinality typedef

Similar to Cost and Selectivity, this is just a double, which can be
used in path and plan nodes to give some hint about the meaning of a
field.
---
 src/include/nodes/nodes.h     |  1 +
 src/include/nodes/pathnodes.h | 46 +++++++++++++++++------------------
 src/include/nodes/plannodes.h |  4 +--
 3 files changed, 26 insertions(+), 25 deletions(-)

diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5ac1996738..b5157ec4c7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -669,6 +669,7 @@ extern bool equal(const void *a, const void *b);
  */
 typedef double Selectivity;		/* fraction of tuples a qualifier will pass */
 typedef double Cost;			/* execution cost (in page-access units) */
+typedef double Cardinality;		/* (estimated) number of rows or other integer count */
 
 
 /*
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6e068f2c8b..d980c7ba04 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -333,11 +333,11 @@ struct PlannerInfo
 
 	MemoryContext planner_cxt;	/* context holding PlannerInfo */
 
-	double		total_table_pages;	/* # of pages in all non-dummy tables of
+	Cardinality	total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
 
-	double		tuple_fraction; /* tuple_fraction passed to query_planner */
-	double		limit_tuples;	/* limit_tuples passed to query_planner */
+	Selectivity	tuple_fraction; /* tuple_fraction passed to query_planner */
+	Cardinality	limit_tuples;	/* limit_tuples passed to query_planner */
 
 	Index		qual_security_level;	/* minimum security_level for quals */
 	/* Note: qual_security_level is zero if there are no securityQuals */
@@ -676,7 +676,7 @@ typedef struct RelOptInfo
 	Relids		relids;			/* set of base relids (rangetable indexes) */
 
 	/* size estimates generated by planner */
-	double		rows;			/* estimated number of result tuples */
+	Cardinality	rows;			/* estimated number of result tuples */
 
 	/* per-relation planner control flags */
 	bool		consider_startup;	/* keep cheap-startup-cost paths? */
@@ -713,7 +713,7 @@ typedef struct RelOptInfo
 	List	   *indexlist;		/* list of IndexOptInfo */
 	List	   *statlist;		/* list of StatisticExtInfo */
 	BlockNumber pages;			/* size estimates derived from pg_class */
-	double		tuples;
+	Cardinality	tuples;
 	double		allvisfrac;
 	Bitmapset  *eclass_indexes; /* Indexes in PlannerInfo's eq_classes list of
 								 * ECs that mention this rel */
@@ -836,7 +836,7 @@ struct IndexOptInfo
 
 	/* index-size statistics (from pg_class and elsewhere) */
 	BlockNumber pages;			/* number of disk pages in index */
-	double		tuples;			/* number of index tuples in index */
+	Cardinality	tuples;			/* number of index tuples in index */
 	int			tree_height;	/* index tree height, or -1 if unknown */
 
 	/* index descriptor information */
@@ -1134,7 +1134,7 @@ typedef struct ParamPathInfo
 	NodeTag		type;
 
 	Relids		ppi_req_outer;	/* rels supplying parameters used by path */
-	double		ppi_rows;		/* estimated number of result tuples */
+	Cardinality	ppi_rows;		/* estimated number of result tuples */
 	List	   *ppi_clauses;	/* join clauses available from outer rels */
 } ParamPathInfo;
 
@@ -1184,7 +1184,7 @@ typedef struct Path
 	int			parallel_workers;	/* desired # of workers; 0 = not parallel */
 
 	/* estimated size/costs for path (see costsize.c for more info) */
-	double		rows;			/* estimated number of result tuples */
+	Cardinality	rows;			/* estimated number of result tuples */
 	Cost		startup_cost;	/* cost expended before fetching any tuples */
 	Cost		total_cost;		/* total cost (assuming all tuples fetched) */
 
@@ -1447,7 +1447,7 @@ typedef struct AppendPath
 	List	   *subpaths;		/* list of component Paths */
 	/* Index of first partial path in subpaths; list_length(subpaths) if none */
 	int			first_partial_path;
-	double		limit_tuples;	/* hard limit on output tuples, or -1 */
+	Cardinality	limit_tuples;	/* hard limit on output tuples, or -1 */
 } AppendPath;
 
 #define IS_DUMMY_APPEND(p) \
@@ -1469,7 +1469,7 @@ typedef struct MergeAppendPath
 {
 	Path		path;
 	List	   *subpaths;		/* list of component Paths */
-	double		limit_tuples;	/* hard limit on output tuples, or -1 */
+	Cardinality	limit_tuples;	/* hard limit on output tuples, or -1 */
 } MergeAppendPath;
 
 /*
@@ -1510,7 +1510,7 @@ typedef struct MemoizePath
 	List	   *param_exprs;	/* cache keys */
 	bool		singlerow;		/* true if the cache entry is to be marked as
 								 * complete after caching the first record. */
-	double		calls;			/* expected number of rescans */
+	Cardinality	calls;			/* expected number of rescans */
 	uint32		est_entries;	/* The maximum number of entries that the
 								 * planner expects will fit in the cache, or 0
 								 * if unknown */
@@ -1662,7 +1662,7 @@ typedef struct HashPath
 	JoinPath	jpath;
 	List	   *path_hashclauses;	/* join clauses used for hashing */
 	int			num_batches;	/* number of batches expected */
-	double		inner_rows_total;	/* total inner rows expected */
+	Cardinality	inner_rows_total;	/* total inner rows expected */
 } HashPath;
 
 /*
@@ -1765,7 +1765,7 @@ typedef struct AggPath
 	Path	   *subpath;		/* path representing input source */
 	AggStrategy aggstrategy;	/* basic strategy, see nodes.h */
 	AggSplit	aggsplit;		/* agg-splitting mode, see nodes.h */
-	double		numGroups;		/* estimated number of groups in input */
+	Cardinality	numGroups;		/* estimated number of groups in input */
 	uint64		transitionSpace;	/* for pass-by-ref transition data */
 	List	   *groupClause;	/* a list of SortGroupClause's */
 	List	   *qual;			/* quals (HAVING quals), if any */
@@ -1779,7 +1779,7 @@ typedef struct GroupingSetData
 {
 	NodeTag		type;
 	List	   *set;			/* grouping set as list of sortgrouprefs */
-	double		numGroups;		/* est. number of result groups */
+	Cardinality	numGroups;		/* est. number of result groups */
 } GroupingSetData;
 
 typedef struct RollupData
@@ -1788,7 +1788,7 @@ typedef struct RollupData
 	List	   *groupClause;	/* applicable subset of parse->groupClause */
 	List	   *gsets;			/* lists of integer indexes into groupClause */
 	List	   *gsets_data;		/* list of GroupingSetData */
-	double		numGroups;		/* est. number of result groups */
+	Cardinality	numGroups;		/* est. number of result groups */
 	bool		hashable;		/* can be hashed */
 	bool		is_hashed;		/* to be implemented as a hashagg */
 } RollupData;
@@ -1839,7 +1839,7 @@ typedef struct SetOpPath
 	List	   *distinctList;	/* SortGroupClauses identifying target cols */
 	AttrNumber	flagColIdx;		/* where is the flag column, if any */
 	int			firstFlag;		/* flag value for first input relation */
-	double		numGroups;		/* estimated number of groups in input */
+	Cardinality	numGroups;		/* estimated number of groups in input */
 } SetOpPath;
 
 /*
@@ -1852,7 +1852,7 @@ typedef struct RecursiveUnionPath
 	Path	   *rightpath;
 	List	   *distinctList;	/* SortGroupClauses identifying target cols */
 	int			wtParam;		/* ID of Param representing work table */
-	double		numGroups;		/* estimated number of groups in input */
+	Cardinality	numGroups;		/* estimated number of groups in input */
 } RecursiveUnionPath;
 
 /*
@@ -2607,7 +2607,7 @@ typedef struct
 typedef struct
 {
 	bool		limit_needed;
-	double		limit_tuples;
+	Cardinality	limit_tuples;
 	int64		count_est;
 	int64		offset_est;
 } FinalPathExtraData;
@@ -2638,15 +2638,15 @@ typedef struct JoinCostWorkspace
 	Cost		inner_rescan_run_cost;
 
 	/* private for cost_mergejoin code */
-	double		outer_rows;
-	double		inner_rows;
-	double		outer_skip_rows;
-	double		inner_skip_rows;
+	Cardinality	outer_rows;
+	Cardinality	inner_rows;
+	Cardinality	outer_skip_rows;
+	Cardinality	inner_skip_rows;
 
 	/* private for cost_hashjoin code */
 	int			numbuckets;
 	int			numbatches;
-	double		inner_rows_total;
+	Cardinality	inner_rows_total;
 } JoinCostWorkspace;
 
 /*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index ec9a8b0c81..01a246d50e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -120,7 +120,7 @@ typedef struct Plan
 	/*
 	 * planner's estimate of result size of this plan step
 	 */
-	double		plan_rows;		/* number of rows plan is expected to emit */
+	Cardinality	plan_rows;		/* number of rows plan is expected to emit */
 	int			plan_width;		/* average row width in bytes */
 
 	/*
@@ -976,7 +976,7 @@ typedef struct Hash
 	AttrNumber	skewColumn;		/* outer join key's column #, or zero */
 	bool		skewInherit;	/* is outer join rel an inheritance tree? */
 	/* all other info is in the parent HashJoin node */
-	double		rows_total;		/* estimate total rows if parallel_aware */
+	Cardinality	rows_total;		/* estimate total rows if parallel_aware */
 } Hash;
 
 /* ----------------
-- 
2.32.0

#10Jacob Champion
pchampion@vmware.com
In reply to: Peter Eisentraut (#9)
Re: automatically generating node support functions

On Tue, 2021-08-17 at 16:36 +0200, Peter Eisentraut wrote:

Here is another set of preparatory patches that clean up various special
cases and similar in the node support.

0001-Remove-T_Expr.patch

Removes unneeded T_Expr.

0002-Add-COPY_ARRAY_FIELD-and-COMPARE_ARRAY_FIELD.patch
0003-Add-WRITE_INDEX_ARRAY.patch

These add macros to handle a few cases that were previously hand-coded.

These look sane to me.

0004-Make-node-output-prefix-match-node-structure-name.patch

Some nodes' output/read functions use a label that is slightly different
from their node name, e.g., "NOTIFY" instead of "NOTIFYSTMT". This
cleans that up so that an automated approach doesn't have to deal with
these special cases.

Is there any concern about the added serialization length, or is that
trivial in practice? The one that particularly caught my eye is
RANGETBLENTRY, which was previously RTE. But I'm not very well-versed
in all the places these strings can be generated and stored.

0005-Add-Cardinality-typedef.patch

Adds a typedef Cardinality for double fields that store an estimated row
or other count. Works alongside Cost and Selectivity.

Should RangeTblEntry.enrtuples also be a Cardinality?

--Jacob

#11Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Jacob Champion (#10)
Re: automatically generating node support functions

On 02.09.21 20:53, Jacob Champion wrote:

0004-Make-node-output-prefix-match-node-structure-name.patch

Some nodes' output/read functions use a label that is slightly different
from their node name, e.g., "NOTIFY" instead of "NOTIFYSTMT". This
cleans that up so that an automated approach doesn't have to deal with
these special cases.

Is there any concern about the added serialization length, or is that
trivial in practice? The one that particularly caught my eye is
RANGETBLENTRY, which was previously RTE. But I'm not very well-versed
in all the places these strings can be generated and stored.

These are just matters of taste. Let's wait a bit more to see if anyone
is concerned.

0005-Add-Cardinality-typedef.patch

Adds a typedef Cardinality for double fields that store an estimated row
or other count. Works alongside Cost and Selectivity.

Should RangeTblEntry.enrtuples also be a Cardinality?

Yes, I'll add that.

#12Noah Misch
noah@leadboat.com
In reply to: Peter Eisentraut (#11)
Re: automatically generating node support functions

On Tue, Sep 07, 2021 at 10:57:02AM +0200, Peter Eisentraut wrote:

On 02.09.21 20:53, Jacob Champion wrote:

0004-Make-node-output-prefix-match-node-structure-name.patch

Some nodes' output/read functions use a label that is slightly different
from their node name, e.g., "NOTIFY" instead of "NOTIFYSTMT". This
cleans that up so that an automated approach doesn't have to deal with
these special cases.

Is there any concern about the added serialization length, or is that
trivial in practice? The one that particularly caught my eye is
RANGETBLENTRY, which was previously RTE. But I'm not very well-versed
in all the places these strings can be generated and stored.

These are just matters of taste. Let's wait a bit more to see if anyone is
concerned.

I am not concerned about changing the serialization length this much. The
format is already quite verbose, and this change is small relative to that
existing verbosity.

#13Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Peter Eisentraut (#9)
Re: automatically generating node support functions

On 17.08.21 16:36, Peter Eisentraut wrote:

Here is another set of preparatory patches that clean up various special
cases and similar in the node support.

This set of patches has been committed. I'll close this commit fest
entry and come back with the main patch series in the future.

#14Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Peter Eisentraut (#13)
1 attachment(s)
Re: automatically generating node support functions

On 15.09.21 21:01, Peter Eisentraut wrote:

On 17.08.21 16:36, Peter Eisentraut wrote:

Here is another set of preparatory patches that clean up various
special cases and similar in the node support.

This set of patches has been committed.  I'll close this commit fest
entry and come back with the main patch series in the future.

Here is an updated version of my original patch, so we have something to
continue the discussion around. This takes into account all the
preparatory patches that have been committed in the meantime. I have
also changed it so that the array size of a pointer is now explicitly
declared using pg_node_attr(array_size(N)) instead of picking the most
recent scalar field, which was admittedly hacky. I have also added MSVC
build support and made the Perl code more portable, so that the cfbot
doesn't have to be sad.

Attachments:

v2-0001-Automatically-generate-node-support-functions.patchtext/plain; charset=UTF-8; name=v2-0001-Automatically-generate-node-support-functions.patch; x-mac-creator=0; x-mac-type=0Download
From 86783a117c1542e292d9f12052e33d75e6107d92 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 11 Oct 2021 10:55:21 +0200
Subject: [PATCH v2] 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.inc1.c and copyfuncs.inc2.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.  When we do
that, some code comments should probably be preserved elsewhere, so
that will need another pass of consideration.

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

Subtyping (TidScan -> Scan) is supported.

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

Discussion: https://www.postgresql.org/message-id/flat/c1097590-a6a4-486a-64b1-e1f9cc0533ce%40enterprisedb.com
---
 src/backend/Makefile                |   8 +-
 src/backend/nodes/.gitignore        |   3 +
 src/backend/nodes/Makefile          |  46 ++
 src/backend/nodes/copyfuncs.c       |  15 +
 src/backend/nodes/equalfuncs.c      |  21 +-
 src/backend/nodes/gen_node_stuff.pl | 642 ++++++++++++++++++++++++++++
 src/backend/nodes/outfuncs.c        |  23 +
 src/backend/nodes/readfuncs.c       |  19 +-
 src/include/nodes/.gitignore        |   2 +
 src/include/nodes/nodes.h           |   8 +
 src/include/nodes/parsenodes.h      |   2 +-
 src/include/nodes/pathnodes.h       | 132 +++---
 src/include/nodes/plannodes.h       |  90 ++--
 src/include/nodes/primnodes.h       |  20 +-
 src/include/pg_config_manual.h      |   4 +-
 src/include/utils/rel.h             |   6 +-
 src/tools/msvc/Solution.pm          |  46 ++
 17 files changed, 954 insertions(+), 133 deletions(-)
 create mode 100644 src/backend/nodes/.gitignore
 create mode 100644 src/backend/nodes/gen_node_stuff.pl
 create mode 100644 src/include/nodes/.gitignore

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 0da848b1fd..a33db1ae01 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -143,11 +143,15 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
 submake-catalog-headers:
 	$(MAKE) -C catalog distprep generated-header-symlinks
 
+# run this unconditionally to avoid needing to know its dependencies here:
+submake-nodes-headers:
+	$(MAKE) -C nodes distprep generated-header-symlinks
+
 # run this unconditionally to avoid needing to know its dependencies here:
 submake-utils-headers:
 	$(MAKE) -C utils distprep generated-header-symlinks
 
-.PHONY: submake-catalog-headers submake-utils-headers
+.PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 # Make symlinks for these headers in the include directory. That way
 # we can cut down on the -I options. Also, a symlink is automatically
@@ -162,7 +166,7 @@ submake-utils-headers:
 
 .PHONY: generated-headers
 
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..232c6e1817
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,3 @@
+/node-stuff-stamp
+/nodetags.h
+/*funcs.inc?.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..fc9226a33c 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,49 @@ OBJS = \
 	value.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+node_headers = \
+	nodes/nodes.h \
+	nodes/execnodes.h \
+	nodes/plannodes.h \
+	nodes/primnodes.h \
+	nodes/pathnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.h \
+	nodes/value.h \
+	commands/trigger.h \
+	commands/event_trigger.h \
+	foreign/fdwapi.h \
+	access/amapi.h \
+	access/tableam.h \
+	access/tsmapi.h \
+	utils/rel.h \
+	nodes/supportnodes.h \
+	executor/tuptable.h \
+	nodes/lockoptions.h \
+	access/sdir.h
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-stuff-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+node-stuff-stamp: gen_node_stuff.pl $(addprefix $(top_srcdir)/src/include/,$(node_headers))
+	$(PERL) $^
+	touch $@
+
+$(top_builddir)/src/include/nodes/header-stamp: node-stuff-stamp
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	cd '$(dir $@)' && for file in nodetags.h; do \
+	  rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \
+	done
+	touch $@
+
+maintainer-clean: clean
+	rm -f node-stuff-stamp *funcs.inc?.c nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..d54f5ef0ff 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -73,6 +73,9 @@
 	(newnode->fldname = from->fldname)
 
 
+#include "copyfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /* ****************************************************************
  *					 plannodes.h copy functions
  * ****************************************************************
@@ -1454,6 +1457,7 @@ _copyVar(const Var *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * _copyConst
@@ -1493,6 +1497,7 @@ _copyConst(const Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -2727,6 +2732,7 @@ _copyParamRef(const ParamRef *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -2764,6 +2770,7 @@ _copyA_Const(const A_Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -4885,6 +4892,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *					extensible.h copy functions
@@ -4907,6 +4915,7 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /* ****************************************************************
  *					value.h copy functions
  * ****************************************************************
@@ -4967,6 +4976,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -4987,6 +4997,8 @@ copyObjectImpl(const void *from)
 
 	switch (nodeTag(from))
 	{
+#include "copyfuncs.inc2.c"
+#ifdef OBSOLETE
 			/*
 			 * PLAN NODES
 			 */
@@ -5344,6 +5356,7 @@ copyObjectImpl(const void *from)
 		case T_BitString:
 			retval = _copyBitString(from);
 			break;
+#endif /*OBSOLETE*/
 
 			/*
 			 * LIST NODES
@@ -5361,6 +5374,7 @@ copyObjectImpl(const void *from)
 			retval = list_copy(from);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -5897,6 +5911,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 19eff20102..699f6bf3f9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -10,9 +10,6 @@
  * because the circular linkages between RelOptInfo and Path nodes can't
  * be handled easily in a simple depth-first traversal.
  *
- * Currently, in fact, equal() doesn't know how to compare Plan trees
- * either.  This might need to be fixed someday.
- *
  * NOTE: it is intentional that parse location fields (in nodes that have
  * one) are not compared.  This is because we want, for example, a variable
  * "x" to be considered equal() to another reference to "x" in the query.
@@ -33,6 +30,7 @@
 #include "nodes/extensible.h"
 #include "nodes/pathnodes.h"
 #include "utils/datum.h"
+#include "utils/rel.h"
 
 
 /*
@@ -97,6 +95,9 @@
 	((void) 0)
 
 
+#include "equalfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from primnodes.h
  */
@@ -185,6 +186,7 @@ _equalVar(const Var *a, const Var *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -207,6 +209,7 @@ _equalConst(const Const *a, const Const *b)
 						a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -946,6 +949,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -967,6 +971,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const ExtensibleNode *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2420,6 +2425,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2437,6 +2443,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3045,6 +3052,7 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3105,6 +3113,7 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from value.h
  */
@@ -3140,6 +3149,7 @@ _equalBitString(const BitString *a, const BitString *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * equal
@@ -3170,6 +3180,8 @@ equal(const void *a, const void *b)
 
 	switch (nodeTag(a))
 	{
+#include "equalfuncs.inc2.c"
+#ifdef OBSOLETE
 			/*
 			 * PRIMITIVE NODES
 			 */
@@ -3348,6 +3360,7 @@ equal(const void *a, const void *b)
 		case T_PlaceHolderInfo:
 			retval = _equalPlaceHolderInfo(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		case T_List:
 		case T_IntList:
@@ -3355,6 +3368,7 @@ equal(const void *a, const void *b)
 			retval = _equalList(a, b);
 			break;
 
+#ifdef OBSOLETE
 		case T_Integer:
 			retval = _equalInteger(a, b);
 			break;
@@ -3897,6 +3911,7 @@ equal(const void *a, const void *b)
 		case T_PublicationTable:
 			retval = _equalPublicationTable(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/gen_node_stuff.pl b/src/backend/nodes/gen_node_stuff.pl
new file mode 100644
index 0000000000..6ddac122d4
--- /dev/null
+++ b/src/backend/nodes/gen_node_stuff.pl
@@ -0,0 +1,642 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# src/backend/nodes/gen_node_stuff.pl
+#
+#----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use File::Basename;
+
+use FindBin;
+use lib "$FindBin::RealBin/../catalog";
+
+use Catalog;  # for RenameTempFile
+
+
+sub elem
+{
+	my $x = shift;
+	return grep { $_ eq $x } @_;
+}
+
+
+my @node_types = qw(Node);
+my %node_type_info;
+
+my @no_copy;
+my @no_read_write;
+
+my @scalar_types = qw(
+	bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+	AclMode AttrNumber Cardinality Cost Index Oid Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr
+);
+
+my @enum_types;
+
+# For abstract types we track their fields, so that subtypes can use
+# them, but we don't emit a node tag, so you can't instantiate them.
+my @abstract_types = qw(
+	Node Expr
+	BufferHeapTupleTableSlot HeapTupleTableSlot MinimalTupleTableSlot VirtualTupleTableSlot
+	JoinPath
+	PartitionPruneStep
+);
+
+# Special cases that either don't have their own struct or the struct
+# is not in a header file.  We just generate node tags for them, but
+# they otherwise don't participate in node support.
+my @extra_tags = qw(
+	IntList OidList
+	AllocSetContext GenerationContext SlabContext
+	TIDBitmap
+	WindowObjectData
+);
+
+# This is a regular node, but we skip parsing it from its header file
+# since we won't use its internal structure here anyway.
+push @node_types, qw(List);
+
+# pathnodes.h exceptions
+push @no_copy, qw(
+	RelOptInfo IndexOptInfo Path PlannerGlobal EquivalenceClass EquivalenceMember ForeignKeyOptInfo
+	GroupingSetData IncrementalSortPath IndexClause MinMaxAggInfo PathTarget PlannerInfo PlannerParamItem
+	ParamPathInfo RollupData RowIdentityVarInfo StatisticExtInfo
+);
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember* QualCost);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation);
+
+
+## read input
+
+foreach my $infile (@ARGV)
+{
+	my $in_struct;
+	my $subline;
+	my $is_node_struct;
+	my $supertype;
+	my $supertype_field;
+
+	my @my_fields;
+	my %my_field_types;
+	my %my_field_attrs;
+
+	open my $ifh, '<', $infile or die "could not open \"$infile\": $!";
+
+	while (my $line = <$ifh>)
+	{
+		chomp $line;
+		$line =~ s!/\*.*$!!;
+		$line =~ s/\s*$//;
+		next if $line eq '';
+		next if $line =~ m!^\s*\*.*$!;  # line starts with *, probably comment continuation
+		next if $line =~ /^#(define|ifdef|endif)/;
+
+		if ($in_struct)
+		{
+			$subline++;
+
+			# first line should have opening brace
+			if ($subline == 1)
+			{
+				$is_node_struct = 0;
+				$supertype = undef;
+				next if $line eq '{';
+				die;
+			}
+			# second line should have node tag or supertype
+			elsif ($subline == 2)
+			{
+				if ($line =~ /^\s*NodeTag\s+type;/)
+				{
+					$is_node_struct = 1;
+					next;
+				}
+				elsif ($line =~ /\s*(\w+)\s+(\w+);/ and elem $1, @node_types)
+				{
+					$is_node_struct = 1;
+					$supertype = $1;
+					$supertype_field = $2;
+					next;
+				}
+			}
+
+			# end of struct
+			if ($line =~ /^\}\s*$in_struct;$/ || $line =~ /^\};$/)
+			{
+				if ($is_node_struct)
+				{
+					push @node_types, $in_struct;
+					my @f = @my_fields;
+					my %ft = %my_field_types;
+					my %fa = %my_field_attrs;
+					if ($supertype)
+					{
+						my @superfields;
+						foreach my $sf (@{$node_type_info{$supertype}->{fields}})
+						{
+							my $fn = "${supertype_field}.$sf";
+							push @superfields, $fn;
+							$ft{$fn} = $node_type_info{$supertype}->{field_types}{$sf};
+							$fa{$fn} = $node_type_info{$supertype}->{field_attrs}{$sf};
+							$fa{$fn} =~ s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/ if $fa{$fn};
+						}
+						unshift @f, @superfields;
+					}
+					$node_type_info{$in_struct}->{fields} = \@f;
+					$node_type_info{$in_struct}->{field_types} = \%ft;
+					$node_type_info{$in_struct}->{field_attrs} = \%fa;
+
+					if (elem basename($infile),
+						qw(execnodes.h trigger.h event_trigger.h amapi.h tableam.h
+							tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h))
+					{
+						push @no_copy, $in_struct;
+						push @no_read_write, $in_struct;
+					}
+
+					if ($supertype && ($supertype eq 'Path' || $supertype eq 'JoinPath'))
+					{
+						push @no_copy, $in_struct;
+					}
+				}
+
+				# start new cycle
+				$in_struct = undef;
+				@my_fields = ();
+				%my_field_types = ();
+				%my_field_attrs = ();
+			}
+			# normal struct field
+			elsif ($line =~ /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w() ]*)\))?;/)
+			{
+				if ($is_node_struct)
+				{
+					my $type = $1;
+					my $name = $2;
+					my $array_size = $3;
+					my $attr = $4;
+
+					$type =~ s/^const\s*//;
+					$type =~ s/\s*$//;
+					$type =~ s/\s+\*$/*/;
+					die if $type eq '';
+					$type = $type . $array_size if $array_size;
+					push @my_fields, $name;
+					$my_field_types{$name} = $type;
+					$my_field_attrs{$name} = $attr;
+				}
+			}
+			else
+			{
+				if ($is_node_struct)
+				{
+					#warn "$infile:$.: could not parse \"$line\"\n";
+				}
+			}
+		}
+		# not in a struct
+		else
+		{
+			# start of a struct?
+			if ($line =~ /^(?:typedef )?struct (\w+)(\s*\/\*.*)?$/ && $1 ne 'Node')
+			{
+				$in_struct = $1;
+				$subline = 0;
+			}
+			# one node type typedef'ed directly from another
+			elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types)
+			{
+				my $alias_of = $1;
+				my $n = $2;
+
+				push @node_types, $n;
+				my @f = @{$node_type_info{$alias_of}->{fields}};
+				my %ft = %{$node_type_info{$alias_of}->{field_types}};
+				my %fa = %{$node_type_info{$alias_of}->{field_attrs}};
+				$node_type_info{$n}->{fields} = \@f;
+				$node_type_info{$n}->{field_types} = \%ft;
+				$node_type_info{$n}->{field_attrs} = \%fa;
+			}
+			# collect enum names
+			elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/)
+			{
+				push @enum_types, $1;
+			}
+		}
+	}
+
+	if ($in_struct)
+	{
+		die "runaway \"$in_struct\" in file \"$infile\"\n";
+	}
+
+	close $ifh;
+} # for each file
+
+
+## write output
+
+my $tmpext  = ".tmp$$";
+
+# nodetags.h
+
+open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
+
+my $i = 1;
+foreach my $n (@node_types,@extra_tags)
+{
+	next if elem $n, @abstract_types;
+	print $nt "\tT_${n} = $i,\n";
+	$i++;
+}
+
+close $nt;
+
+
+# copyfuncs.c, equalfuncs.c
+
+open my $cf, '>', 'copyfuncs.inc1.c' . $tmpext or die $!;
+open my $ef, '>', 'equalfuncs.inc1.c' . $tmpext or die $!;
+open my $cf2, '>', 'copyfuncs.inc2.c' . $tmpext or die $!;
+open my $ef2, '>', 'equalfuncs.inc2.c' . $tmpext or die $!;
+
+my @custom_copy = qw(A_Const Const ExtensibleNode);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_copy;
+	next if $n eq 'List';
+
+	print $cf2 "
+\t\tcase T_${n}:
+\t\t\tretval = _copy${n}(from);
+\t\t\tbreak;";
+
+	print $ef2 "
+\t\tcase T_${n}:
+\t\t\tretval = _equal${n}(a, b);
+\t\t\tbreak;";
+
+	next if elem $n, @custom_copy;
+
+	print $cf "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+";
+
+	print $ef "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+";
+
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $copy_ignore = ($a =~ /\bcopy_ignore\b/);
+		my $equal_ignore = ($a =~ /\bequal_ignore\b/);
+		if ($t eq 'char*')
+		{
+			print $cf "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_STRING_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $cf "\tCOPY_BITMAPSET_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_BITMAPSET_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $cf "\tCOPY_LOCATION_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_LOCATION_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif (elem $t, @scalar_types or elem $t, @enum_types)
+		{
+			print $cf "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			if ($a =~ /\bequal_ignore_if_zero\b/)
+			{
+				print $ef "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
+			}
+			else
+			{
+				print $ef "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+			}
+		}
+		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
+		{
+			my $tt = $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $cf "\tCOPY_POINTER_FIELD($f, list_length(from->$array_size_field) * sizeof($tt));\n" unless $copy_ignore;
+				print $ef "\tCOMPARE_POINTER_FIELD($f, list_length(a->$array_size_field) * sizeof($tt));\n" unless $equal_ignore;
+			}
+			else
+			{
+				print $cf "\tCOPY_POINTER_FIELD($f, from->$array_size_field * sizeof($tt));\n" unless $copy_ignore;
+				print $ef "\tCOMPARE_POINTER_FIELD($f, a->$array_size_field * sizeof($tt));\n" unless $equal_ignore;
+			}
+		}
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $cf "\tCOPY_NODE_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t =~ /\w+\[/)
+		{
+			print $cf "\tCOPY_ARRAY_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_ARRAY_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $cf "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $cf "
+\treturn newnode;
+}
+";
+	print $ef "
+\treturn true;
+}
+";
+}
+
+close $cf;
+close $ef;
+close $cf2;
+close $ef2;
+
+
+# outfuncs.c, readfuncs.c
+
+open my $of, '>', 'outfuncs.inc1.c' . $tmpext or die $!;
+open my $rf, '>', 'readfuncs.inc1.c' . $tmpext or die $!;
+open my $of2, '>', 'outfuncs.inc2.c' . $tmpext or die $!;
+open my $rf2, '>', 'readfuncs.inc2.c' . $tmpext or die $!;
+
+my @custom_readwrite = qw(A_Const A_Expr BoolExpr Const Constraint ExtensibleNode Query RangeTblEntry);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_read_write;
+	next if $n eq 'List';
+	next if elem $n, qw(BitString Float Integer String);
+
+	# XXX For now, skip all "Stmt"s except that ones that were there before.
+	if ($n =~ /Stmt$/)
+	{
+		my @keep = qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt ImportForeignSchemaStmt IndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt);
+		next unless elem $n, @keep;
+	}
+
+	# XXX Also skip read support for those that didn't have it before.
+	my $no_read = ($n eq 'A_Star' || $n eq 'A_Const' || $n eq 'A_Expr' || $n eq 'Constraint' || $n =~ /Path$/ || $n eq 'ForeignKeyCacheInfo' || $n eq 'ForeignKeyOptInfo' || $n eq 'PathTarget');
+
+	my $N = uc $n;
+	$N =~ s/_//g;
+
+	print $of2 "\t\t\tcase T_${n}:\n".
+	  "\t\t\t\t_out${n}(str, obj);\n".
+	  "\t\t\t\tbreak;\n";
+
+	print $rf2 "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+
+	next if elem $n, @custom_readwrite;
+
+	print $of "
+static void
+_out${n}(StringInfo str, const $n *node)
+{
+\tWRITE_NODE_TYPE(\"$N\");
+
+";
+
+	print $rf "
+static $n *
+_read${n}(void)
+{
+\tREAD_LOCALS($n);
+
+" unless $no_read;
+
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $readwrite_ignore = ($a =~ /\breadwrite_ignore\b/);
+		next if $readwrite_ignore;
+
+		# XXX Previously, for subtyping, only the leaf field name is
+		# used. Ponder whether we want to keep it that way.
+
+		if ($t eq 'bool')
+		{
+			print $of "\tWRITE_BOOL_FIELD($f);\n";
+			print $rf "\tREAD_BOOL_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $of "\tWRITE_LOCATION_FIELD($f);\n";
+			print $rf "\tREAD_LOCATION_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' || $t eq 'int32' || $t eq 'AttrNumber' || $t eq 'StrategyNumber')
+		{
+			print $of "\tWRITE_INT_FIELD($f);\n";
+			print $rf "\tREAD_INT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint32' || $t eq 'bits32' || $t eq 'AclMode' || $t eq 'BlockNumber' || $t eq 'Index' || $t eq 'SubTransactionId')
+		{
+			print $of "\tWRITE_UINT_FIELD($f);\n";
+			print $rf "\tREAD_UINT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint64')
+		{
+			print $of "\tWRITE_UINT64_FIELD($f);\n";
+			print $rf "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Oid')
+		{
+			print $of "\tWRITE_OID_FIELD($f);\n";
+			print $rf "\tREAD_OID_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'long')
+		{
+			print $of "\tWRITE_LONG_FIELD($f);\n";
+			print $rf "\tREAD_LONG_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char')
+		{
+			print $of "\tWRITE_CHAR_FIELD($f);\n";
+			print $rf "\tREAD_CHAR_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'double')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.6f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cardinality')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.0f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cost')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.2f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'QualCost')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f.startup, \"%.2f\");\n";
+			print $of "\tWRITE_FLOAT_FIELD($f.per_tuple, \"%.2f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read;
+			print $rf "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
+		}
+		elsif ($t eq 'Selectivity')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.4f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $of "\tWRITE_STRING_FIELD($f);\n";
+			print $rf "\tREAD_STRING_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $of "\tWRITE_BITMAPSET_FIELD($f);\n";
+			print $rf "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
+		}
+		elsif (elem $t, @enum_types)
+		{
+			print $of "\tWRITE_ENUM_FIELD($f, $t);\n";
+			print $rf "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read;
+		}
+		elsif ($t =~ /(\w+)(\*|\[)/ and elem $1, @scalar_types)
+		{
+			my $tt = uc $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $of "\tWRITE_${tt}_ARRAY($f, list_length(node->$array_size_field));\n";
+				print $rf "\tREAD_${tt}_ARRAY($f, list_length(local_node->$array_size_field));\n" unless $no_read;
+			}
+			else
+			{
+				print $of "\tWRITE_${tt}_ARRAY($f, node->$array_size_field);\n";
+				print $rf "\tREAD_${tt}_ARRAY($f, local_node->$array_size_field);\n" unless $no_read;
+			}
+		}
+		elsif ($t eq 'RelOptInfo*' && $a eq 'path_hack1')
+		{
+			print $of "\tappendStringInfoString(str, \" :parent_relids \");\n".
+			  "\toutBitmapset(str, node->$f->relids);\n";
+		}
+		elsif ($t eq 'PathTarget*' && $a eq 'path_hack2')
+		{
+			(my $f2 = $f) =~ s/pathtarget/parent/;
+			print $of "\tif (node->$f != node->$f2->reltarget)\n".
+			  "\t\tWRITE_NODE_FIELD($f);\n";
+		}
+		elsif ($t eq 'ParamPathInfo*' && $a eq 'path_hack3')
+		{
+			print $of "\tif (node->$f)\n".
+			  "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
+			  "\telse\n".
+			  "\t\toutBitmapset(str, NULL);\n";
+		}
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $of "\tWRITE_NODE_FIELD($f);\n";
+			print $rf "\tREAD_NODE_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $of q{
+	appendStringInfoString(str, " :methods ");
+	outToken(str, node->methods->CustomName);
+};
+			print $rf q!
+	{
+		/* Lookup CustomScanMethods by CustomName */
+		char	   *custom_name;
+		const CustomScanMethods *methods;
+		token = pg_strtok(&length); /* skip methods: */
+		token = pg_strtok(&length); /* CustomName */
+		custom_name = nullable_string(token, length);
+		methods = GetCustomScanMethods(custom_name, false);
+		local_node->methods = methods;
+	}
+! unless $no_read;
+		}
+		elsif ($t eq 'ParamListInfo' || $t =~ /PartitionBoundInfoData/ || $t eq 'PartitionDirectory' || $t eq 'PartitionScheme' || $t eq 'void*' || $t =~ /\*\*$/)
+		{
+			# ignore
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $of "}
+";
+	print $rf "
+\tREAD_DONE();
+}
+" unless $no_read;
+}
+
+close $of;
+close $rf;
+close $of2;
+close $rf2;
+
+
+foreach my $file (qw(nodetags.h copyfuncs.inc1.c copyfuncs.inc2.c equalfuncs.inc1.c equalfuncs.inc2.c outfuncs.inc1.c outfuncs.inc2.c readfuncs.inc1.c readfuncs.inc2.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2e5ed77e18..c9c13d5963 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -295,6 +295,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
 }
 
 
+#include "outfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from plannodes.h
  */
@@ -1133,6 +1136,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)
@@ -1154,6 +1158,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)
 {
@@ -1324,6 +1329,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)
@@ -1352,6 +1358,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2670,6 +2677,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
 	WRITE_NODE_FIELD(item);
 	WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2692,6 +2700,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -3024,6 +3033,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)
@@ -3096,6 +3106,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3234,6 +3245,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)
@@ -3314,6 +3326,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3337,6 +3350,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)
@@ -3449,6 +3463,7 @@ _outBitString(StringInfo str, const BitString *node)
 	appendStringInfoString(str, node->val);
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3480,6 +3495,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)
@@ -3496,6 +3512,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)
 {
@@ -3640,6 +3657,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)
@@ -3760,6 +3778,7 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -3820,6 +3839,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -3849,6 +3869,8 @@ outNode(StringInfo str, const void *obj)
 		appendStringInfoChar(str, '{');
 		switch (nodeTag(obj))
 		{
+#include "outfuncs.inc2.c"
+#ifdef OBSOLETE
 			case T_PlannedStmt:
 				_outPlannedStmt(str, obj);
 				break;
@@ -4521,6 +4543,7 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionRangeDatum:
 				_outPartitionRangeDatum(str, obj);
 				break;
+#endif /*OBSOLETE*/
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index abf08b7a2f..d75442a5f2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -240,6 +240,8 @@ readBitmapset(void)
 	return _readBitmapset();
 }
 
+#include "readfuncs.inc1.c"
+
 /*
  * _readQuery
  */
@@ -291,6 +293,7 @@ _readQuery(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readNotifyStmt
  */
@@ -589,6 +592,7 @@ _readVar(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readConst
@@ -615,6 +619,7 @@ _readConst(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readParam
  */
@@ -840,6 +845,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readBoolExpr
@@ -867,6 +873,7 @@ _readBoolExpr(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readSubLink
  */
@@ -1421,6 +1428,7 @@ _readAppendRelInfo(void)
 /*
  *	Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1516,6 +1524,7 @@ _readRangeTblEntry(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2636,6 +2645,7 @@ _readAlternativeSubPlan(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2667,6 +2677,7 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2701,6 +2712,7 @@ _readPartitionRangeDatum(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2725,7 +2737,11 @@ parseNodeString(void)
 #define MATCH(tokname, namelen) \
 	(length == namelen && memcmp(token, tokname, namelen) == 0)
 
-	if (MATCH("QUERY", 5))
+	if (false)
+		;
+#include "readfuncs.inc2.c"
+#ifdef OBSOLETE
+	else if (MATCH("QUERY", 5))
 		return_value = _readQuery();
 	else if (MATCH("WITHCHECKOPTION", 15))
 		return_value = _readWithCheckOption();
@@ -2973,6 +2989,7 @@ parseNodeString(void)
 		return_value = _readPartitionBoundSpec();
 	else if (MATCH("PARTITIONRANGEDATUM", 19))
 		return_value = _readPartitionRangeDatum();
+#endif /*OBSOLETE*/
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/include/nodes/.gitignore b/src/include/nodes/.gitignore
new file mode 100644
index 0000000000..99fb1d3787
--- /dev/null
+++ b/src/include/nodes/.gitignore
@@ -0,0 +1,2 @@
+/nodetags.h
+/header-stamp
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..b1466a793e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -27,6 +27,8 @@ typedef enum NodeTag
 {
 	T_Invalid = 0,
 
+#include "nodes/nodetags.h"
+#ifdef OBSOLETE
 	/*
 	 * TAGS FOR EXECUTOR NODES (execnodes.h)
 	 */
@@ -525,8 +527,14 @@ typedef enum NodeTag
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * used in node definitions to set extra information for gen_node_stuff.pl
+ */
+#define pg_node_attr(x)
+
 /*
  * The first field of a node of any type is guaranteed to be the NodeTag.
  * Hence the type of any node can be gotten by casting it to Node. Declaring
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..d2ea096498 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -121,7 +121,7 @@ typedef struct Query
 
 	QuerySource querySource;	/* where did I come from? */
 
-	uint64		queryId;		/* query identifier (can be set by plugins) */
+	uint64		queryId pg_node_attr(equal_ignore);		/* query identifier (can be set by plugins) */
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 2a53a6e344..ec6e7286c9 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -227,7 +227,7 @@ struct PlannerInfo
 	 * GEQO.
 	 */
 	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
-	struct HTAB *join_rel_hash; /* optional hashtable for join relations */
+	struct HTAB *join_rel_hash pg_node_attr(readwrite_ignore); /* optional hashtable for join relations */
 
 	/*
 	 * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -329,10 +329,10 @@ struct PlannerInfo
 	List	   *update_colnos;
 
 	/* Fields filled during create_plan() for use in setrefs.c */
-	AttrNumber *grouping_map;	/* for GroupingFunc fixup */
+	AttrNumber *grouping_map pg_node_attr(array_size(update_colnos));	/* for GroupingFunc fixup */
 	List	   *minmax_aggs;	/* List of MinMaxAggInfos */
 
-	MemoryContext planner_cxt;	/* context holding PlannerInfo */
+	MemoryContext planner_cxt pg_node_attr(readwrite_ignore);	/* context holding PlannerInfo */
 
 	Cardinality	total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -369,8 +369,8 @@ struct PlannerInfo
 	List	   *curOuterParams; /* not-yet-assigned NestLoopParams */
 
 	/* These fields are workspace for setrefs.c */
-	bool	   *isAltSubplan;	/* array corresponding to glob->subplans */
-	bool	   *isUsedSubplan;	/* array corresponding to glob->subplans */
+	bool	   *isAltSubplan pg_node_attr(array_size(curOuterParams));	/* array corresponding to glob->subplans */
+	bool	   *isUsedSubplan pg_node_attr(array_size(curOuterParams));	/* array corresponding to glob->subplans */
 
 	/* optional private data for join_search_hook, e.g., GEQO */
 	void	   *join_search_private;
@@ -711,8 +711,8 @@ typedef struct RelOptInfo
 	RTEKind		rtekind;		/* RELATION, SUBQUERY, FUNCTION, etc */
 	AttrNumber	min_attr;		/* smallest attrno of rel (often <0) */
 	AttrNumber	max_attr;		/* largest attrno of rel */
-	Relids	   *attr_needed;	/* array indexed [min_attr .. max_attr] */
-	int32	   *attr_widths;	/* array indexed [min_attr .. max_attr] */
+	Relids	   *attr_needed pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
+	int32	   *attr_widths pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
 	List	   *lateral_vars;	/* LATERAL Vars and PHVs referenced by rel */
 	Relids		lateral_referencers;	/* rels that reference me laterally */
 	List	   *indexlist;		/* list of IndexOptInfo */
@@ -733,13 +733,13 @@ typedef struct RelOptInfo
 	Oid			userid;			/* identifies user to check access as */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
-	struct FdwRoutine *fdwroutine;
+	struct FdwRoutine *fdwroutine pg_node_attr(readwrite_ignore);
 	void	   *fdw_private;
 
 	/* cache space for remembering if we have proven this relation unique */
-	List	   *unique_for_rels;	/* known unique for these other relid
+	List	   *unique_for_rels pg_node_attr(readwrite_ignore);	/* known unique for these other relid
 									 * set(s) */
-	List	   *non_unique_for_rels;	/* known not unique for these set(s) */
+	List	   *non_unique_for_rels pg_node_attr(readwrite_ignore);	/* known not unique for these set(s) */
 
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;	/* RestrictInfo structures (if base rel) */
@@ -837,7 +837,7 @@ struct IndexOptInfo
 
 	Oid			indexoid;		/* OID of the index relation */
 	Oid			reltablespace;	/* tablespace of index (not table) */
-	RelOptInfo *rel;			/* back-link to index's table */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to index's table */
 
 	/* index-size statistics (from pg_class and elsewhere) */
 	BlockNumber pages;			/* number of disk pages in index */
@@ -847,20 +847,20 @@ struct IndexOptInfo
 	/* index descriptor information */
 	int			ncolumns;		/* number of columns in index */
 	int			nkeycolumns;	/* number of key columns in index */
-	int		   *indexkeys;		/* column numbers of index's attributes both
+	int		   *indexkeys pg_node_attr(readwrite_ignore);		/* column numbers of index's attributes both
 								 * key and included columns, or 0 */
-	Oid		   *indexcollations;	/* OIDs of collations of index columns */
-	Oid		   *opfamily;		/* OIDs of operator families for columns */
-	Oid		   *opcintype;		/* OIDs of opclass declared input data types */
-	Oid		   *sortopfamily;	/* OIDs of btree opfamilies, if orderable */
-	bool	   *reverse_sort;	/* is sort order descending? */
-	bool	   *nulls_first;	/* do NULLs come first in the sort order? */
-	bytea	  **opclassoptions; /* opclass-specific options for columns */
-	bool	   *canreturn;		/* which index cols can be returned in an
+	Oid		   *indexcollations pg_node_attr(readwrite_ignore);	/* OIDs of collations of index columns */
+	Oid		   *opfamily pg_node_attr(readwrite_ignore);		/* OIDs of operator families for columns */
+	Oid		   *opcintype pg_node_attr(readwrite_ignore);		/* OIDs of opclass declared input data types */
+	Oid		   *sortopfamily pg_node_attr(readwrite_ignore);	/* OIDs of btree opfamilies, if orderable */
+	bool	   *reverse_sort pg_node_attr(readwrite_ignore);	/* is sort order descending? */
+	bool	   *nulls_first pg_node_attr(readwrite_ignore);	/* do NULLs come first in the sort order? */
+	bytea	  **opclassoptions pg_node_attr(readwrite_ignore); /* opclass-specific options for columns */
+	bool	   *canreturn pg_node_attr(readwrite_ignore);		/* which index cols can be returned in an
 								 * index-only scan? */
 	Oid			relam;			/* OID of the access method (in pg_am) */
 
-	List	   *indexprs;		/* expressions for non-simple index columns */
+	List	   *indexprs pg_node_attr(readwrite_ignore);		/* expressions for non-simple index columns */
 	List	   *indpred;		/* predicate if a partial index, else NIL */
 
 	List	   *indextlist;		/* targetlist representing index columns */
@@ -877,14 +877,14 @@ struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
-	bool		amoptionalkey;	/* can query omit key for the first column? */
-	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
-	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
-	bool		amhasgettuple;	/* does AM have amgettuple interface? */
-	bool		amhasgetbitmap; /* does AM have amgetbitmap interface? */
-	bool		amcanparallel;	/* does AM support parallel scan? */
-	bool		amcanmarkpos;	/* does AM support mark/restore? */
+	bool		amcanorderbyop pg_node_attr(readwrite_ignore); /* does AM support order by operator result? */
+	bool		amoptionalkey pg_node_attr(readwrite_ignore);	/* can query omit key for the first column? */
+	bool		amsearcharray pg_node_attr(readwrite_ignore);	/* can AM handle ScalarArrayOpExpr quals? */
+	bool		amsearchnulls pg_node_attr(readwrite_ignore);	/* can AM search for NULL/NOT NULL entries? */
+	bool		amhasgettuple pg_node_attr(readwrite_ignore);	/* does AM have amgettuple interface? */
+	bool		amhasgetbitmap pg_node_attr(readwrite_ignore); /* does AM have amgetbitmap interface? */
+	bool		amcanparallel pg_node_attr(readwrite_ignore);	/* does AM support parallel scan? */
+	bool		amcanmarkpos pg_node_attr(readwrite_ignore);	/* does AM support mark/restore? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
 };
@@ -905,9 +905,9 @@ typedef struct ForeignKeyOptInfo
 	Index		con_relid;		/* RT index of the referencing table */
 	Index		ref_relid;		/* RT index of the referenced table */
 	int			nkeys;			/* number of columns in the foreign key */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 
 	/* Derived info about whether FK's equality conditions match the query: */
 	int			nmatched_ec;	/* # of FK cols matched by ECs */
@@ -934,7 +934,7 @@ typedef struct StatisticExtInfo
 	NodeTag		type;
 
 	Oid			statOid;		/* OID of the statistics row */
-	RelOptInfo *rel;			/* back-link to statistic's table */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to statistic's table */
 	char		kind;			/* statistics kind of this entry */
 	Bitmapset  *keys;			/* attnums of the columns covered */
 	List	   *exprs;			/* expressions */
@@ -1108,7 +1108,7 @@ typedef struct PathTarget
 {
 	NodeTag		type;
 	List	   *exprs;			/* list of expressions to be computed */
-	Index	   *sortgrouprefs;	/* corresponding sort/group refnos, or 0 */
+	Index	   *sortgrouprefs pg_node_attr(array_size(exprs));	/* corresponding sort/group refnos, or 0 */
 	QualCost	cost;			/* cost of evaluating the expressions */
 	int			width;			/* estimated avg width of result tuples */
 	VolatileFunctionStatus has_volatile_expr;	/* indicates if exprs contain
@@ -1179,10 +1179,10 @@ typedef struct Path
 
 	NodeTag		pathtype;		/* tag identifying scan/join method */
 
-	RelOptInfo *parent;			/* the relation this path can build */
-	PathTarget *pathtarget;		/* list of Vars/Exprs, cost, width */
+	RelOptInfo *parent pg_node_attr(path_hack1);			/* the relation this path can build */
+	PathTarget *pathtarget pg_node_attr(path_hack2);		/* list of Vars/Exprs, cost, width */
 
-	ParamPathInfo *param_info;	/* parameterization info, or NULL if none */
+	ParamPathInfo *param_info pg_node_attr(path_hack3);	/* parameterization info, or NULL if none */
 
 	bool		parallel_aware; /* engage parallel-aware logic? */
 	bool		parallel_safe;	/* OK to use as part of parallel plan? */
@@ -2059,19 +2059,19 @@ typedef struct RestrictInfo
 
 	bool		outerjoin_delayed;	/* true if delayed by lower outer join */
 
-	bool		can_join;		/* see comment above */
+	bool		can_join pg_node_attr(equal_ignore);		/* see comment above */
 
-	bool		pseudoconstant; /* see comment above */
+	bool		pseudoconstant pg_node_attr(equal_ignore); /* see comment above */
 
-	bool		leakproof;		/* true if known to contain no leaked Vars */
+	bool		leakproof pg_node_attr(equal_ignore);		/* true if known to contain no leaked Vars */
 
-	VolatileFunctionStatus has_volatile;	/* to indicate if clause contains
+	VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);	/* to indicate if clause contains
 											 * any volatile functions. */
 
 	Index		security_level; /* see comment above */
 
 	/* The set of relids (varnos) actually referenced in the clause: */
-	Relids		clause_relids;
+	Relids		clause_relids pg_node_attr(equal_ignore);
 
 	/* The set of relids required to evaluate the clause: */
 	Relids		required_relids;
@@ -2083,47 +2083,47 @@ typedef struct RestrictInfo
 	Relids		nullable_relids;
 
 	/* These fields are set for any binary opclause: */
-	Relids		left_relids;	/* relids in left side of clause */
-	Relids		right_relids;	/* relids in right side of clause */
+	Relids		left_relids pg_node_attr(equal_ignore);	/* relids in left side of clause */
+	Relids		right_relids pg_node_attr(equal_ignore);	/* relids in right side of clause */
 
 	/* This field is NULL unless clause is an OR clause: */
-	Expr	   *orclause;		/* modified clause with RestrictInfos */
+	Expr	   *orclause pg_node_attr(equal_ignore);		/* modified clause with RestrictInfos */
 
 	/* This field is NULL unless clause is potentially redundant: */
-	EquivalenceClass *parent_ec;	/* generating EquivalenceClass */
+	EquivalenceClass *parent_ec pg_node_attr(equal_ignore readwrite_ignore);	/* generating EquivalenceClass */
 
 	/* cache space for cost and selectivity */
-	QualCost	eval_cost;		/* eval cost of clause; -1 if not yet set */
-	Selectivity norm_selec;		/* selectivity for "normal" (JOIN_INNER)
+	QualCost	eval_cost pg_node_attr(equal_ignore);		/* eval cost of clause; -1 if not yet set */
+	Selectivity norm_selec pg_node_attr(equal_ignore);		/* selectivity for "normal" (JOIN_INNER)
 								 * semantics; -1 if not yet set; >1 means a
 								 * redundant clause */
-	Selectivity outer_selec;	/* selectivity for outer join semantics; -1 if
+	Selectivity outer_selec pg_node_attr(equal_ignore);	/* selectivity for outer join semantics; -1 if
 								 * not yet set */
 
 	/* valid if clause is mergejoinable, else NIL */
-	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *mergeopfamilies pg_node_attr(equal_ignore);	/* opfamilies containing clause operator */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
-	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
-	EquivalenceClass *right_ec; /* EquivalenceClass containing righthand */
-	EquivalenceMember *left_em; /* EquivalenceMember for lefthand */
-	EquivalenceMember *right_em;	/* EquivalenceMember for righthand */
-	List	   *scansel_cache;	/* list of MergeScanSelCache structs */
+	EquivalenceClass *left_ec pg_node_attr(equal_ignore readwrite_ignore);	/* EquivalenceClass containing lefthand */
+	EquivalenceClass *right_ec pg_node_attr(equal_ignore readwrite_ignore); /* EquivalenceClass containing righthand */
+	EquivalenceMember *left_em pg_node_attr(equal_ignore); /* EquivalenceMember for lefthand */
+	EquivalenceMember *right_em pg_node_attr(equal_ignore);	/* EquivalenceMember for righthand */
+	List	   *scansel_cache pg_node_attr(copy_ignore equal_ignore);	/* list of MergeScanSelCache structs */
 
 	/* transient workspace for use while considering a specific join path */
-	bool		outer_is_left;	/* T = outer var on left, F = on right */
+	bool		outer_is_left pg_node_attr(equal_ignore);	/* T = outer var on left, F = on right */
 
 	/* valid if clause is hashjoinable, else InvalidOid: */
-	Oid			hashjoinoperator;	/* copy of clause operator */
+	Oid			hashjoinoperator pg_node_attr(equal_ignore);	/* copy of clause operator */
 
 	/* cache space for hashclause processing; -1 if not yet set */
-	Selectivity left_bucketsize;	/* avg bucketsize of left side */
-	Selectivity right_bucketsize;	/* avg bucketsize of right side */
-	Selectivity left_mcvfreq;	/* left side's most common val's freq */
-	Selectivity right_mcvfreq;	/* right side's most common val's freq */
+	Selectivity left_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of left side */
+	Selectivity right_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of right side */
+	Selectivity left_mcvfreq pg_node_attr(equal_ignore);	/* left side's most common val's freq */
+	Selectivity right_mcvfreq pg_node_attr(equal_ignore);	/* right side's most common val's freq */
 
 	/* hash equality operator used for memoize nodes, else InvalidOid */
-	Oid			hasheqoperator;
+	Oid			hasheqoperator pg_node_attr(equal_ignore);
 } RestrictInfo;
 
 /*
@@ -2178,8 +2178,8 @@ typedef struct MergeScanSelCache
 typedef struct PlaceHolderVar
 {
 	Expr		xpr;
-	Expr	   *phexpr;			/* the represented expression */
-	Relids		phrels;			/* base relids syntactically within expr src */
+	Expr	   *phexpr pg_node_attr(equal_ignore);			/* the represented expression */
+	Relids		phrels pg_node_attr(equal_ignore);			/* base relids syntactically within expr src */
 	Index		phid;			/* ID for PHV (unique within planner run) */
 	Index		phlevelsup;		/* > 0 if PHV belongs to outer query */
 } PlaceHolderVar;
@@ -2340,7 +2340,7 @@ typedef struct AppendRelInfo
 	 * child column is dropped or doesn't exist in the parent.
 	 */
 	int			num_child_cols; /* length of array */
-	AttrNumber *parent_colnos;	/* array of parent attnos, or zeroes */
+	AttrNumber *parent_colnos pg_node_attr(array_size(num_child_cols));	/* array of parent attnos, or zeroes */
 
 	/*
 	 * We store the parent table's OID here for inheritance, or InvalidOid for
@@ -2428,7 +2428,7 @@ typedef struct MinMaxAggInfo
 	Oid			aggfnoid;		/* pg_proc Oid of the aggregate */
 	Oid			aggsortop;		/* Oid of its sort operator */
 	Expr	   *target;			/* expression we are aggregating on */
-	PlannerInfo *subroot;		/* modified "root" for planning the subquery */
+	PlannerInfo *subroot pg_node_attr(readwrite_ignore);		/* modified "root" for planning the subquery */
 	Path	   *path;			/* access path for subquery */
 	Cost		pathcost;		/* estimated cost to fetch first row */
 	Param	   *param;			/* param for subplan's output */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 01a246d50e..3220381dbe 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -275,10 +275,10 @@ typedef struct MergeAppend
 	List	   *mergeplans;
 	/* these fields are just like the sort-key info in struct Sort: */
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 	/* Info for run-time subplan pruning; NULL if we're not doing that */
 	struct PartitionPruneInfo *part_prune_info;
 } MergeAppend;
@@ -298,9 +298,9 @@ typedef struct RecursiveUnion
 	/* Remaining fields are zero/null in UNION ALL case */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
-	Oid		   *dupCollations;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 	long		numGroups;		/* estimated number of groups in input */
 } RecursiveUnion;
 
@@ -750,10 +750,10 @@ typedef struct MergeJoin
 	bool		skip_mark_restore;	/* Can we skip mark/restore calls? */
 	List	   *mergeclauses;	/* mergeclauses as expression trees */
 	/* these are arrays, but have the same length as the mergeclauses list: */
-	Oid		   *mergeFamilies;	/* per-clause OIDs of btree opfamilies */
-	Oid		   *mergeCollations;	/* per-clause OIDs of collations */
-	int		   *mergeStrategies;	/* per-clause ordering (ASC or DESC) */
-	bool	   *mergeNullsFirst;	/* per-clause nulls ordering */
+	Oid		   *mergeFamilies pg_node_attr(array_size(mergeclauses));	/* per-clause OIDs of btree opfamilies */
+	Oid		   *mergeCollations pg_node_attr(array_size(mergeclauses));	/* per-clause OIDs of collations */
+	int		   *mergeStrategies pg_node_attr(array_size(mergeclauses));	/* per-clause ordering (ASC or DESC) */
+	bool	   *mergeNullsFirst pg_node_attr(array_size(mergeclauses));	/* per-clause nulls ordering */
 } MergeJoin;
 
 /* ----------------
@@ -793,8 +793,8 @@ typedef struct Memoize
 
 	int			numKeys;		/* size of the two arrays below */
 
-	Oid		   *hashOperators;	/* hash operators for each key */
-	Oid		   *collations;		/* cache keys */
+	Oid		   *hashOperators pg_node_attr(array_size(numKeys));	/* hash operators for each key */
+	Oid		   *collations pg_node_attr(array_size(numKeys));		/* cache keys */
 	List	   *param_exprs;	/* exprs containing parameters */
 	bool		singlerow;		/* true if the cache entry should be marked as
 								 * complete after we store the first tuple in
@@ -812,10 +812,10 @@ typedef struct Sort
 {
 	Plan		plan;
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 } Sort;
 
 /* ----------------
@@ -838,9 +838,9 @@ typedef struct Group
 {
 	Plan		plan;
 	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
-	Oid		   *grpCollations;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 } Group;
 
 /* ---------------
@@ -863,9 +863,9 @@ typedef struct Agg
 	AggStrategy aggstrategy;	/* basic strategy, see nodes.h */
 	AggSplit	aggsplit;		/* agg-splitting mode, see nodes.h */
 	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
-	Oid		   *grpCollations;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 	long		numGroups;		/* estimated number of groups in input */
 	uint64		transitionSpace;	/* for pass-by-ref transition data */
 	Bitmapset  *aggParams;		/* IDs of Params used in Aggref inputs */
@@ -883,13 +883,13 @@ typedef struct WindowAgg
 	Plan		plan;
 	Index		winref;			/* ID referenced by window functions */
 	int			partNumCols;	/* number of columns in partition clause */
-	AttrNumber *partColIdx;		/* their indexes in the target list */
-	Oid		   *partOperators;	/* equality operators for partition columns */
-	Oid		   *partCollations; /* collations for partition columns */
+	AttrNumber *partColIdx pg_node_attr(array_size(partNumCols));		/* their indexes in the target list */
+	Oid		   *partOperators pg_node_attr(array_size(partNumCols));	/* equality operators for partition columns */
+	Oid		   *partCollations pg_node_attr(array_size(partNumCols)); /* collations for partition columns */
 	int			ordNumCols;		/* number of columns in ordering clause */
-	AttrNumber *ordColIdx;		/* their indexes in the target list */
-	Oid		   *ordOperators;	/* equality operators for ordering columns */
-	Oid		   *ordCollations;	/* collations for ordering columns */
+	AttrNumber *ordColIdx pg_node_attr(array_size(ordNumCols));		/* their indexes in the target list */
+	Oid		   *ordOperators pg_node_attr(array_size(ordNumCols));	/* equality operators for ordering columns */
+	Oid		   *ordCollations pg_node_attr(array_size(ordNumCols));	/* collations for ordering columns */
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
@@ -909,9 +909,9 @@ typedef struct Unique
 {
 	Plan		plan;
 	int			numCols;		/* number of columns to check for uniqueness */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+	AttrNumber *uniqColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *uniqOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *uniqCollations pg_node_attr(array_size(numCols)); /* collations for equality comparisons */
 } Unique;
 
 /* ------------
@@ -947,10 +947,10 @@ typedef struct GatherMerge
 	int			rescan_param;	/* ID of Param that signals a rescan, or -1 */
 	/* remaining fields are just like the sort-key info in struct Sort */
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 	Bitmapset  *initParam;		/* param id's of initplans which are referred
 								 * at gather merge or one of it's child node */
 } GatherMerge;
@@ -990,9 +990,9 @@ typedef struct SetOp
 	SetOpStrategy strategy;		/* how to do it, see nodes.h */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
-	Oid		   *dupCollations;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 	AttrNumber	flagColIdx;		/* where is the flag column, if any */
 	int			firstFlag;		/* flag value for first input relation */
 	long		numGroups;		/* estimated number of groups in input */
@@ -1028,9 +1028,9 @@ typedef struct Limit
 	Node	   *limitCount;		/* COUNT parameter, or NULL if none */
 	LimitOption limitOption;	/* limit type */
 	int			uniqNumCols;	/* number of columns to check for similarity  */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+	AttrNumber *uniqColIdx pg_node_attr(array_size(uniqNumCols));		/* their indexes in the target list */
+	Oid		   *uniqOperators pg_node_attr(array_size(uniqNumCols));	/* equality operators to compare with */
+	Oid		   *uniqCollations pg_node_attr(array_size(uniqNumCols)); /* collations for equality comparisons */
 } Limit;
 
 
@@ -1189,9 +1189,9 @@ typedef struct PartitionedRelPruneInfo
 	Bitmapset  *present_parts;	/* Indexes of all partitions which subplans or
 								 * subparts are present for */
 	int			nparts;			/* Length of the following arrays: */
-	int		   *subplan_map;	/* subplan index by partition index, or -1 */
-	int		   *subpart_map;	/* subpart index by partition index, or -1 */
-	Oid		   *relid_map;		/* relation OID by partition index, or 0 */
+	int		   *subplan_map pg_node_attr(array_size(nparts));	/* subplan index by partition index, or -1 */
+	int		   *subpart_map pg_node_attr(array_size(nparts));	/* subpart index by partition index, or -1 */
+	Oid		   *relid_map pg_node_attr(array_size(nparts));		/* relation OID by partition index, or 0 */
 
 	/*
 	 * initial_pruning_steps shows how to prune during executor startup (i.e.,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 433437643e..d8abd98b55 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -63,7 +63,7 @@ typedef enum OnCommitAction
 typedef struct RangeVar
 {
 	NodeTag		type;
-	char	   *catalogname;	/* the catalog (database) name, or NULL */
+	char	   *catalogname pg_node_attr(readwrite_ignore);	/* the catalog (database) name, or NULL */
 	char	   *schemaname;		/* the schema name, or NULL */
 	char	   *relname;		/* the relation/sequence name */
 	bool		inh;			/* expand rel by inheritance? recursively act
@@ -196,8 +196,8 @@ typedef struct Var
 	Index		varlevelsup;	/* for subquery variables referencing outer
 								 * relations; 0 in a normal var, >0 means N
 								 * levels up */
-	Index		varnosyn;		/* syntactic relation index (0 if unknown) */
-	AttrNumber	varattnosyn;	/* syntactic attribute number */
+	Index		varnosyn pg_node_attr(equal_ignore);		/* syntactic relation index (0 if unknown) */
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore);	/* syntactic attribute number */
 	int			location;		/* token location, or -1 if unknown */
 } Var;
 
@@ -324,7 +324,7 @@ typedef struct Aggref
 	Oid			aggtype;		/* type Oid of result of the aggregate */
 	Oid			aggcollid;		/* OID of collation of result */
 	Oid			inputcollid;	/* OID of collation that function should use */
-	Oid			aggtranstype;	/* type Oid of aggregate's transition value */
+	Oid			aggtranstype pg_node_attr(equal_ignore);	/* type Oid of aggregate's transition value */
 	List	   *aggargtypes;	/* type Oids of direct and aggregated args */
 	List	   *aggdirectargs;	/* direct arguments, if an ordered-set agg */
 	List	   *args;			/* aggregated arguments and sort expressions */
@@ -371,8 +371,8 @@ typedef struct GroupingFunc
 	Expr		xpr;
 	List	   *args;			/* arguments, not evaluated but kept for
 								 * benefit of EXPLAIN etc. */
-	List	   *refs;			/* ressortgrouprefs of arguments */
-	List	   *cols;			/* actual column positions set by planner */
+	List	   *refs pg_node_attr(equal_ignore);			/* ressortgrouprefs of arguments */
+	List	   *cols pg_node_attr(equal_ignore);			/* actual column positions set by planner */
 	Index		agglevelsup;	/* same as Aggref.agglevelsup */
 	int			location;		/* token location */
 } GroupingFunc;
@@ -540,7 +540,7 @@ typedef struct OpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of underlying function */
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of underlying function */
 	Oid			opresulttype;	/* PG_TYPE OID of result value */
 	bool		opretset;		/* true if operator returns set */
 	Oid			opcollid;		/* OID of collation of result */
@@ -597,9 +597,9 @@ typedef struct ScalarArrayOpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of comparison function */
-	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
-	Oid			negfuncid;		/* PG_PROC OID of negator of opfuncid function
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of comparison function */
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of hash func or InvalidOid */
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of negator of opfuncid function
 								 * or InvalidOid.  See above */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..ee22f9c5c5 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -365,14 +365,14 @@
  * copyObject(), to facilitate catching errors and omissions in
  * copyObject().
  */
-/* #define COPY_PARSE_PLAN_TREES */
+#define COPY_PARSE_PLAN_TREES
 
 /*
  * Define this to force all parse and plan trees to be passed through
  * outfuncs.c/readfuncs.c, to facilitate catching errors and omissions in
  * those modules.
  */
-/* #define WRITE_READ_PARSE_PLAN_TREES */
+#define WRITE_READ_PARSE_PLAN_TREES
 
 /*
  * Define this to force all raw parse trees for DML statements to be scanned
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..d91ee6ae85 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -272,9 +272,9 @@ typedef struct ForeignKeyCacheInfo
 	Oid			confrelid;		/* relation referenced by the foreign key */
 	int			nkeys;			/* number of columns in the foreign key */
 	/* these arrays each have nkeys valid entries: */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 } ForeignKeyCacheInfo;
 
 
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 165a93987a..8dff6b4cb2 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -835,6 +835,52 @@ EOF
 		close($chs);
 	}
 
+	if (IsNewer('src/backend/nodes/node-stuff-stamp',
+		'src/backend/nodes/gen_node_stuff.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_stuff.pl @node_files");
+		open(my $f, '>', 'node-stuff-stamp') || confess "Could not touch node-stuff-stamp";
+		close($f);
+		chdir('../../..');
+	}
+
+	if (IsNewer(
+			'src/include/nodes/nodetags.h',
+			'src/backend/nodes/nodetags.h'))
+	{
+		copyFile('src/backend/nodes/nodetags.h',
+			'src/include/nodes/nodetags.h');
+	}
+
 	open(my $o, '>', "doc/src/sgml/version.sgml")
 	  || croak "Could not write to version.sgml\n";
 	print $o <<EOF;

base-commit: 68f7c4b57a27dbcd3e93ba3ff7b0b49664b25e09
-- 
2.33.0

#15Corey Huinker
corey.huinker@gmail.com
In reply to: Peter Eisentraut (#14)
Re: automatically generating node support functions

build support and made the Perl code more portable, so that the cfbot
doesn't have to be sad.

Was this also the reason for doing the output with print statements rather
than using one of the templating libraries? I'm mostly just curious, and
certainly don't want it to get in the way of working code.

#16Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Corey Huinker (#15)
Re: automatically generating node support functions

On 12.10.21 03:06, Corey Huinker wrote:

build support and made the Perl code more portable, so that the cfbot
doesn't have to be sad.

Was this also the reason for doing the output with print statements
rather than using one of the templating libraries? I'm mostly just
curious, and certainly don't want it to get in the way of working code.

Unless there is a templating library that ships with Perl (>= 5.8.3,
apparently now), this seems impractical.

#17Andrew Dunstan
andrew@dunslane.net
In reply to: Peter Eisentraut (#14)
Re: automatically generating node support functions

On 10/11/21 10:22 AM, Peter Eisentraut wrote:

On 15.09.21 21:01, Peter Eisentraut wrote:

On 17.08.21 16:36, Peter Eisentraut wrote:

Here is another set of preparatory patches that clean up various
special cases and similar in the node support.

This set of patches has been committed.  I'll close this commit fest
entry and come back with the main patch series in the future.

Here is an updated version of my original patch, so we have something
to continue the discussion around.  This takes into account all the
preparatory patches that have been committed in the meantime.  I have
also changed it so that the array size of a pointer is now explicitly
declared using pg_node_attr(array_size(N)) instead of picking the most
recent scalar field, which was admittedly hacky.  I have also added
MSVC build support and made the Perl code more portable, so that the
cfbot doesn't have to be sad.

I haven't been through the whole thing, but I did notice this: the
comment stripping code looks rather fragile. I think it would blow up if
there were a continuation line not starting with  qr/\s*\*/. It's a lot
simpler and more robust to do this if you slurp the file in whole.
Here's what we do in the buildfarm code:

    my $src = file_contents($_);
# strip C comments
    # We used to use the recipe in perlfaq6 but there is actually no point.
    # We don't need to keep the quoted string values anyway, and
    # on some platforms the complex regex causes perl to barf and crash.
    $src =~ s{/\*.*?\*/}{}gs;

After you've done that splitting it into lines is pretty simple.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#18Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Andrew Dunstan (#17)
1 attachment(s)
Re: automatically generating node support functions

On 12.10.21 15:52, Andrew Dunstan wrote:

I haven't been through the whole thing, but I did notice this: the
comment stripping code looks rather fragile. I think it would blow up if
there were a continuation line not starting with  qr/\s*\*/. It's a lot
simpler and more robust to do this if you slurp the file in whole.
Here's what we do in the buildfarm code:

    my $src = file_contents($_);
# strip C comments
    # We used to use the recipe in perlfaq6 but there is actually no point.
    # We don't need to keep the quoted string values anyway, and
    # on some platforms the complex regex causes perl to barf and crash.
    $src =~ s{/\*.*?\*/}{}gs;

After you've done that splitting it into lines is pretty simple.

Here is an updated patch, with some general rebasing, and the above
improvement. It now also generates #include lines necessary in
copyfuncs etc. to pull in all the node types it operates on.

Further, I have looked more into the "metadata" approach discussed in
[0]: /messages/by-id/20190828234136.fk2ndqtld3onfrrp@alap3.anarazel.de
structures my script produces. You just loop over all the node types
and print stuff and keep a few counters. I don't plan to work on that
at this time, but I just wanted to point out that if people wanted to
move into that direction, my patch wouldn't be in the way.

[0]: /messages/by-id/20190828234136.fk2ndqtld3onfrrp@alap3.anarazel.de
/messages/by-id/20190828234136.fk2ndqtld3onfrrp@alap3.anarazel.de

Attachments:

v3-0001-Automatically-generate-node-support-functions.patchtext/plain; charset=UTF-8; name=v3-0001-Automatically-generate-node-support-functions.patchDownload
From e2c08d8b793200a07b8fe5ae85dd23f401ddcef1 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 29 Dec 2021 12:00:41 +0100
Subject: [PATCH v3] 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.inc1.c and copyfuncs.inc2.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.  When we do
that, some code comments should probably be preserved elsewhere, so
that will need another pass of consideration.

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

Subtyping (TidScan -> Scan) is supported.

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

Discussion: https://www.postgresql.org/message-id/flat/c1097590-a6a4-486a-64b1-e1f9cc0533ce%40enterprisedb.com
---
 src/backend/Makefile                  |   8 +-
 src/backend/nodes/.gitignore          |   3 +
 src/backend/nodes/Makefile            |  46 ++
 src/backend/nodes/copyfuncs.c         |  19 +-
 src/backend/nodes/equalfuncs.c        |  22 +-
 src/backend/nodes/gen_node_support.pl | 660 ++++++++++++++++++++++++++
 src/backend/nodes/outfuncs.c          |  30 +-
 src/backend/nodes/readfuncs.c         |  23 +-
 src/include/nodes/.gitignore          |   2 +
 src/include/nodes/nodes.h             |   8 +
 src/include/nodes/parsenodes.h        |   2 +-
 src/include/nodes/pathnodes.h         | 134 +++---
 src/include/nodes/plannodes.h         |  90 ++--
 src/include/nodes/primnodes.h         |  20 +-
 src/include/pg_config_manual.h        |   4 +-
 src/include/utils/rel.h               |   6 +-
 src/tools/msvc/Solution.pm            |  46 ++
 17 files changed, 976 insertions(+), 147 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 0da848b1fd..a33db1ae01 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -143,11 +143,15 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
 submake-catalog-headers:
 	$(MAKE) -C catalog distprep generated-header-symlinks
 
+# run this unconditionally to avoid needing to know its dependencies here:
+submake-nodes-headers:
+	$(MAKE) -C nodes distprep generated-header-symlinks
+
 # run this unconditionally to avoid needing to know its dependencies here:
 submake-utils-headers:
 	$(MAKE) -C utils distprep generated-header-symlinks
 
-.PHONY: submake-catalog-headers submake-utils-headers
+.PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 # Make symlinks for these headers in the include directory. That way
 # we can cut down on the -I options. Also, a symlink is automatically
@@ -162,7 +166,7 @@ submake-utils-headers:
 
 .PHONY: generated-headers
 
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..2a79ee6ed8
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,3 @@
+/node-support-stamp
+/nodetags.h
+/*funcs.inc?.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..56cddc06da 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,49 @@ OBJS = \
 	value.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+node_headers = \
+	nodes/nodes.h \
+	nodes/execnodes.h \
+	nodes/plannodes.h \
+	nodes/primnodes.h \
+	nodes/pathnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.h \
+	nodes/value.h \
+	commands/trigger.h \
+	commands/event_trigger.h \
+	foreign/fdwapi.h \
+	access/amapi.h \
+	access/tableam.h \
+	access/tsmapi.h \
+	utils/rel.h \
+	nodes/supportnodes.h \
+	executor/tuptable.h \
+	nodes/lockoptions.h \
+	access/sdir.h
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-support-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+node-support-stamp: gen_node_support.pl $(addprefix $(top_srcdir)/src/include/,$(node_headers))
+	$(PERL) $^
+	touch $@
+
+$(top_builddir)/src/include/nodes/header-stamp: node-support-stamp
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	cd '$(dir $@)' && for file in nodetags.h; do \
+	  rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \
+	done
+	touch $@
+
+maintainer-clean: clean
+	rm -f node-support-stamp *funcs.inc?.c nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index df0b747883..6932178694 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.inc1.c"
+
+#ifdef OBSOLETE
 /* ****************************************************************
  *					 plannodes.h copy functions
  * ****************************************************************
@@ -1456,6 +1455,7 @@ _copyVar(const Var *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * _copyConst
@@ -1495,6 +1495,7 @@ _copyConst(const Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -2730,6 +2731,7 @@ _copyParamRef(const ParamRef *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -2767,6 +2769,7 @@ _copyA_Const(const A_Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -4902,6 +4905,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *					extensible.h copy functions
@@ -4924,6 +4928,7 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /* ****************************************************************
  *					value.h copy functions
  * ****************************************************************
@@ -4984,6 +4989,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5004,6 +5010,8 @@ copyObjectImpl(const void *from)
 
 	switch (nodeTag(from))
 	{
+#include "copyfuncs.inc2.c"
+#ifdef OBSOLETE
 			/*
 			 * PLAN NODES
 			 */
@@ -5361,6 +5369,7 @@ copyObjectImpl(const void *from)
 		case T_BitString:
 			retval = _copyBitString(from);
 			break;
+#endif /*OBSOLETE*/
 
 			/*
 			 * LIST NODES
@@ -5378,6 +5387,7 @@ copyObjectImpl(const void *from)
 			retval = list_copy(from);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -5917,6 +5927,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 cb7ddd463c..c61fd6650b 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.inc1.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from primnodes.h
  */
@@ -185,6 +183,7 @@ _equalVar(const Var *a, const Var *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -207,6 +206,7 @@ _equalConst(const Const *a, const Const *b)
 						a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -946,6 +946,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -967,6 +968,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const ExtensibleNode *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2432,6 +2434,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2449,6 +2452,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3058,6 +3062,7 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3118,6 +3123,7 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from value.h
  */
@@ -3153,6 +3159,7 @@ _equalBitString(const BitString *a, const BitString *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * equal
@@ -3183,6 +3190,8 @@ equal(const void *a, const void *b)
 
 	switch (nodeTag(a))
 	{
+#include "equalfuncs.inc2.c"
+#ifdef OBSOLETE
 			/*
 			 * PRIMITIVE NODES
 			 */
@@ -3361,6 +3370,7 @@ equal(const void *a, const void *b)
 		case T_PlaceHolderInfo:
 			retval = _equalPlaceHolderInfo(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		case T_List:
 		case T_IntList:
@@ -3368,6 +3378,7 @@ equal(const void *a, const void *b)
 			retval = _equalList(a, b);
 			break;
 
+#ifdef OBSOLETE
 		case T_Integer:
 			retval = _equalInteger(a, b);
 			break;
@@ -3913,6 +3924,7 @@ equal(const void *a, const void *b)
 		case T_PublicationTable:
 			retval = _equalPublicationTable(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..75488377a5
--- /dev/null
+++ b/src/backend/nodes/gen_node_support.pl
@@ -0,0 +1,660 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# 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
+
+
+sub elem
+{
+	my $x = shift;
+	return grep { $_ eq $x } @_;
+}
+
+
+my @node_types = qw(Node);
+my %node_type_info;
+
+my @no_copy;
+my @no_read_write;
+
+my @scalar_types = qw(
+	bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+	AclMode AttrNumber Cardinality Cost Index Oid Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr
+);
+
+my @enum_types;
+
+# For abstract types we track their fields, so that subtypes can use
+# them, but we don't emit a node tag, so you can't instantiate them.
+my @abstract_types = qw(
+	Node Expr
+	BufferHeapTupleTableSlot HeapTupleTableSlot MinimalTupleTableSlot VirtualTupleTableSlot
+	JoinPath
+	PartitionPruneStep
+);
+
+# Special cases that either don't have their own struct or the struct
+# is not in a header file.  We just generate node tags for them, but
+# they otherwise don't participate in node support.
+my @extra_tags = qw(
+	IntList OidList
+	AllocSetContext GenerationContext SlabContext
+	TIDBitmap
+	WindowObjectData
+);
+
+# This is a regular node, but we skip parsing it from its header file
+# since we won't use its internal structure here anyway.
+push @node_types, qw(List);
+
+# pathnodes.h exceptions
+push @no_copy, qw(
+	RelOptInfo IndexOptInfo Path PlannerGlobal EquivalenceClass EquivalenceMember ForeignKeyOptInfo
+	GroupingSetData IncrementalSortPath IndexClause MinMaxAggInfo PathTarget PlannerInfo PlannerParamItem
+	ParamPathInfo RollupData RowIdentityVarInfo StatisticExtInfo
+);
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember* QualCost);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation);
+
+
+## read input
+
+foreach my $infile (@ARGV)
+{
+	my $in_struct;
+	my $subline;
+	my $is_node_struct;
+	my $supertype;
+	my $supertype_field;
+
+	my @my_fields;
+	my %my_field_types;
+	my %my_field_attrs;
+
+	open my $ifh, '<', $infile or die "could not open \"$infile\": $!";
+
+	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)/;
+
+		if ($in_struct)
+		{
+			$subline++;
+
+			# first line should have opening brace
+			if ($subline == 1)
+			{
+				$is_node_struct = 0;
+				$supertype = undef;
+				next if $line eq '{';
+				die;
+			}
+			# second line should have node tag or supertype
+			elsif ($subline == 2)
+			{
+				if ($line =~ /^\s*NodeTag\s+type;/)
+				{
+					$is_node_struct = 1;
+					next;
+				}
+				elsif ($line =~ /\s*(\w+)\s+(\w+);/ and elem $1, @node_types)
+				{
+					$is_node_struct = 1;
+					$supertype = $1;
+					$supertype_field = $2;
+					next;
+				}
+			}
+
+			# end of struct
+			if ($line =~ /^\}\s*$in_struct;$/ || $line =~ /^\};$/)
+			{
+				if ($is_node_struct)
+				{
+					push @node_types, $in_struct;
+					my @f = @my_fields;
+					my %ft = %my_field_types;
+					my %fa = %my_field_attrs;
+					if ($supertype)
+					{
+						my @superfields;
+						foreach my $sf (@{$node_type_info{$supertype}->{fields}})
+						{
+							my $fn = "${supertype_field}.$sf";
+							push @superfields, $fn;
+							$ft{$fn} = $node_type_info{$supertype}->{field_types}{$sf};
+							$fa{$fn} = $node_type_info{$supertype}->{field_attrs}{$sf};
+							$fa{$fn} =~ s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/ if $fa{$fn};
+						}
+						unshift @f, @superfields;
+					}
+					$node_type_info{$in_struct}->{fields} = \@f;
+					$node_type_info{$in_struct}->{field_types} = \%ft;
+					$node_type_info{$in_struct}->{field_attrs} = \%fa;
+
+					if (elem basename($infile),
+						qw(execnodes.h trigger.h event_trigger.h amapi.h tableam.h
+							tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h))
+					{
+						push @no_copy, $in_struct;
+						push @no_read_write, $in_struct;
+					}
+
+					if ($supertype && ($supertype eq 'Path' || $supertype eq 'JoinPath'))
+					{
+						push @no_copy, $in_struct;
+					}
+				}
+
+				# start new cycle
+				$in_struct = undef;
+				@my_fields = ();
+				%my_field_types = ();
+				%my_field_attrs = ();
+			}
+			# normal struct field
+			elsif ($line =~ /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w() ]*)\))?;/)
+			{
+				if ($is_node_struct)
+				{
+					my $type = $1;
+					my $name = $2;
+					my $array_size = $3;
+					my $attr = $4;
+
+					$type =~ s/^const\s*//;
+					$type =~ s/\s*$//;
+					$type =~ s/\s+\*$/*/;
+					die if $type eq '';
+					$type = $type . $array_size if $array_size;
+					push @my_fields, $name;
+					$my_field_types{$name} = $type;
+					$my_field_attrs{$name} = $attr;
+				}
+			}
+			else
+			{
+				if ($is_node_struct)
+				{
+					#warn "$infile:$.: could not parse \"$line\"\n";
+				}
+			}
+		}
+		# not in a struct
+		else
+		{
+			# start of a struct?
+			if ($line =~ /^(?:typedef )?struct (\w+)(\s*\/\*.*)?$/ && $1 ne 'Node')
+			{
+				$in_struct = $1;
+				$subline = 0;
+			}
+			# one node type typedef'ed directly from another
+			elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types)
+			{
+				my $alias_of = $1;
+				my $n = $2;
+
+				push @node_types, $n;
+				my @f = @{$node_type_info{$alias_of}->{fields}};
+				my %ft = %{$node_type_info{$alias_of}->{field_types}};
+				my %fa = %{$node_type_info{$alias_of}->{field_attrs}};
+				$node_type_info{$n}->{fields} = \@f;
+				$node_type_info{$n}->{field_types} = \%ft;
+				$node_type_info{$n}->{field_attrs} = \%fa;
+			}
+			# collect enum names
+			elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/)
+			{
+				push @enum_types, $1;
+			}
+		}
+	}
+
+	if ($in_struct)
+	{
+		die "runaway \"$in_struct\" in file \"$infile\"\n";
+	}
+
+	close $ifh;
+} # for each file
+
+
+## write output
+
+my $tmpext  = ".tmp$$";
+
+# nodetags.h
+
+open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
+
+my $i = 1;
+foreach my $n (@node_types,@extra_tags)
+{
+	next if elem $n, @abstract_types;
+	print $nt "\tT_${n} = $i,\n";
+	$i++;
+}
+
+close $nt;
+
+
+# #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 $cf, '>', 'copyfuncs.inc1.c' . $tmpext or die $!;
+open my $ef, '>', 'equalfuncs.inc1.c' . $tmpext or die $!;
+open my $cf2, '>', 'copyfuncs.inc2.c' . $tmpext or die $!;
+open my $ef2, '>', 'equalfuncs.inc2.c' . $tmpext or die $!;
+
+print $cf $node_includes;
+print $ef $node_includes;
+
+my @custom_copy = qw(A_Const Const ExtensibleNode);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_copy;
+	next if $n eq 'List';
+
+	print $cf2 "
+\t\tcase T_${n}:
+\t\t\tretval = _copy${n}(from);
+\t\t\tbreak;";
+
+	print $ef2 "
+\t\tcase T_${n}:
+\t\t\tretval = _equal${n}(a, b);
+\t\t\tbreak;";
+
+	next if elem $n, @custom_copy;
+
+	print $cf "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+";
+
+	print $ef "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+";
+
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $copy_ignore = ($a =~ /\bcopy_ignore\b/);
+		my $equal_ignore = ($a =~ /\bequal_ignore\b/);
+		if ($t eq 'char*')
+		{
+			print $cf "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_STRING_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $cf "\tCOPY_BITMAPSET_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_BITMAPSET_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $cf "\tCOPY_LOCATION_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_LOCATION_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif (elem $t, @scalar_types or elem $t, @enum_types)
+		{
+			print $cf "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			if ($a =~ /\bequal_ignore_if_zero\b/)
+			{
+				print $ef "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
+			}
+			else
+			{
+				print $ef "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+			}
+		}
+		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
+		{
+			my $tt = $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $cf "\tCOPY_POINTER_FIELD($f, list_length(from->$array_size_field) * sizeof($tt));\n" unless $copy_ignore;
+				print $ef "\tCOMPARE_POINTER_FIELD($f, list_length(a->$array_size_field) * sizeof($tt));\n" unless $equal_ignore;
+			}
+			else
+			{
+				print $cf "\tCOPY_POINTER_FIELD($f, from->$array_size_field * sizeof($tt));\n" unless $copy_ignore;
+				print $ef "\tCOMPARE_POINTER_FIELD($f, a->$array_size_field * sizeof($tt));\n" unless $equal_ignore;
+			}
+		}
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $cf "\tCOPY_NODE_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t =~ /\w+\[/)
+		{
+			print $cf "\tCOPY_ARRAY_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_ARRAY_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $cf "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $cf "
+\treturn newnode;
+}
+";
+	print $ef "
+\treturn true;
+}
+";
+}
+
+close $cf;
+close $ef;
+close $cf2;
+close $ef2;
+
+
+# outfuncs.c, readfuncs.c
+
+open my $of, '>', 'outfuncs.inc1.c' . $tmpext or die $!;
+open my $rf, '>', 'readfuncs.inc1.c' . $tmpext or die $!;
+open my $of2, '>', 'outfuncs.inc2.c' . $tmpext or die $!;
+open my $rf2, '>', 'readfuncs.inc2.c' . $tmpext or die $!;
+
+print $of $node_includes;
+print $rf $node_includes;
+
+my @custom_readwrite = qw(A_Const A_Expr BoolExpr Const Constraint ExtensibleNode Query RangeTblEntry);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_read_write;
+	next if $n eq 'List';
+	next if elem $n, qw(BitString Float Integer String);
+
+	# XXX For now, skip all "Stmt"s except that ones that were there before.
+	if ($n =~ /Stmt$/)
+	{
+		my @keep = qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt ImportForeignSchemaStmt IndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt);
+		next unless elem $n, @keep;
+	}
+
+	# XXX Also skip read support for those that didn't have it before.
+	my $no_read = ($n eq 'A_Star' || $n eq 'A_Const' || $n eq 'A_Expr' || $n eq 'Constraint' || $n =~ /Path$/ || $n eq 'ForeignKeyCacheInfo' || $n eq 'ForeignKeyOptInfo' || $n eq 'PathTarget');
+
+	my $N = uc $n;
+	$N =~ s/_//g;
+
+	print $of2 "\t\t\tcase T_${n}:\n".
+	  "\t\t\t\t_out${n}(str, obj);\n".
+	  "\t\t\t\tbreak;\n";
+
+	print $rf2 "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+
+	next if elem $n, @custom_readwrite;
+
+	print $of "
+static void
+_out${n}(StringInfo str, const $n *node)
+{
+\tWRITE_NODE_TYPE(\"$N\");
+
+";
+
+	print $rf "
+static $n *
+_read${n}(void)
+{
+\tREAD_LOCALS($n);
+
+" unless $no_read;
+
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $readwrite_ignore = ($a =~ /\breadwrite_ignore\b/);
+		next if $readwrite_ignore;
+
+		# XXX Previously, for subtyping, only the leaf field name is
+		# used. Ponder whether we want to keep it that way.
+
+		if ($t eq 'bool')
+		{
+			print $of "\tWRITE_BOOL_FIELD($f);\n";
+			print $rf "\tREAD_BOOL_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $of "\tWRITE_LOCATION_FIELD($f);\n";
+			print $rf "\tREAD_LOCATION_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' || $t eq 'int32' || $t eq 'AttrNumber' || $t eq 'StrategyNumber')
+		{
+			print $of "\tWRITE_INT_FIELD($f);\n";
+			print $rf "\tREAD_INT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint32' || $t eq 'bits32' || $t eq 'AclMode' || $t eq 'BlockNumber' || $t eq 'Index' || $t eq 'SubTransactionId')
+		{
+			print $of "\tWRITE_UINT_FIELD($f);\n";
+			print $rf "\tREAD_UINT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint64')
+		{
+			print $of "\tWRITE_UINT64_FIELD($f);\n";
+			print $rf "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Oid')
+		{
+			print $of "\tWRITE_OID_FIELD($f);\n";
+			print $rf "\tREAD_OID_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'long')
+		{
+			print $of "\tWRITE_LONG_FIELD($f);\n";
+			print $rf "\tREAD_LONG_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char')
+		{
+			print $of "\tWRITE_CHAR_FIELD($f);\n";
+			print $rf "\tREAD_CHAR_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'double')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.6f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cardinality')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.0f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cost')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.2f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'QualCost')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f.startup, \"%.2f\");\n";
+			print $of "\tWRITE_FLOAT_FIELD($f.per_tuple, \"%.2f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read;
+			print $rf "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
+		}
+		elsif ($t eq 'Selectivity')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.4f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $of "\tWRITE_STRING_FIELD($f);\n";
+			print $rf "\tREAD_STRING_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $of "\tWRITE_BITMAPSET_FIELD($f);\n";
+			print $rf "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
+		}
+		elsif (elem $t, @enum_types)
+		{
+			print $of "\tWRITE_ENUM_FIELD($f, $t);\n";
+			print $rf "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read;
+		}
+		elsif ($t =~ /(\w+)(\*|\[)/ and elem $1, @scalar_types)
+		{
+			my $tt = uc $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $of "\tWRITE_${tt}_ARRAY($f, list_length(node->$array_size_field));\n";
+				print $rf "\tREAD_${tt}_ARRAY($f, list_length(local_node->$array_size_field));\n" unless $no_read;
+			}
+			else
+			{
+				print $of "\tWRITE_${tt}_ARRAY($f, node->$array_size_field);\n";
+				print $rf "\tREAD_${tt}_ARRAY($f, local_node->$array_size_field);\n" unless $no_read;
+			}
+		}
+		elsif ($t eq 'RelOptInfo*' && $a eq 'path_hack1')
+		{
+			print $of "\tappendStringInfoString(str, \" :parent_relids \");\n".
+			  "\toutBitmapset(str, node->$f->relids);\n";
+		}
+		elsif ($t eq 'PathTarget*' && $a eq 'path_hack2')
+		{
+			(my $f2 = $f) =~ s/pathtarget/parent/;
+			print $of "\tif (node->$f != node->$f2->reltarget)\n".
+			  "\t\tWRITE_NODE_FIELD($f);\n";
+		}
+		elsif ($t eq 'ParamPathInfo*' && $a eq 'path_hack3')
+		{
+			print $of "\tif (node->$f)\n".
+			  "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
+			  "\telse\n".
+			  "\t\toutBitmapset(str, NULL);\n";
+		}
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $of "\tWRITE_NODE_FIELD($f);\n";
+			print $rf "\tREAD_NODE_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $of q{
+	appendStringInfoString(str, " :methods ");
+	outToken(str, node->methods->CustomName);
+};
+			print $rf q!
+	{
+		/* Lookup CustomScanMethods by CustomName */
+		char	   *custom_name;
+		const CustomScanMethods *methods;
+		token = pg_strtok(&length); /* skip methods: */
+		token = pg_strtok(&length); /* CustomName */
+		custom_name = nullable_string(token, length);
+		methods = GetCustomScanMethods(custom_name, false);
+		local_node->methods = methods;
+	}
+! unless $no_read;
+		}
+		elsif ($t eq 'ParamListInfo' || $t =~ /PartitionBoundInfoData/ || $t eq 'PartitionDirectory' || $t eq 'PartitionScheme' || $t eq 'void*' || $t =~ /\*\*$/)
+		{
+			# ignore
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $of "}
+";
+	print $rf "
+\tREAD_DONE();
+}
+" unless $no_read;
+}
+
+close $of;
+close $rf;
+close $of2;
+close $rf2;
+
+
+foreach my $file (qw(nodetags.h copyfuncs.inc1.c copyfuncs.inc2.c equalfuncs.inc1.c equalfuncs.inc2.c outfuncs.inc1.c outfuncs.inc2.c readfuncs.inc1.c readfuncs.inc2.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 91a89b6d51..264bbf3f7f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -31,11 +31,10 @@
 
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
-#include "nodes/plannodes.h"
+#include "nodes/bitmapset.h"
+#include "nodes/nodes.h"
+#include "nodes/pg_list.h"
 #include "utils/datum.h"
-#include "utils/rel.h"
 
 static void outChar(StringInfo str, char c);
 
@@ -295,6 +294,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
 }
 
 
+#include "outfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from plannodes.h
  */
@@ -1135,6 +1137,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)
@@ -1156,6 +1159,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)
 {
@@ -1326,6 +1330,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)
@@ -1354,6 +1359,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2674,6 +2680,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
 	WRITE_NODE_FIELD(item);
 	WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2696,6 +2703,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -3028,6 +3036,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)
@@ -3100,6 +3109,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3238,6 +3248,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)
@@ -3318,6 +3329,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3341,6 +3353,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)
@@ -3453,6 +3466,7 @@ _outBitString(StringInfo str, const BitString *node)
 	appendStringInfoString(str, node->val);
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3484,6 +3498,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)
@@ -3500,6 +3515,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)
 {
@@ -3644,6 +3660,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)
@@ -3765,6 +3782,7 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -3825,6 +3843,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -3854,6 +3873,8 @@ outNode(StringInfo str, const void *obj)
 		appendStringInfoChar(str, '{');
 		switch (nodeTag(obj))
 		{
+#include "outfuncs.inc2.c"
+#ifdef OBSOLETE
 			case T_PlannedStmt:
 				_outPlannedStmt(str, obj);
 				break;
@@ -4526,6 +4547,7 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionRangeDatum:
 				_outPartitionRangeDatum(str, obj);
 				break;
+#endif /*OBSOLETE*/
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d79af6e56e..b66a42188b 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.inc1.c"
+
 /*
  * _readQuery
  */
@@ -289,6 +289,7 @@ _readQuery(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readNotifyStmt
  */
@@ -587,6 +588,7 @@ _readVar(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readConst
@@ -613,6 +615,7 @@ _readConst(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readParam
  */
@@ -838,6 +841,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readBoolExpr
@@ -865,6 +869,7 @@ _readBoolExpr(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readSubLink
  */
@@ -1419,6 +1424,7 @@ _readAppendRelInfo(void)
 /*
  *	Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1514,6 +1520,7 @@ _readRangeTblEntry(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2636,6 +2643,7 @@ _readAlternativeSubPlan(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2667,6 +2675,7 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2701,6 +2710,7 @@ _readPartitionRangeDatum(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2725,7 +2735,11 @@ parseNodeString(void)
 #define MATCH(tokname, namelen) \
 	(length == namelen && memcmp(token, tokname, namelen) == 0)
 
-	if (MATCH("QUERY", 5))
+	if (false)
+		;
+#include "readfuncs.inc2.c"
+#ifdef OBSOLETE
+	else if (MATCH("QUERY", 5))
 		return_value = _readQuery();
 	else if (MATCH("WITHCHECKOPTION", 15))
 		return_value = _readWithCheckOption();
@@ -2973,6 +2987,7 @@ parseNodeString(void)
 		return_value = _readPartitionBoundSpec();
 	else if (MATCH("PARTITIONRANGEDATUM", 19))
 		return_value = _readPartitionRangeDatum();
+#endif /*OBSOLETE*/
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/include/nodes/.gitignore b/src/include/nodes/.gitignore
new file mode 100644
index 0000000000..99fb1d3787
--- /dev/null
+++ b/src/include/nodes/.gitignore
@@ -0,0 +1,2 @@
+/nodetags.h
+/header-stamp
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7c657c1241..012b478a76 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -27,6 +27,8 @@ typedef enum NodeTag
 {
 	T_Invalid = 0,
 
+#include "nodes/nodetags.h"
+#ifdef OBSOLETE
 	/*
 	 * TAGS FOR EXECUTOR NODES (execnodes.h)
 	 */
@@ -527,8 +529,14 @@ typedef enum NodeTag
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * used in node definitions to set extra information for gen_node_support.pl
+ */
+#define pg_node_attr(x)
+
 /*
  * The first field of a node of any type is guaranteed to be the NodeTag.
  * Hence the type of any node can be gotten by casting it to Node. Declaring
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4c5a8a39bf..629a785865 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -121,7 +121,7 @@ typedef struct Query
 
 	QuerySource querySource;	/* where did I come from? */
 
-	uint64		queryId;		/* query identifier (can be set by plugins) */
+	uint64		queryId pg_node_attr(equal_ignore);		/* query identifier (can be set by plugins) */
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 324d92880b..9d0e4068f3 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -227,7 +227,7 @@ struct PlannerInfo
 	 * GEQO.
 	 */
 	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
-	struct HTAB *join_rel_hash; /* optional hashtable for join relations */
+	struct HTAB *join_rel_hash pg_node_attr(readwrite_ignore); /* optional hashtable for join relations */
 
 	/*
 	 * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -329,10 +329,10 @@ struct PlannerInfo
 	List	   *update_colnos;
 
 	/* Fields filled during create_plan() for use in setrefs.c */
-	AttrNumber *grouping_map;	/* for GroupingFunc fixup */
+	AttrNumber *grouping_map pg_node_attr(array_size(update_colnos));	/* for GroupingFunc fixup */
 	List	   *minmax_aggs;	/* List of MinMaxAggInfos */
 
-	MemoryContext planner_cxt;	/* context holding PlannerInfo */
+	MemoryContext planner_cxt pg_node_attr(readwrite_ignore);	/* context holding PlannerInfo */
 
 	Cardinality	total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -369,8 +369,8 @@ struct PlannerInfo
 	List	   *curOuterParams; /* not-yet-assigned NestLoopParams */
 
 	/* These fields are workspace for setrefs.c */
-	bool	   *isAltSubplan;	/* array corresponding to glob->subplans */
-	bool	   *isUsedSubplan;	/* array corresponding to glob->subplans */
+	bool	   *isAltSubplan pg_node_attr(array_size(curOuterParams));	/* array corresponding to glob->subplans */
+	bool	   *isUsedSubplan pg_node_attr(array_size(curOuterParams));	/* array corresponding to glob->subplans */
 
 	/* optional private data for join_search_hook, e.g., GEQO */
 	void	   *join_search_private;
@@ -711,8 +711,8 @@ typedef struct RelOptInfo
 	RTEKind		rtekind;		/* RELATION, SUBQUERY, FUNCTION, etc */
 	AttrNumber	min_attr;		/* smallest attrno of rel (often <0) */
 	AttrNumber	max_attr;		/* largest attrno of rel */
-	Relids	   *attr_needed;	/* array indexed [min_attr .. max_attr] */
-	int32	   *attr_widths;	/* array indexed [min_attr .. max_attr] */
+	Relids	   *attr_needed pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
+	int32	   *attr_widths pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
 	List	   *lateral_vars;	/* LATERAL Vars and PHVs referenced by rel */
 	Relids		lateral_referencers;	/* rels that reference me laterally */
 	List	   *indexlist;		/* list of IndexOptInfo */
@@ -733,13 +733,13 @@ typedef struct RelOptInfo
 	Oid			userid;			/* identifies user to check access as */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
-	struct FdwRoutine *fdwroutine;
+	struct FdwRoutine *fdwroutine pg_node_attr(readwrite_ignore);
 	void	   *fdw_private;
 
 	/* cache space for remembering if we have proven this relation unique */
-	List	   *unique_for_rels;	/* known unique for these other relid
+	List	   *unique_for_rels pg_node_attr(readwrite_ignore);	/* known unique for these other relid
 									 * set(s) */
-	List	   *non_unique_for_rels;	/* known not unique for these set(s) */
+	List	   *non_unique_for_rels pg_node_attr(readwrite_ignore);	/* known not unique for these set(s) */
 
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;	/* RestrictInfo structures (if base rel) */
@@ -837,7 +837,7 @@ struct IndexOptInfo
 
 	Oid			indexoid;		/* OID of the index relation */
 	Oid			reltablespace;	/* tablespace of index (not table) */
-	RelOptInfo *rel;			/* back-link to index's table */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to index's table */
 
 	/* index-size statistics (from pg_class and elsewhere) */
 	BlockNumber pages;			/* number of disk pages in index */
@@ -847,20 +847,20 @@ struct IndexOptInfo
 	/* index descriptor information */
 	int			ncolumns;		/* number of columns in index */
 	int			nkeycolumns;	/* number of key columns in index */
-	int		   *indexkeys;		/* column numbers of index's attributes both
+	int		   *indexkeys pg_node_attr(readwrite_ignore);		/* column numbers of index's attributes both
 								 * key and included columns, or 0 */
-	Oid		   *indexcollations;	/* OIDs of collations of index columns */
-	Oid		   *opfamily;		/* OIDs of operator families for columns */
-	Oid		   *opcintype;		/* OIDs of opclass declared input data types */
-	Oid		   *sortopfamily;	/* OIDs of btree opfamilies, if orderable */
-	bool	   *reverse_sort;	/* is sort order descending? */
-	bool	   *nulls_first;	/* do NULLs come first in the sort order? */
-	bytea	  **opclassoptions; /* opclass-specific options for columns */
-	bool	   *canreturn;		/* which index cols can be returned in an
+	Oid		   *indexcollations pg_node_attr(readwrite_ignore);	/* OIDs of collations of index columns */
+	Oid		   *opfamily pg_node_attr(readwrite_ignore);		/* OIDs of operator families for columns */
+	Oid		   *opcintype pg_node_attr(readwrite_ignore);		/* OIDs of opclass declared input data types */
+	Oid		   *sortopfamily pg_node_attr(readwrite_ignore);	/* OIDs of btree opfamilies, if orderable */
+	bool	   *reverse_sort pg_node_attr(readwrite_ignore);	/* is sort order descending? */
+	bool	   *nulls_first pg_node_attr(readwrite_ignore);	/* do NULLs come first in the sort order? */
+	bytea	  **opclassoptions pg_node_attr(readwrite_ignore); /* opclass-specific options for columns */
+	bool	   *canreturn pg_node_attr(readwrite_ignore);		/* which index cols can be returned in an
 								 * index-only scan? */
 	Oid			relam;			/* OID of the access method (in pg_am) */
 
-	List	   *indexprs;		/* expressions for non-simple index columns */
+	List	   *indexprs pg_node_attr(readwrite_ignore);		/* expressions for non-simple index columns */
 	List	   *indpred;		/* predicate if a partial index, else NIL */
 
 	List	   *indextlist;		/* targetlist representing index columns */
@@ -877,14 +877,14 @@ struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
-	bool		amoptionalkey;	/* can query omit key for the first column? */
-	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
-	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
-	bool		amhasgettuple;	/* does AM have amgettuple interface? */
-	bool		amhasgetbitmap; /* does AM have amgetbitmap interface? */
-	bool		amcanparallel;	/* does AM support parallel scan? */
-	bool		amcanmarkpos;	/* does AM support mark/restore? */
+	bool		amcanorderbyop pg_node_attr(readwrite_ignore); /* does AM support order by operator result? */
+	bool		amoptionalkey pg_node_attr(readwrite_ignore);	/* can query omit key for the first column? */
+	bool		amsearcharray pg_node_attr(readwrite_ignore);	/* can AM handle ScalarArrayOpExpr quals? */
+	bool		amsearchnulls pg_node_attr(readwrite_ignore);	/* can AM search for NULL/NOT NULL entries? */
+	bool		amhasgettuple pg_node_attr(readwrite_ignore);	/* does AM have amgettuple interface? */
+	bool		amhasgetbitmap pg_node_attr(readwrite_ignore); /* does AM have amgetbitmap interface? */
+	bool		amcanparallel pg_node_attr(readwrite_ignore);	/* does AM support parallel scan? */
+	bool		amcanmarkpos pg_node_attr(readwrite_ignore);	/* does AM support mark/restore? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
 };
@@ -905,9 +905,9 @@ typedef struct ForeignKeyOptInfo
 	Index		con_relid;		/* RT index of the referencing table */
 	Index		ref_relid;		/* RT index of the referenced table */
 	int			nkeys;			/* number of columns in the foreign key */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 
 	/* Derived info about whether FK's equality conditions match the query: */
 	int			nmatched_ec;	/* # of FK cols matched by ECs */
@@ -934,7 +934,7 @@ typedef struct StatisticExtInfo
 	NodeTag		type;
 
 	Oid			statOid;		/* OID of the statistics row */
-	RelOptInfo *rel;			/* back-link to statistic's table */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to statistic's table */
 	char		kind;			/* statistics kind of this entry */
 	Bitmapset  *keys;			/* attnums of the columns covered */
 	List	   *exprs;			/* expressions */
@@ -1108,7 +1108,7 @@ typedef struct PathTarget
 {
 	NodeTag		type;
 	List	   *exprs;			/* list of expressions to be computed */
-	Index	   *sortgrouprefs;	/* corresponding sort/group refnos, or 0 */
+	Index	   *sortgrouprefs pg_node_attr(array_size(exprs));	/* corresponding sort/group refnos, or 0 */
 	QualCost	cost;			/* cost of evaluating the expressions */
 	int			width;			/* estimated avg width of result tuples */
 	VolatileFunctionStatus has_volatile_expr;	/* indicates if exprs contain
@@ -1179,10 +1179,10 @@ typedef struct Path
 
 	NodeTag		pathtype;		/* tag identifying scan/join method */
 
-	RelOptInfo *parent;			/* the relation this path can build */
-	PathTarget *pathtarget;		/* list of Vars/Exprs, cost, width */
+	RelOptInfo *parent pg_node_attr(path_hack1);			/* the relation this path can build */
+	PathTarget *pathtarget pg_node_attr(path_hack2);		/* list of Vars/Exprs, cost, width */
 
-	ParamPathInfo *param_info;	/* parameterization info, or NULL if none */
+	ParamPathInfo *param_info pg_node_attr(path_hack3);	/* parameterization info, or NULL if none */
 
 	bool		parallel_aware; /* engage parallel-aware logic? */
 	bool		parallel_safe;	/* OK to use as part of parallel plan? */
@@ -2061,19 +2061,19 @@ typedef struct RestrictInfo
 
 	bool		outerjoin_delayed;	/* true if delayed by lower outer join */
 
-	bool		can_join;		/* see comment above */
+	bool		can_join pg_node_attr(equal_ignore);		/* see comment above */
 
-	bool		pseudoconstant; /* see comment above */
+	bool		pseudoconstant pg_node_attr(equal_ignore); /* see comment above */
 
-	bool		leakproof;		/* true if known to contain no leaked Vars */
+	bool		leakproof pg_node_attr(equal_ignore);		/* true if known to contain no leaked Vars */
 
-	VolatileFunctionStatus has_volatile;	/* to indicate if clause contains
+	VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);	/* to indicate if clause contains
 											 * any volatile functions. */
 
 	Index		security_level; /* see comment above */
 
 	/* The set of relids (varnos) actually referenced in the clause: */
-	Relids		clause_relids;
+	Relids		clause_relids pg_node_attr(equal_ignore);
 
 	/* The set of relids required to evaluate the clause: */
 	Relids		required_relids;
@@ -2085,48 +2085,48 @@ typedef struct RestrictInfo
 	Relids		nullable_relids;
 
 	/* These fields are set for any binary opclause: */
-	Relids		left_relids;	/* relids in left side of clause */
-	Relids		right_relids;	/* relids in right side of clause */
+	Relids		left_relids pg_node_attr(equal_ignore);	/* relids in left side of clause */
+	Relids		right_relids pg_node_attr(equal_ignore);	/* relids in right side of clause */
 
 	/* This field is NULL unless clause is an OR clause: */
-	Expr	   *orclause;		/* modified clause with RestrictInfos */
+	Expr	   *orclause pg_node_attr(equal_ignore);		/* modified clause with RestrictInfos */
 
 	/* This field is NULL unless clause is potentially redundant: */
-	EquivalenceClass *parent_ec;	/* generating EquivalenceClass */
+	EquivalenceClass *parent_ec pg_node_attr(equal_ignore readwrite_ignore);	/* generating EquivalenceClass */
 
 	/* cache space for cost and selectivity */
-	QualCost	eval_cost;		/* eval cost of clause; -1 if not yet set */
-	Selectivity norm_selec;		/* selectivity for "normal" (JOIN_INNER)
+	QualCost	eval_cost pg_node_attr(equal_ignore);		/* eval cost of clause; -1 if not yet set */
+	Selectivity norm_selec pg_node_attr(equal_ignore);		/* selectivity for "normal" (JOIN_INNER)
 								 * semantics; -1 if not yet set; >1 means a
 								 * redundant clause */
-	Selectivity outer_selec;	/* selectivity for outer join semantics; -1 if
+	Selectivity outer_selec pg_node_attr(equal_ignore);	/* selectivity for outer join semantics; -1 if
 								 * not yet set */
 
 	/* valid if clause is mergejoinable, else NIL */
-	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *mergeopfamilies pg_node_attr(equal_ignore);	/* opfamilies containing clause operator */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
-	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
-	EquivalenceClass *right_ec; /* EquivalenceClass containing righthand */
-	EquivalenceMember *left_em; /* EquivalenceMember for lefthand */
-	EquivalenceMember *right_em;	/* EquivalenceMember for righthand */
-	List	   *scansel_cache;	/* list of MergeScanSelCache structs */
+	EquivalenceClass *left_ec pg_node_attr(equal_ignore readwrite_ignore);	/* EquivalenceClass containing lefthand */
+	EquivalenceClass *right_ec pg_node_attr(equal_ignore readwrite_ignore); /* EquivalenceClass containing righthand */
+	EquivalenceMember *left_em pg_node_attr(equal_ignore); /* EquivalenceMember for lefthand */
+	EquivalenceMember *right_em pg_node_attr(equal_ignore);	/* EquivalenceMember for righthand */
+	List	   *scansel_cache pg_node_attr(copy_ignore equal_ignore);	/* list of MergeScanSelCache structs */
 
 	/* transient workspace for use while considering a specific join path */
-	bool		outer_is_left;	/* T = outer var on left, F = on right */
+	bool		outer_is_left pg_node_attr(equal_ignore);	/* T = outer var on left, F = on right */
 
 	/* valid if clause is hashjoinable, else InvalidOid: */
-	Oid			hashjoinoperator;	/* copy of clause operator */
+	Oid			hashjoinoperator pg_node_attr(equal_ignore);	/* copy of clause operator */
 
 	/* cache space for hashclause processing; -1 if not yet set */
-	Selectivity left_bucketsize;	/* avg bucketsize of left side */
-	Selectivity right_bucketsize;	/* avg bucketsize of right side */
-	Selectivity left_mcvfreq;	/* left side's most common val's freq */
-	Selectivity right_mcvfreq;	/* right side's most common val's freq */
+	Selectivity left_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of left side */
+	Selectivity right_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of right side */
+	Selectivity left_mcvfreq pg_node_attr(equal_ignore);	/* left side's most common val's freq */
+	Selectivity right_mcvfreq pg_node_attr(equal_ignore);	/* right side's most common val's freq */
 
 	/* hash equality operators used for memoize nodes, else InvalidOid */
-	Oid			left_hasheqoperator;
-	Oid			right_hasheqoperator;
+	Oid			left_hasheqoperator pg_node_attr(equal_ignore);
+	Oid			right_hasheqoperator pg_node_attr(equal_ignore);
 } RestrictInfo;
 
 /*
@@ -2181,8 +2181,8 @@ typedef struct MergeScanSelCache
 typedef struct PlaceHolderVar
 {
 	Expr		xpr;
-	Expr	   *phexpr;			/* the represented expression */
-	Relids		phrels;			/* base relids syntactically within expr src */
+	Expr	   *phexpr pg_node_attr(equal_ignore);			/* the represented expression */
+	Relids		phrels pg_node_attr(equal_ignore);			/* base relids syntactically within expr src */
 	Index		phid;			/* ID for PHV (unique within planner run) */
 	Index		phlevelsup;		/* > 0 if PHV belongs to outer query */
 } PlaceHolderVar;
@@ -2343,7 +2343,7 @@ typedef struct AppendRelInfo
 	 * child column is dropped or doesn't exist in the parent.
 	 */
 	int			num_child_cols; /* length of array */
-	AttrNumber *parent_colnos;	/* array of parent attnos, or zeroes */
+	AttrNumber *parent_colnos pg_node_attr(array_size(num_child_cols));	/* array of parent attnos, or zeroes */
 
 	/*
 	 * We store the parent table's OID here for inheritance, or InvalidOid for
@@ -2431,7 +2431,7 @@ typedef struct MinMaxAggInfo
 	Oid			aggfnoid;		/* pg_proc Oid of the aggregate */
 	Oid			aggsortop;		/* Oid of its sort operator */
 	Expr	   *target;			/* expression we are aggregating on */
-	PlannerInfo *subroot;		/* modified "root" for planning the subquery */
+	PlannerInfo *subroot pg_node_attr(readwrite_ignore);		/* modified "root" for planning the subquery */
 	Path	   *path;			/* access path for subquery */
 	Cost		pathcost;		/* estimated cost to fetch first row */
 	Param	   *param;			/* param for subplan's output */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index be3c30704a..b772d91e2d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -275,10 +275,10 @@ typedef struct MergeAppend
 	List	   *mergeplans;
 	/* these fields are just like the sort-key info in struct Sort: */
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 	/* Info for run-time subplan pruning; NULL if we're not doing that */
 	struct PartitionPruneInfo *part_prune_info;
 } MergeAppend;
@@ -298,9 +298,9 @@ typedef struct RecursiveUnion
 	/* Remaining fields are zero/null in UNION ALL case */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
-	Oid		   *dupCollations;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 	long		numGroups;		/* estimated number of groups in input */
 } RecursiveUnion;
 
@@ -750,10 +750,10 @@ typedef struct MergeJoin
 	bool		skip_mark_restore;	/* Can we skip mark/restore calls? */
 	List	   *mergeclauses;	/* mergeclauses as expression trees */
 	/* these are arrays, but have the same length as the mergeclauses list: */
-	Oid		   *mergeFamilies;	/* per-clause OIDs of btree opfamilies */
-	Oid		   *mergeCollations;	/* per-clause OIDs of collations */
-	int		   *mergeStrategies;	/* per-clause ordering (ASC or DESC) */
-	bool	   *mergeNullsFirst;	/* per-clause nulls ordering */
+	Oid		   *mergeFamilies pg_node_attr(array_size(mergeclauses));	/* per-clause OIDs of btree opfamilies */
+	Oid		   *mergeCollations pg_node_attr(array_size(mergeclauses));	/* per-clause OIDs of collations */
+	int		   *mergeStrategies pg_node_attr(array_size(mergeclauses));	/* per-clause ordering (ASC or DESC) */
+	bool	   *mergeNullsFirst pg_node_attr(array_size(mergeclauses));	/* per-clause nulls ordering */
 } MergeJoin;
 
 /* ----------------
@@ -793,8 +793,8 @@ typedef struct Memoize
 
 	int			numKeys;		/* size of the two arrays below */
 
-	Oid		   *hashOperators;	/* hash operators for each key */
-	Oid		   *collations;		/* cache keys */
+	Oid		   *hashOperators pg_node_attr(array_size(numKeys));	/* hash operators for each key */
+	Oid		   *collations pg_node_attr(array_size(numKeys));		/* cache keys */
 	List	   *param_exprs;	/* exprs containing parameters */
 	bool		singlerow;		/* true if the cache entry should be marked as
 								 * complete after we store the first tuple in
@@ -815,10 +815,10 @@ typedef struct Sort
 {
 	Plan		plan;
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 } Sort;
 
 /* ----------------
@@ -841,9 +841,9 @@ typedef struct Group
 {
 	Plan		plan;
 	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
-	Oid		   *grpCollations;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 } Group;
 
 /* ---------------
@@ -866,9 +866,9 @@ typedef struct Agg
 	AggStrategy aggstrategy;	/* basic strategy, see nodes.h */
 	AggSplit	aggsplit;		/* agg-splitting mode, see nodes.h */
 	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
-	Oid		   *grpCollations;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 	long		numGroups;		/* estimated number of groups in input */
 	uint64		transitionSpace;	/* for pass-by-ref transition data */
 	Bitmapset  *aggParams;		/* IDs of Params used in Aggref inputs */
@@ -886,13 +886,13 @@ typedef struct WindowAgg
 	Plan		plan;
 	Index		winref;			/* ID referenced by window functions */
 	int			partNumCols;	/* number of columns in partition clause */
-	AttrNumber *partColIdx;		/* their indexes in the target list */
-	Oid		   *partOperators;	/* equality operators for partition columns */
-	Oid		   *partCollations; /* collations for partition columns */
+	AttrNumber *partColIdx pg_node_attr(array_size(partNumCols));		/* their indexes in the target list */
+	Oid		   *partOperators pg_node_attr(array_size(partNumCols));	/* equality operators for partition columns */
+	Oid		   *partCollations pg_node_attr(array_size(partNumCols)); /* collations for partition columns */
 	int			ordNumCols;		/* number of columns in ordering clause */
-	AttrNumber *ordColIdx;		/* their indexes in the target list */
-	Oid		   *ordOperators;	/* equality operators for ordering columns */
-	Oid		   *ordCollations;	/* collations for ordering columns */
+	AttrNumber *ordColIdx pg_node_attr(array_size(ordNumCols));		/* their indexes in the target list */
+	Oid		   *ordOperators pg_node_attr(array_size(ordNumCols));	/* equality operators for ordering columns */
+	Oid		   *ordCollations pg_node_attr(array_size(ordNumCols));	/* collations for ordering columns */
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
@@ -912,9 +912,9 @@ typedef struct Unique
 {
 	Plan		plan;
 	int			numCols;		/* number of columns to check for uniqueness */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+	AttrNumber *uniqColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *uniqOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *uniqCollations pg_node_attr(array_size(numCols)); /* collations for equality comparisons */
 } Unique;
 
 /* ------------
@@ -950,10 +950,10 @@ typedef struct GatherMerge
 	int			rescan_param;	/* ID of Param that signals a rescan, or -1 */
 	/* remaining fields are just like the sort-key info in struct Sort */
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 	Bitmapset  *initParam;		/* param id's of initplans which are referred
 								 * at gather merge or one of it's child node */
 } GatherMerge;
@@ -993,9 +993,9 @@ typedef struct SetOp
 	SetOpStrategy strategy;		/* how to do it, see nodes.h */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
-	Oid		   *dupCollations;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 	AttrNumber	flagColIdx;		/* where is the flag column, if any */
 	int			firstFlag;		/* flag value for first input relation */
 	long		numGroups;		/* estimated number of groups in input */
@@ -1031,9 +1031,9 @@ typedef struct Limit
 	Node	   *limitCount;		/* COUNT parameter, or NULL if none */
 	LimitOption limitOption;	/* limit type */
 	int			uniqNumCols;	/* number of columns to check for similarity  */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+	AttrNumber *uniqColIdx pg_node_attr(array_size(uniqNumCols));		/* their indexes in the target list */
+	Oid		   *uniqOperators pg_node_attr(array_size(uniqNumCols));	/* equality operators to compare with */
+	Oid		   *uniqCollations pg_node_attr(array_size(uniqNumCols)); /* collations for equality comparisons */
 } Limit;
 
 
@@ -1192,9 +1192,9 @@ typedef struct PartitionedRelPruneInfo
 	Bitmapset  *present_parts;	/* Indexes of all partitions which subplans or
 								 * subparts are present for */
 	int			nparts;			/* Length of the following arrays: */
-	int		   *subplan_map;	/* subplan index by partition index, or -1 */
-	int		   *subpart_map;	/* subpart index by partition index, or -1 */
-	Oid		   *relid_map;		/* relation OID by partition index, or 0 */
+	int		   *subplan_map pg_node_attr(array_size(nparts));	/* subplan index by partition index, or -1 */
+	int		   *subpart_map pg_node_attr(array_size(nparts));	/* subpart index by partition index, or -1 */
+	Oid		   *relid_map pg_node_attr(array_size(nparts));		/* relation OID by partition index, or 0 */
 
 	/*
 	 * initial_pruning_steps shows how to prune during executor startup (i.e.,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 433437643e..d8abd98b55 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -63,7 +63,7 @@ typedef enum OnCommitAction
 typedef struct RangeVar
 {
 	NodeTag		type;
-	char	   *catalogname;	/* the catalog (database) name, or NULL */
+	char	   *catalogname pg_node_attr(readwrite_ignore);	/* the catalog (database) name, or NULL */
 	char	   *schemaname;		/* the schema name, or NULL */
 	char	   *relname;		/* the relation/sequence name */
 	bool		inh;			/* expand rel by inheritance? recursively act
@@ -196,8 +196,8 @@ typedef struct Var
 	Index		varlevelsup;	/* for subquery variables referencing outer
 								 * relations; 0 in a normal var, >0 means N
 								 * levels up */
-	Index		varnosyn;		/* syntactic relation index (0 if unknown) */
-	AttrNumber	varattnosyn;	/* syntactic attribute number */
+	Index		varnosyn pg_node_attr(equal_ignore);		/* syntactic relation index (0 if unknown) */
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore);	/* syntactic attribute number */
 	int			location;		/* token location, or -1 if unknown */
 } Var;
 
@@ -324,7 +324,7 @@ typedef struct Aggref
 	Oid			aggtype;		/* type Oid of result of the aggregate */
 	Oid			aggcollid;		/* OID of collation of result */
 	Oid			inputcollid;	/* OID of collation that function should use */
-	Oid			aggtranstype;	/* type Oid of aggregate's transition value */
+	Oid			aggtranstype pg_node_attr(equal_ignore);	/* type Oid of aggregate's transition value */
 	List	   *aggargtypes;	/* type Oids of direct and aggregated args */
 	List	   *aggdirectargs;	/* direct arguments, if an ordered-set agg */
 	List	   *args;			/* aggregated arguments and sort expressions */
@@ -371,8 +371,8 @@ typedef struct GroupingFunc
 	Expr		xpr;
 	List	   *args;			/* arguments, not evaluated but kept for
 								 * benefit of EXPLAIN etc. */
-	List	   *refs;			/* ressortgrouprefs of arguments */
-	List	   *cols;			/* actual column positions set by planner */
+	List	   *refs pg_node_attr(equal_ignore);			/* ressortgrouprefs of arguments */
+	List	   *cols pg_node_attr(equal_ignore);			/* actual column positions set by planner */
 	Index		agglevelsup;	/* same as Aggref.agglevelsup */
 	int			location;		/* token location */
 } GroupingFunc;
@@ -540,7 +540,7 @@ typedef struct OpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of underlying function */
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of underlying function */
 	Oid			opresulttype;	/* PG_TYPE OID of result value */
 	bool		opretset;		/* true if operator returns set */
 	Oid			opcollid;		/* OID of collation of result */
@@ -597,9 +597,9 @@ typedef struct ScalarArrayOpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of comparison function */
-	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
-	Oid			negfuncid;		/* PG_PROC OID of negator of opfuncid function
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of comparison function */
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of hash func or InvalidOid */
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of negator of opfuncid function
 								 * or InvalidOid.  See above */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 427ffabd14..b72ff3794e 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -371,14 +371,14 @@
  * copyObject(), to facilitate catching errors and omissions in
  * copyObject().
  */
-/* #define COPY_PARSE_PLAN_TREES */
+#define COPY_PARSE_PLAN_TREES
 
 /*
  * Define this to force all parse and plan trees to be passed through
  * outfuncs.c/readfuncs.c, to facilitate catching errors and omissions in
  * those modules.
  */
-/* #define WRITE_READ_PARSE_PLAN_TREES */
+#define WRITE_READ_PARSE_PLAN_TREES
 
 /*
  * Define this to force all raw parse trees for DML statements to be scanned
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 31281279cf..20fba84a99 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -273,9 +273,9 @@ typedef struct ForeignKeyCacheInfo
 	Oid			confrelid;		/* relation referenced by the foreign key */
 	int			nkeys;			/* number of columns in the foreign key */
 	/* these arrays each have nkeys valid entries: */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 } ForeignKeyCacheInfo;
 
 
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 2c8cd521e9..290197ed4d 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -832,6 +832,52 @@ EOF
 		close($chs);
 	}
 
+	if (IsNewer('src/backend/nodes/node-support-stamp',
+		'src/backend/nodes/gen_node_support.pl'))
+	{
+		# XXX duplicates src/backend/nodes/Makefile
+
+		my @node_headers = qw(
+			nodes/nodes.h
+			nodes/execnodes.h
+			nodes/plannodes.h
+			nodes/primnodes.h
+			nodes/pathnodes.h
+			nodes/extensible.h
+			nodes/parsenodes.h
+			nodes/replnodes.h
+			nodes/value.h
+			commands/trigger.h
+			commands/event_trigger.h
+			foreign/fdwapi.h
+			access/amapi.h
+			access/tableam.h
+			access/tsmapi.h
+			utils/rel.h
+			nodes/supportnodes.h
+			executor/tuptable.h
+			nodes/lockoptions.h
+			access/sdir.h
+		);
+
+		chdir('src/backend/nodes');
+
+		my @node_files = map { "../../../src/include/$_" } @node_headers;
+
+		system("perl gen_node_support.pl @node_files");
+		open(my $f, '>', 'node-support-stamp') || confess "Could not touch node-support-stamp";
+		close($f);
+		chdir('../../..');
+	}
+
+	if (IsNewer(
+			'src/include/nodes/nodetags.h',
+			'src/backend/nodes/nodetags.h'))
+	{
+		copyFile('src/backend/nodes/nodetags.h',
+			'src/include/nodes/nodetags.h');
+	}
+
 	open(my $o, '>', "doc/src/sgml/version.sgml")
 	  || croak "Could not write to version.sgml\n";
 	print $o <<EOF;

base-commit: cab5b9ab2c066ba904f13de2681872dcda31e207
-- 
2.34.1

#19Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Peter Eisentraut (#18)
1 attachment(s)
Re: automatically generating node support functions

Rebased patch to resolve some merge conflicts

Show quoted text

On 29.12.21 12:08, Peter Eisentraut wrote:

On 12.10.21 15:52, Andrew Dunstan wrote:

I haven't been through the whole thing, but I did notice this: the
comment stripping code looks rather fragile. I think it would blow up if
there were a continuation line not starting with  qr/\s*\*/. It's a lot
simpler and more robust to do this if you slurp the file in whole.
Here's what we do in the buildfarm code:

     my $src = file_contents($_);
     # strip C comments
     # We used to use the recipe in perlfaq6 but there is actually no
point.
     # We don't need to keep the quoted string values anyway, and
     # on some platforms the complex regex causes perl to barf and crash.
     $src =~ s{/\*.*?\*/}{}gs;

After you've done that splitting it into lines is pretty simple.

Here is an updated patch, with some general rebasing, and the above
improvement.  It now also generates #include lines necessary in
copyfuncs etc. to pull in all the node types it operates on.

Further, I have looked more into the "metadata" approach discussed in
[0].  It's pretty easy to generate that kind of output from the data
structures my script produces.  You just loop over all the node types
and print stuff and keep a few counters.  I don't plan to work on that
at this time, but I just wanted to point out that if people wanted to
move into that direction, my patch wouldn't be in the way.

[0]:
/messages/by-id/20190828234136.fk2ndqtld3onfrrp@alap3.anarazel.de

Attachments:

v4-0001-Automatically-generate-node-support-functions.patchtext/plain; charset=UTF-8; name=v4-0001-Automatically-generate-node-support-functions.patchDownload
From d99c818447ee4a68b762fb8a8c645ee6ef051ff9 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 24 Jan 2022 16:13:21 +0100
Subject: [PATCH v4] 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.inc1.c and copyfuncs.inc2.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.  When we do
that, some code comments should probably be preserved elsewhere, so
that will need another pass of consideration.

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

Subtyping (TidScan -> Scan) is supported.

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

Discussion: https://www.postgresql.org/message-id/flat/c1097590-a6a4-486a-64b1-e1f9cc0533ce%40enterprisedb.com
---
 src/backend/Makefile                  |   8 +-
 src/backend/nodes/.gitignore          |   3 +
 src/backend/nodes/Makefile            |  46 ++
 src/backend/nodes/copyfuncs.c         |  19 +-
 src/backend/nodes/equalfuncs.c        |  22 +-
 src/backend/nodes/gen_node_support.pl | 660 ++++++++++++++++++++++++++
 src/backend/nodes/outfuncs.c          |  30 +-
 src/backend/nodes/readfuncs.c         |  23 +-
 src/include/nodes/.gitignore          |   2 +
 src/include/nodes/nodes.h             |   8 +
 src/include/nodes/parsenodes.h        |   2 +-
 src/include/nodes/pathnodes.h         | 136 +++---
 src/include/nodes/plannodes.h         |  90 ++--
 src/include/nodes/primnodes.h         |  20 +-
 src/include/pg_config_manual.h        |   4 +-
 src/include/utils/rel.h               |   6 +-
 src/tools/msvc/Solution.pm            |  46 ++
 17 files changed, 977 insertions(+), 148 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 add9560be4..f9ce7cefcc 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -143,11 +143,15 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
 submake-catalog-headers:
 	$(MAKE) -C catalog distprep generated-header-symlinks
 
+# run this unconditionally to avoid needing to know its dependencies here:
+submake-nodes-headers:
+	$(MAKE) -C nodes distprep generated-header-symlinks
+
 # run this unconditionally to avoid needing to know its dependencies here:
 submake-utils-headers:
 	$(MAKE) -C utils distprep generated-header-symlinks
 
-.PHONY: submake-catalog-headers submake-utils-headers
+.PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 # Make symlinks for these headers in the include directory. That way
 # we can cut down on the -I options. Also, a symlink is automatically
@@ -162,7 +166,7 @@ submake-utils-headers:
 
 .PHONY: generated-headers
 
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..2a79ee6ed8
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,3 @@
+/node-support-stamp
+/nodetags.h
+/*funcs.inc?.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..56cddc06da 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,49 @@ OBJS = \
 	value.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+node_headers = \
+	nodes/nodes.h \
+	nodes/execnodes.h \
+	nodes/plannodes.h \
+	nodes/primnodes.h \
+	nodes/pathnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.h \
+	nodes/value.h \
+	commands/trigger.h \
+	commands/event_trigger.h \
+	foreign/fdwapi.h \
+	access/amapi.h \
+	access/tableam.h \
+	access/tsmapi.h \
+	utils/rel.h \
+	nodes/supportnodes.h \
+	executor/tuptable.h \
+	nodes/lockoptions.h \
+	access/sdir.h
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-support-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+node-support-stamp: gen_node_support.pl $(addprefix $(top_srcdir)/src/include/,$(node_headers))
+	$(PERL) $^
+	touch $@
+
+$(top_builddir)/src/include/nodes/header-stamp: node-support-stamp
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	cd '$(dir $@)' && for file in nodetags.h; do \
+	  rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \
+	done
+	touch $@
+
+maintainer-clean: clean
+	rm -f node-support-stamp *funcs.inc?.c nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90b5da51c9..c47c0aca9f 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.inc1.c"
+
+#ifdef OBSOLETE
 /* ****************************************************************
  *					 plannodes.h copy functions
  * ****************************************************************
@@ -1457,6 +1456,7 @@ _copyVar(const Var *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * _copyConst
@@ -1496,6 +1496,7 @@ _copyConst(const Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -2731,6 +2732,7 @@ _copyParamRef(const ParamRef *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -2771,6 +2773,7 @@ _copyA_Const(const A_Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -4906,6 +4909,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *					extensible.h copy functions
@@ -4928,6 +4932,7 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /* ****************************************************************
  *					value.h copy functions
  * ****************************************************************
@@ -4998,6 +5003,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5018,6 +5024,8 @@ copyObjectImpl(const void *from)
 
 	switch (nodeTag(from))
 	{
+#include "copyfuncs.inc2.c"
+#ifdef OBSOLETE
 			/*
 			 * PLAN NODES
 			 */
@@ -5378,6 +5386,7 @@ copyObjectImpl(const void *from)
 		case T_BitString:
 			retval = _copyBitString(from);
 			break;
+#endif /*OBSOLETE*/
 
 			/*
 			 * LIST NODES
@@ -5395,6 +5404,7 @@ copyObjectImpl(const void *from)
 			retval = list_copy(from);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -5934,6 +5944,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 06345da3ba..152274a38c 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.inc1.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from primnodes.h
  */
@@ -185,6 +183,7 @@ _equalVar(const Var *a, const Var *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -207,6 +206,7 @@ _equalConst(const Const *a, const Const *b)
 						a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -946,6 +946,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -967,6 +968,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const ExtensibleNode *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2432,6 +2434,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2449,6 +2452,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3058,6 +3062,7 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3118,6 +3123,7 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from value.h
  */
@@ -3161,6 +3167,7 @@ _equalBitString(const BitString *a, const BitString *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * equal
@@ -3191,6 +3198,8 @@ equal(const void *a, const void *b)
 
 	switch (nodeTag(a))
 	{
+#include "equalfuncs.inc2.c"
+#ifdef OBSOLETE
 			/*
 			 * PRIMITIVE NODES
 			 */
@@ -3369,6 +3378,7 @@ equal(const void *a, const void *b)
 		case T_PlaceHolderInfo:
 			retval = _equalPlaceHolderInfo(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		case T_List:
 		case T_IntList:
@@ -3376,6 +3386,7 @@ equal(const void *a, const void *b)
 			retval = _equalList(a, b);
 			break;
 
+#ifdef OBSOLETE
 		case T_Integer:
 			retval = _equalInteger(a, b);
 			break;
@@ -3924,6 +3935,7 @@ equal(const void *a, const void *b)
 		case T_PublicationTable:
 			retval = _equalPublicationTable(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..deb1023b2d
--- /dev/null
+++ b/src/backend/nodes/gen_node_support.pl
@@ -0,0 +1,660 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# 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
+
+
+sub elem
+{
+	my $x = shift;
+	return grep { $_ eq $x } @_;
+}
+
+
+my @node_types = qw(Node);
+my %node_type_info;
+
+my @no_copy;
+my @no_read_write;
+
+my @scalar_types = qw(
+	bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+	AclMode AttrNumber Cardinality Cost Index Oid Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr
+);
+
+my @enum_types;
+
+# For abstract types we track their fields, so that subtypes can use
+# them, but we don't emit a node tag, so you can't instantiate them.
+my @abstract_types = qw(
+	Node Expr
+	BufferHeapTupleTableSlot HeapTupleTableSlot MinimalTupleTableSlot VirtualTupleTableSlot
+	JoinPath
+	PartitionPruneStep
+);
+
+# Special cases that either don't have their own struct or the struct
+# is not in a header file.  We just generate node tags for them, but
+# they otherwise don't participate in node support.
+my @extra_tags = qw(
+	IntList OidList
+	AllocSetContext GenerationContext SlabContext
+	TIDBitmap
+	WindowObjectData
+);
+
+# This is a regular node, but we skip parsing it from its header file
+# since we won't use its internal structure here anyway.
+push @node_types, qw(List);
+
+# pathnodes.h exceptions
+push @no_copy, qw(
+	RelOptInfo IndexOptInfo Path PlannerGlobal EquivalenceClass EquivalenceMember ForeignKeyOptInfo
+	GroupingSetData IncrementalSortPath IndexClause MinMaxAggInfo PathTarget PlannerInfo PlannerParamItem
+	ParamPathInfo RollupData RowIdentityVarInfo StatisticExtInfo
+);
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember* QualCost);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation);
+
+
+## read input
+
+foreach my $infile (@ARGV)
+{
+	my $in_struct;
+	my $subline;
+	my $is_node_struct;
+	my $supertype;
+	my $supertype_field;
+
+	my @my_fields;
+	my %my_field_types;
+	my %my_field_attrs;
+
+	open my $ifh, '<', $infile or die "could not open \"$infile\": $!";
+
+	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)/;
+
+		if ($in_struct)
+		{
+			$subline++;
+
+			# first line should have opening brace
+			if ($subline == 1)
+			{
+				$is_node_struct = 0;
+				$supertype = undef;
+				next if $line eq '{';
+				die;
+			}
+			# second line should have node tag or supertype
+			elsif ($subline == 2)
+			{
+				if ($line =~ /^\s*NodeTag\s+type;/)
+				{
+					$is_node_struct = 1;
+					next;
+				}
+				elsif ($line =~ /\s*(\w+)\s+(\w+);/ and elem $1, @node_types)
+				{
+					$is_node_struct = 1;
+					$supertype = $1;
+					$supertype_field = $2;
+					next;
+				}
+			}
+
+			# end of struct
+			if ($line =~ /^\}\s*$in_struct;$/ || $line =~ /^\};$/)
+			{
+				if ($is_node_struct)
+				{
+					push @node_types, $in_struct;
+					my @f = @my_fields;
+					my %ft = %my_field_types;
+					my %fa = %my_field_attrs;
+					if ($supertype)
+					{
+						my @superfields;
+						foreach my $sf (@{$node_type_info{$supertype}->{fields}})
+						{
+							my $fn = "${supertype_field}.$sf";
+							push @superfields, $fn;
+							$ft{$fn} = $node_type_info{$supertype}->{field_types}{$sf};
+							$fa{$fn} = $node_type_info{$supertype}->{field_attrs}{$sf};
+							$fa{$fn} =~ s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/ if $fa{$fn};
+						}
+						unshift @f, @superfields;
+					}
+					$node_type_info{$in_struct}->{fields} = \@f;
+					$node_type_info{$in_struct}->{field_types} = \%ft;
+					$node_type_info{$in_struct}->{field_attrs} = \%fa;
+
+					if (elem basename($infile),
+						qw(execnodes.h trigger.h event_trigger.h amapi.h tableam.h
+							tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h))
+					{
+						push @no_copy, $in_struct;
+						push @no_read_write, $in_struct;
+					}
+
+					if ($supertype && ($supertype eq 'Path' || $supertype eq 'JoinPath'))
+					{
+						push @no_copy, $in_struct;
+					}
+				}
+
+				# start new cycle
+				$in_struct = undef;
+				@my_fields = ();
+				%my_field_types = ();
+				%my_field_attrs = ();
+			}
+			# normal struct field
+			elsif ($line =~ /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w() ]*)\))?;/)
+			{
+				if ($is_node_struct)
+				{
+					my $type = $1;
+					my $name = $2;
+					my $array_size = $3;
+					my $attr = $4;
+
+					$type =~ s/^const\s*//;
+					$type =~ s/\s*$//;
+					$type =~ s/\s+\*$/*/;
+					die if $type eq '';
+					$type = $type . $array_size if $array_size;
+					push @my_fields, $name;
+					$my_field_types{$name} = $type;
+					$my_field_attrs{$name} = $attr;
+				}
+			}
+			else
+			{
+				if ($is_node_struct)
+				{
+					#warn "$infile:$.: could not parse \"$line\"\n";
+				}
+			}
+		}
+		# not in a struct
+		else
+		{
+			# start of a struct?
+			if ($line =~ /^(?:typedef )?struct (\w+)(\s*\/\*.*)?$/ && $1 ne 'Node')
+			{
+				$in_struct = $1;
+				$subline = 0;
+			}
+			# one node type typedef'ed directly from another
+			elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types)
+			{
+				my $alias_of = $1;
+				my $n = $2;
+
+				push @node_types, $n;
+				my @f = @{$node_type_info{$alias_of}->{fields}};
+				my %ft = %{$node_type_info{$alias_of}->{field_types}};
+				my %fa = %{$node_type_info{$alias_of}->{field_attrs}};
+				$node_type_info{$n}->{fields} = \@f;
+				$node_type_info{$n}->{field_types} = \%ft;
+				$node_type_info{$n}->{field_attrs} = \%fa;
+			}
+			# collect enum names
+			elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/)
+			{
+				push @enum_types, $1;
+			}
+		}
+	}
+
+	if ($in_struct)
+	{
+		die "runaway \"$in_struct\" in file \"$infile\"\n";
+	}
+
+	close $ifh;
+} # for each file
+
+
+## write output
+
+my $tmpext  = ".tmp$$";
+
+# nodetags.h
+
+open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
+
+my $i = 1;
+foreach my $n (@node_types,@extra_tags)
+{
+	next if elem $n, @abstract_types;
+	print $nt "\tT_${n} = $i,\n";
+	$i++;
+}
+
+close $nt;
+
+
+# #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 $cf, '>', 'copyfuncs.inc1.c' . $tmpext or die $!;
+open my $ef, '>', 'equalfuncs.inc1.c' . $tmpext or die $!;
+open my $cf2, '>', 'copyfuncs.inc2.c' . $tmpext or die $!;
+open my $ef2, '>', 'equalfuncs.inc2.c' . $tmpext or die $!;
+
+print $cf $node_includes;
+print $ef $node_includes;
+
+my @custom_copy = qw(A_Const Const ExtensibleNode);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_copy;
+	next if $n eq 'List';
+
+	print $cf2 "
+\t\tcase T_${n}:
+\t\t\tretval = _copy${n}(from);
+\t\t\tbreak;";
+
+	print $ef2 "
+\t\tcase T_${n}:
+\t\t\tretval = _equal${n}(a, b);
+\t\t\tbreak;";
+
+	next if elem $n, @custom_copy;
+
+	print $cf "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+";
+
+	print $ef "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+";
+
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $copy_ignore = ($a =~ /\bcopy_ignore\b/);
+		my $equal_ignore = ($a =~ /\bequal_ignore\b/);
+		if ($t eq 'char*')
+		{
+			print $cf "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_STRING_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $cf "\tCOPY_BITMAPSET_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_BITMAPSET_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $cf "\tCOPY_LOCATION_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_LOCATION_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif (elem $t, @scalar_types or elem $t, @enum_types)
+		{
+			print $cf "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			if ($a =~ /\bequal_ignore_if_zero\b/)
+			{
+				print $ef "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
+			}
+			else
+			{
+				print $ef "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+			}
+		}
+		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
+		{
+			my $tt = $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $cf "\tCOPY_POINTER_FIELD($f, list_length(from->$array_size_field) * sizeof($tt));\n" unless $copy_ignore;
+				print $ef "\tCOMPARE_POINTER_FIELD($f, list_length(a->$array_size_field) * sizeof($tt));\n" unless $equal_ignore;
+			}
+			else
+			{
+				print $cf "\tCOPY_POINTER_FIELD($f, from->$array_size_field * sizeof($tt));\n" unless $copy_ignore;
+				print $ef "\tCOMPARE_POINTER_FIELD($f, a->$array_size_field * sizeof($tt));\n" unless $equal_ignore;
+			}
+		}
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $cf "\tCOPY_NODE_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t =~ /\w+\[/)
+		{
+			print $cf "\tCOPY_ARRAY_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_ARRAY_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $cf "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			print $ef "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $cf "
+\treturn newnode;
+}
+";
+	print $ef "
+\treturn true;
+}
+";
+}
+
+close $cf;
+close $ef;
+close $cf2;
+close $ef2;
+
+
+# outfuncs.c, readfuncs.c
+
+open my $of, '>', 'outfuncs.inc1.c' . $tmpext or die $!;
+open my $rf, '>', 'readfuncs.inc1.c' . $tmpext or die $!;
+open my $of2, '>', 'outfuncs.inc2.c' . $tmpext or die $!;
+open my $rf2, '>', 'readfuncs.inc2.c' . $tmpext or die $!;
+
+print $of $node_includes;
+print $rf $node_includes;
+
+my @custom_readwrite = qw(A_Const A_Expr BoolExpr Const Constraint ExtensibleNode Query RangeTblEntry);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_read_write;
+	next if $n eq 'List';
+	next if elem $n, qw(BitString Boolean Float Integer String);
+
+	# XXX For now, skip all "Stmt"s except that ones that were there before.
+	if ($n =~ /Stmt$/)
+	{
+		my @keep = qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt ImportForeignSchemaStmt IndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt);
+		next unless elem $n, @keep;
+	}
+
+	# XXX Also skip read support for those that didn't have it before.
+	my $no_read = ($n eq 'A_Star' || $n eq 'A_Const' || $n eq 'A_Expr' || $n eq 'Constraint' || $n =~ /Path$/ || $n eq 'ForeignKeyCacheInfo' || $n eq 'ForeignKeyOptInfo' || $n eq 'PathTarget');
+
+	my $N = uc $n;
+	$N =~ s/_//g;
+
+	print $of2 "\t\t\tcase T_${n}:\n".
+	  "\t\t\t\t_out${n}(str, obj);\n".
+	  "\t\t\t\tbreak;\n";
+
+	print $rf2 "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+
+	next if elem $n, @custom_readwrite;
+
+	print $of "
+static void
+_out${n}(StringInfo str, const $n *node)
+{
+\tWRITE_NODE_TYPE(\"$N\");
+
+";
+
+	print $rf "
+static $n *
+_read${n}(void)
+{
+\tREAD_LOCALS($n);
+
+" unless $no_read;
+
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $readwrite_ignore = ($a =~ /\breadwrite_ignore\b/);
+		next if $readwrite_ignore;
+
+		# XXX Previously, for subtyping, only the leaf field name is
+		# used. Ponder whether we want to keep it that way.
+
+		if ($t eq 'bool')
+		{
+			print $of "\tWRITE_BOOL_FIELD($f);\n";
+			print $rf "\tREAD_BOOL_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $of "\tWRITE_LOCATION_FIELD($f);\n";
+			print $rf "\tREAD_LOCATION_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' || $t eq 'int32' || $t eq 'AttrNumber' || $t eq 'StrategyNumber')
+		{
+			print $of "\tWRITE_INT_FIELD($f);\n";
+			print $rf "\tREAD_INT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint32' || $t eq 'bits32' || $t eq 'AclMode' || $t eq 'BlockNumber' || $t eq 'Index' || $t eq 'SubTransactionId')
+		{
+			print $of "\tWRITE_UINT_FIELD($f);\n";
+			print $rf "\tREAD_UINT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint64')
+		{
+			print $of "\tWRITE_UINT64_FIELD($f);\n";
+			print $rf "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Oid')
+		{
+			print $of "\tWRITE_OID_FIELD($f);\n";
+			print $rf "\tREAD_OID_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'long')
+		{
+			print $of "\tWRITE_LONG_FIELD($f);\n";
+			print $rf "\tREAD_LONG_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char')
+		{
+			print $of "\tWRITE_CHAR_FIELD($f);\n";
+			print $rf "\tREAD_CHAR_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'double')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.6f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cardinality')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.0f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cost')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.2f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'QualCost')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f.startup, \"%.2f\");\n";
+			print $of "\tWRITE_FLOAT_FIELD($f.per_tuple, \"%.2f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read;
+			print $rf "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
+		}
+		elsif ($t eq 'Selectivity')
+		{
+			print $of "\tWRITE_FLOAT_FIELD($f, \"%.4f\");\n";
+			print $rf "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $of "\tWRITE_STRING_FIELD($f);\n";
+			print $rf "\tREAD_STRING_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $of "\tWRITE_BITMAPSET_FIELD($f);\n";
+			print $rf "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
+		}
+		elsif (elem $t, @enum_types)
+		{
+			print $of "\tWRITE_ENUM_FIELD($f, $t);\n";
+			print $rf "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read;
+		}
+		elsif ($t =~ /(\w+)(\*|\[)/ and elem $1, @scalar_types)
+		{
+			my $tt = uc $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $of "\tWRITE_${tt}_ARRAY($f, list_length(node->$array_size_field));\n";
+				print $rf "\tREAD_${tt}_ARRAY($f, list_length(local_node->$array_size_field));\n" unless $no_read;
+			}
+			else
+			{
+				print $of "\tWRITE_${tt}_ARRAY($f, node->$array_size_field);\n";
+				print $rf "\tREAD_${tt}_ARRAY($f, local_node->$array_size_field);\n" unless $no_read;
+			}
+		}
+		elsif ($t eq 'RelOptInfo*' && $a eq 'path_hack1')
+		{
+			print $of "\tappendStringInfoString(str, \" :parent_relids \");\n".
+			  "\toutBitmapset(str, node->$f->relids);\n";
+		}
+		elsif ($t eq 'PathTarget*' && $a eq 'path_hack2')
+		{
+			(my $f2 = $f) =~ s/pathtarget/parent/;
+			print $of "\tif (node->$f != node->$f2->reltarget)\n".
+			  "\t\tWRITE_NODE_FIELD($f);\n";
+		}
+		elsif ($t eq 'ParamPathInfo*' && $a eq 'path_hack3')
+		{
+			print $of "\tif (node->$f)\n".
+			  "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
+			  "\telse\n".
+			  "\t\toutBitmapset(str, NULL);\n";
+		}
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $of "\tWRITE_NODE_FIELD($f);\n";
+			print $rf "\tREAD_NODE_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $of q{
+	appendStringInfoString(str, " :methods ");
+	outToken(str, node->methods->CustomName);
+};
+			print $rf q!
+	{
+		/* Lookup CustomScanMethods by CustomName */
+		char	   *custom_name;
+		const CustomScanMethods *methods;
+		token = pg_strtok(&length); /* skip methods: */
+		token = pg_strtok(&length); /* CustomName */
+		custom_name = nullable_string(token, length);
+		methods = GetCustomScanMethods(custom_name, false);
+		local_node->methods = methods;
+	}
+! unless $no_read;
+		}
+		elsif ($t eq 'ParamListInfo' || $t =~ /PartitionBoundInfoData/ || $t eq 'PartitionDirectory' || $t eq 'PartitionScheme' || $t eq 'void*' || $t =~ /\*\*$/)
+		{
+			# ignore
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $of "}
+";
+	print $rf "
+\tREAD_DONE();
+}
+" unless $no_read;
+}
+
+close $of;
+close $rf;
+close $of2;
+close $rf2;
+
+
+foreach my $file (qw(nodetags.h copyfuncs.inc1.c copyfuncs.inc2.c equalfuncs.inc1.c equalfuncs.inc2.c outfuncs.inc1.c outfuncs.inc2.c readfuncs.inc1.c readfuncs.inc2.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2b0236937a..586316e2e2 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -31,11 +31,10 @@
 
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
-#include "nodes/plannodes.h"
+#include "nodes/bitmapset.h"
+#include "nodes/nodes.h"
+#include "nodes/pg_list.h"
 #include "utils/datum.h"
-#include "utils/rel.h"
 
 static void outChar(StringInfo str, char c);
 
@@ -295,6 +294,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
 }
 
 
+#include "outfuncs.inc1.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from plannodes.h
  */
@@ -1136,6 +1138,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)
@@ -1157,6 +1160,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)
 {
@@ -1327,6 +1331,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)
@@ -1355,6 +1360,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2675,6 +2681,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
 	WRITE_NODE_FIELD(item);
 	WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2697,6 +2704,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -3029,6 +3037,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)
@@ -3101,6 +3110,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3239,6 +3249,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)
@@ -3319,6 +3330,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3342,6 +3354,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)
@@ -3460,6 +3473,7 @@ _outBitString(StringInfo str, const BitString *node)
 	appendStringInfoString(str, node->bsval);
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3491,6 +3505,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)
@@ -3507,6 +3522,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)
 {
@@ -3651,6 +3667,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)
@@ -3772,6 +3789,7 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -3832,6 +3850,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -3863,6 +3882,8 @@ outNode(StringInfo str, const void *obj)
 		appendStringInfoChar(str, '{');
 		switch (nodeTag(obj))
 		{
+#include "outfuncs.inc2.c"
+#ifdef OBSOLETE
 			case T_PlannedStmt:
 				_outPlannedStmt(str, obj);
 				break;
@@ -4535,6 +4556,7 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionRangeDatum:
 				_outPartitionRangeDatum(str, obj);
 				break;
+#endif /*OBSOLETE*/
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3f68f7c18d..8afccd2590 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.inc1.c"
+
 /*
  * _readQuery
  */
@@ -289,6 +289,7 @@ _readQuery(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readNotifyStmt
  */
@@ -587,6 +588,7 @@ _readVar(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readConst
@@ -613,6 +615,7 @@ _readConst(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readParam
  */
@@ -838,6 +841,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readBoolExpr
@@ -865,6 +869,7 @@ _readBoolExpr(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readSubLink
  */
@@ -1419,6 +1424,7 @@ _readAppendRelInfo(void)
 /*
  *	Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1514,6 +1520,7 @@ _readRangeTblEntry(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2637,6 +2644,7 @@ _readAlternativeSubPlan(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2668,6 +2676,7 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2702,6 +2711,7 @@ _readPartitionRangeDatum(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2726,7 +2736,11 @@ parseNodeString(void)
 #define MATCH(tokname, namelen) \
 	(length == namelen && memcmp(token, tokname, namelen) == 0)
 
-	if (MATCH("QUERY", 5))
+	if (false)
+		;
+#include "readfuncs.inc2.c"
+#ifdef OBSOLETE
+	else if (MATCH("QUERY", 5))
 		return_value = _readQuery();
 	else if (MATCH("WITHCHECKOPTION", 15))
 		return_value = _readWithCheckOption();
@@ -2974,6 +2988,7 @@ parseNodeString(void)
 		return_value = _readPartitionBoundSpec();
 	else if (MATCH("PARTITIONRANGEDATUM", 19))
 		return_value = _readPartitionRangeDatum();
+#endif /*OBSOLETE*/
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/include/nodes/.gitignore b/src/include/nodes/.gitignore
new file mode 100644
index 0000000000..99fb1d3787
--- /dev/null
+++ b/src/include/nodes/.gitignore
@@ -0,0 +1,2 @@
+/nodetags.h
+/header-stamp
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f9ddafd345..ce0aca9764 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -27,6 +27,8 @@ typedef enum NodeTag
 {
 	T_Invalid = 0,
 
+#include "nodes/nodetags.h"
+#ifdef OBSOLETE
 	/*
 	 * TAGS FOR EXECUTOR NODES (execnodes.h)
 	 */
@@ -528,8 +530,14 @@ typedef enum NodeTag
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * used in node definitions to set extra information for gen_node_support.pl
+ */
+#define pg_node_attr(x)
+
 /*
  * The first field of a node of any type is guaranteed to be the NodeTag.
  * Hence the type of any node can be gotten by casting it to Node. Declaring
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3e9bdc781f..4249fff481 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -121,7 +121,7 @@ typedef struct Query
 
 	QuerySource querySource;	/* where did I come from? */
 
-	uint64		queryId;		/* query identifier (can be set by plugins) */
+	uint64		queryId pg_node_attr(equal_ignore);		/* query identifier (can be set by plugins) */
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1f3845b3fe..e16dd43394 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -227,7 +227,7 @@ struct PlannerInfo
 	 * GEQO.
 	 */
 	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
-	struct HTAB *join_rel_hash; /* optional hashtable for join relations */
+	struct HTAB *join_rel_hash pg_node_attr(readwrite_ignore); /* optional hashtable for join relations */
 
 	/*
 	 * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -329,10 +329,10 @@ struct PlannerInfo
 	List	   *update_colnos;
 
 	/* Fields filled during create_plan() for use in setrefs.c */
-	AttrNumber *grouping_map;	/* for GroupingFunc fixup */
+	AttrNumber *grouping_map pg_node_attr(array_size(update_colnos));	/* for GroupingFunc fixup */
 	List	   *minmax_aggs;	/* List of MinMaxAggInfos */
 
-	MemoryContext planner_cxt;	/* context holding PlannerInfo */
+	MemoryContext planner_cxt pg_node_attr(readwrite_ignore);	/* context holding PlannerInfo */
 
 	Cardinality	total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -369,8 +369,8 @@ struct PlannerInfo
 	List	   *curOuterParams; /* not-yet-assigned NestLoopParams */
 
 	/* These fields are workspace for setrefs.c */
-	bool	   *isAltSubplan;	/* array corresponding to glob->subplans */
-	bool	   *isUsedSubplan;	/* array corresponding to glob->subplans */
+	bool	   *isAltSubplan pg_node_attr(array_size(curOuterParams));	/* array corresponding to glob->subplans */
+	bool	   *isUsedSubplan pg_node_attr(array_size(curOuterParams));	/* array corresponding to glob->subplans */
 
 	/* optional private data for join_search_hook, e.g., GEQO */
 	void	   *join_search_private;
@@ -711,8 +711,8 @@ typedef struct RelOptInfo
 	RTEKind		rtekind;		/* RELATION, SUBQUERY, FUNCTION, etc */
 	AttrNumber	min_attr;		/* smallest attrno of rel (often <0) */
 	AttrNumber	max_attr;		/* largest attrno of rel */
-	Relids	   *attr_needed;	/* array indexed [min_attr .. max_attr] */
-	int32	   *attr_widths;	/* array indexed [min_attr .. max_attr] */
+	Relids	   *attr_needed pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
+	int32	   *attr_widths pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
 	List	   *lateral_vars;	/* LATERAL Vars and PHVs referenced by rel */
 	Relids		lateral_referencers;	/* rels that reference me laterally */
 	List	   *indexlist;		/* list of IndexOptInfo */
@@ -733,13 +733,13 @@ typedef struct RelOptInfo
 	Oid			userid;			/* identifies user to check access as */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
-	struct FdwRoutine *fdwroutine;
+	struct FdwRoutine *fdwroutine pg_node_attr(readwrite_ignore);
 	void	   *fdw_private;
 
 	/* cache space for remembering if we have proven this relation unique */
-	List	   *unique_for_rels;	/* known unique for these other relid
+	List	   *unique_for_rels pg_node_attr(readwrite_ignore);	/* known unique for these other relid
 									 * set(s) */
-	List	   *non_unique_for_rels;	/* known not unique for these set(s) */
+	List	   *non_unique_for_rels pg_node_attr(readwrite_ignore);	/* known not unique for these set(s) */
 
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;	/* RestrictInfo structures (if base rel) */
@@ -837,7 +837,7 @@ struct IndexOptInfo
 
 	Oid			indexoid;		/* OID of the index relation */
 	Oid			reltablespace;	/* tablespace of index (not table) */
-	RelOptInfo *rel;			/* back-link to index's table */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to index's table */
 
 	/* index-size statistics (from pg_class and elsewhere) */
 	BlockNumber pages;			/* number of disk pages in index */
@@ -847,20 +847,20 @@ struct IndexOptInfo
 	/* index descriptor information */
 	int			ncolumns;		/* number of columns in index */
 	int			nkeycolumns;	/* number of key columns in index */
-	int		   *indexkeys;		/* column numbers of index's attributes both
+	int		   *indexkeys pg_node_attr(readwrite_ignore);		/* column numbers of index's attributes both
 								 * key and included columns, or 0 */
-	Oid		   *indexcollations;	/* OIDs of collations of index columns */
-	Oid		   *opfamily;		/* OIDs of operator families for columns */
-	Oid		   *opcintype;		/* OIDs of opclass declared input data types */
-	Oid		   *sortopfamily;	/* OIDs of btree opfamilies, if orderable */
-	bool	   *reverse_sort;	/* is sort order descending? */
-	bool	   *nulls_first;	/* do NULLs come first in the sort order? */
-	bytea	  **opclassoptions; /* opclass-specific options for columns */
-	bool	   *canreturn;		/* which index cols can be returned in an
+	Oid		   *indexcollations pg_node_attr(readwrite_ignore);	/* OIDs of collations of index columns */
+	Oid		   *opfamily pg_node_attr(readwrite_ignore);		/* OIDs of operator families for columns */
+	Oid		   *opcintype pg_node_attr(readwrite_ignore);		/* OIDs of opclass declared input data types */
+	Oid		   *sortopfamily pg_node_attr(readwrite_ignore);	/* OIDs of btree opfamilies, if orderable */
+	bool	   *reverse_sort pg_node_attr(readwrite_ignore);	/* is sort order descending? */
+	bool	   *nulls_first pg_node_attr(readwrite_ignore);	/* do NULLs come first in the sort order? */
+	bytea	  **opclassoptions pg_node_attr(readwrite_ignore); /* opclass-specific options for columns */
+	bool	   *canreturn pg_node_attr(readwrite_ignore);		/* which index cols can be returned in an
 								 * index-only scan? */
 	Oid			relam;			/* OID of the access method (in pg_am) */
 
-	List	   *indexprs;		/* expressions for non-simple index columns */
+	List	   *indexprs pg_node_attr(readwrite_ignore);		/* expressions for non-simple index columns */
 	List	   *indpred;		/* predicate if a partial index, else NIL */
 
 	List	   *indextlist;		/* targetlist representing index columns */
@@ -877,14 +877,14 @@ struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
-	bool		amoptionalkey;	/* can query omit key for the first column? */
-	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
-	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
-	bool		amhasgettuple;	/* does AM have amgettuple interface? */
-	bool		amhasgetbitmap; /* does AM have amgetbitmap interface? */
-	bool		amcanparallel;	/* does AM support parallel scan? */
-	bool		amcanmarkpos;	/* does AM support mark/restore? */
+	bool		amcanorderbyop pg_node_attr(readwrite_ignore); /* does AM support order by operator result? */
+	bool		amoptionalkey pg_node_attr(readwrite_ignore);	/* can query omit key for the first column? */
+	bool		amsearcharray pg_node_attr(readwrite_ignore);	/* can AM handle ScalarArrayOpExpr quals? */
+	bool		amsearchnulls pg_node_attr(readwrite_ignore);	/* can AM search for NULL/NOT NULL entries? */
+	bool		amhasgettuple pg_node_attr(readwrite_ignore);	/* does AM have amgettuple interface? */
+	bool		amhasgetbitmap pg_node_attr(readwrite_ignore); /* does AM have amgetbitmap interface? */
+	bool		amcanparallel pg_node_attr(readwrite_ignore);	/* does AM support parallel scan? */
+	bool		amcanmarkpos pg_node_attr(readwrite_ignore);	/* does AM support mark/restore? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
 };
@@ -905,9 +905,9 @@ typedef struct ForeignKeyOptInfo
 	Index		con_relid;		/* RT index of the referencing table */
 	Index		ref_relid;		/* RT index of the referenced table */
 	int			nkeys;			/* number of columns in the foreign key */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 
 	/* Derived info about whether FK's equality conditions match the query: */
 	int			nmatched_ec;	/* # of FK cols matched by ECs */
@@ -934,8 +934,8 @@ typedef struct StatisticExtInfo
 	NodeTag		type;
 
 	Oid			statOid;		/* OID of the statistics row */
-	bool		inherit;		/* includes child relations */
-	RelOptInfo *rel;			/* back-link to statistic's table */
+	bool		inherit pg_node_attr(readwrite_ignore);		/* includes child relations */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to statistic's table */
 	char		kind;			/* statistics kind of this entry */
 	Bitmapset  *keys;			/* attnums of the columns covered */
 	List	   *exprs;			/* expressions */
@@ -1109,7 +1109,7 @@ typedef struct PathTarget
 {
 	NodeTag		type;
 	List	   *exprs;			/* list of expressions to be computed */
-	Index	   *sortgrouprefs;	/* corresponding sort/group refnos, or 0 */
+	Index	   *sortgrouprefs pg_node_attr(array_size(exprs));	/* corresponding sort/group refnos, or 0 */
 	QualCost	cost;			/* cost of evaluating the expressions */
 	int			width;			/* estimated avg width of result tuples */
 	VolatileFunctionStatus has_volatile_expr;	/* indicates if exprs contain
@@ -1180,10 +1180,10 @@ typedef struct Path
 
 	NodeTag		pathtype;		/* tag identifying scan/join method */
 
-	RelOptInfo *parent;			/* the relation this path can build */
-	PathTarget *pathtarget;		/* list of Vars/Exprs, cost, width */
+	RelOptInfo *parent pg_node_attr(path_hack1);			/* the relation this path can build */
+	PathTarget *pathtarget pg_node_attr(path_hack2);		/* list of Vars/Exprs, cost, width */
 
-	ParamPathInfo *param_info;	/* parameterization info, or NULL if none */
+	ParamPathInfo *param_info pg_node_attr(path_hack3);	/* parameterization info, or NULL if none */
 
 	bool		parallel_aware; /* engage parallel-aware logic? */
 	bool		parallel_safe;	/* OK to use as part of parallel plan? */
@@ -2062,19 +2062,19 @@ typedef struct RestrictInfo
 
 	bool		outerjoin_delayed;	/* true if delayed by lower outer join */
 
-	bool		can_join;		/* see comment above */
+	bool		can_join pg_node_attr(equal_ignore);		/* see comment above */
 
-	bool		pseudoconstant; /* see comment above */
+	bool		pseudoconstant pg_node_attr(equal_ignore); /* see comment above */
 
-	bool		leakproof;		/* true if known to contain no leaked Vars */
+	bool		leakproof pg_node_attr(equal_ignore);		/* true if known to contain no leaked Vars */
 
-	VolatileFunctionStatus has_volatile;	/* to indicate if clause contains
+	VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);	/* to indicate if clause contains
 											 * any volatile functions. */
 
 	Index		security_level; /* see comment above */
 
 	/* The set of relids (varnos) actually referenced in the clause: */
-	Relids		clause_relids;
+	Relids		clause_relids pg_node_attr(equal_ignore);
 
 	/* The set of relids required to evaluate the clause: */
 	Relids		required_relids;
@@ -2086,48 +2086,48 @@ typedef struct RestrictInfo
 	Relids		nullable_relids;
 
 	/* These fields are set for any binary opclause: */
-	Relids		left_relids;	/* relids in left side of clause */
-	Relids		right_relids;	/* relids in right side of clause */
+	Relids		left_relids pg_node_attr(equal_ignore);	/* relids in left side of clause */
+	Relids		right_relids pg_node_attr(equal_ignore);	/* relids in right side of clause */
 
 	/* This field is NULL unless clause is an OR clause: */
-	Expr	   *orclause;		/* modified clause with RestrictInfos */
+	Expr	   *orclause pg_node_attr(equal_ignore);		/* modified clause with RestrictInfos */
 
 	/* This field is NULL unless clause is potentially redundant: */
-	EquivalenceClass *parent_ec;	/* generating EquivalenceClass */
+	EquivalenceClass *parent_ec pg_node_attr(equal_ignore readwrite_ignore);	/* generating EquivalenceClass */
 
 	/* cache space for cost and selectivity */
-	QualCost	eval_cost;		/* eval cost of clause; -1 if not yet set */
-	Selectivity norm_selec;		/* selectivity for "normal" (JOIN_INNER)
+	QualCost	eval_cost pg_node_attr(equal_ignore);		/* eval cost of clause; -1 if not yet set */
+	Selectivity norm_selec pg_node_attr(equal_ignore);		/* selectivity for "normal" (JOIN_INNER)
 								 * semantics; -1 if not yet set; >1 means a
 								 * redundant clause */
-	Selectivity outer_selec;	/* selectivity for outer join semantics; -1 if
+	Selectivity outer_selec pg_node_attr(equal_ignore);	/* selectivity for outer join semantics; -1 if
 								 * not yet set */
 
 	/* valid if clause is mergejoinable, else NIL */
-	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *mergeopfamilies pg_node_attr(equal_ignore);	/* opfamilies containing clause operator */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
-	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
-	EquivalenceClass *right_ec; /* EquivalenceClass containing righthand */
-	EquivalenceMember *left_em; /* EquivalenceMember for lefthand */
-	EquivalenceMember *right_em;	/* EquivalenceMember for righthand */
-	List	   *scansel_cache;	/* list of MergeScanSelCache structs */
+	EquivalenceClass *left_ec pg_node_attr(equal_ignore readwrite_ignore);	/* EquivalenceClass containing lefthand */
+	EquivalenceClass *right_ec pg_node_attr(equal_ignore readwrite_ignore); /* EquivalenceClass containing righthand */
+	EquivalenceMember *left_em pg_node_attr(equal_ignore); /* EquivalenceMember for lefthand */
+	EquivalenceMember *right_em pg_node_attr(equal_ignore);	/* EquivalenceMember for righthand */
+	List	   *scansel_cache pg_node_attr(copy_ignore equal_ignore);	/* list of MergeScanSelCache structs */
 
 	/* transient workspace for use while considering a specific join path */
-	bool		outer_is_left;	/* T = outer var on left, F = on right */
+	bool		outer_is_left pg_node_attr(equal_ignore);	/* T = outer var on left, F = on right */
 
 	/* valid if clause is hashjoinable, else InvalidOid: */
-	Oid			hashjoinoperator;	/* copy of clause operator */
+	Oid			hashjoinoperator pg_node_attr(equal_ignore);	/* copy of clause operator */
 
 	/* cache space for hashclause processing; -1 if not yet set */
-	Selectivity left_bucketsize;	/* avg bucketsize of left side */
-	Selectivity right_bucketsize;	/* avg bucketsize of right side */
-	Selectivity left_mcvfreq;	/* left side's most common val's freq */
-	Selectivity right_mcvfreq;	/* right side's most common val's freq */
+	Selectivity left_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of left side */
+	Selectivity right_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of right side */
+	Selectivity left_mcvfreq pg_node_attr(equal_ignore);	/* left side's most common val's freq */
+	Selectivity right_mcvfreq pg_node_attr(equal_ignore);	/* right side's most common val's freq */
 
 	/* hash equality operators used for memoize nodes, else InvalidOid */
-	Oid			left_hasheqoperator;
-	Oid			right_hasheqoperator;
+	Oid			left_hasheqoperator pg_node_attr(equal_ignore);
+	Oid			right_hasheqoperator pg_node_attr(equal_ignore);
 } RestrictInfo;
 
 /*
@@ -2182,8 +2182,8 @@ typedef struct MergeScanSelCache
 typedef struct PlaceHolderVar
 {
 	Expr		xpr;
-	Expr	   *phexpr;			/* the represented expression */
-	Relids		phrels;			/* base relids syntactically within expr src */
+	Expr	   *phexpr pg_node_attr(equal_ignore);			/* the represented expression */
+	Relids		phrels pg_node_attr(equal_ignore);			/* base relids syntactically within expr src */
 	Index		phid;			/* ID for PHV (unique within planner run) */
 	Index		phlevelsup;		/* > 0 if PHV belongs to outer query */
 } PlaceHolderVar;
@@ -2344,7 +2344,7 @@ typedef struct AppendRelInfo
 	 * child column is dropped or doesn't exist in the parent.
 	 */
 	int			num_child_cols; /* length of array */
-	AttrNumber *parent_colnos;	/* array of parent attnos, or zeroes */
+	AttrNumber *parent_colnos pg_node_attr(array_size(num_child_cols));	/* array of parent attnos, or zeroes */
 
 	/*
 	 * We store the parent table's OID here for inheritance, or InvalidOid for
@@ -2432,7 +2432,7 @@ typedef struct MinMaxAggInfo
 	Oid			aggfnoid;		/* pg_proc Oid of the aggregate */
 	Oid			aggsortop;		/* Oid of its sort operator */
 	Expr	   *target;			/* expression we are aggregating on */
-	PlannerInfo *subroot;		/* modified "root" for planning the subquery */
+	PlannerInfo *subroot pg_node_attr(readwrite_ignore);		/* modified "root" for planning the subquery */
 	Path	   *path;			/* access path for subquery */
 	Cost		pathcost;		/* estimated cost to fetch first row */
 	Param	   *param;			/* param for subplan's output */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0b518ce6b2..d2d372a5d0 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -275,10 +275,10 @@ typedef struct MergeAppend
 	List	   *mergeplans;
 	/* these fields are just like the sort-key info in struct Sort: */
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 	/* Info for run-time subplan pruning; NULL if we're not doing that */
 	struct PartitionPruneInfo *part_prune_info;
 } MergeAppend;
@@ -298,9 +298,9 @@ typedef struct RecursiveUnion
 	/* Remaining fields are zero/null in UNION ALL case */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
-	Oid		   *dupCollations;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 	long		numGroups;		/* estimated number of groups in input */
 } RecursiveUnion;
 
@@ -765,10 +765,10 @@ typedef struct MergeJoin
 	bool		skip_mark_restore;	/* Can we skip mark/restore calls? */
 	List	   *mergeclauses;	/* mergeclauses as expression trees */
 	/* these are arrays, but have the same length as the mergeclauses list: */
-	Oid		   *mergeFamilies;	/* per-clause OIDs of btree opfamilies */
-	Oid		   *mergeCollations;	/* per-clause OIDs of collations */
-	int		   *mergeStrategies;	/* per-clause ordering (ASC or DESC) */
-	bool	   *mergeNullsFirst;	/* per-clause nulls ordering */
+	Oid		   *mergeFamilies pg_node_attr(array_size(mergeclauses));	/* per-clause OIDs of btree opfamilies */
+	Oid		   *mergeCollations pg_node_attr(array_size(mergeclauses));	/* per-clause OIDs of collations */
+	int		   *mergeStrategies pg_node_attr(array_size(mergeclauses));	/* per-clause ordering (ASC or DESC) */
+	bool	   *mergeNullsFirst pg_node_attr(array_size(mergeclauses));	/* per-clause nulls ordering */
 } MergeJoin;
 
 /* ----------------
@@ -808,8 +808,8 @@ typedef struct Memoize
 
 	int			numKeys;		/* size of the two arrays below */
 
-	Oid		   *hashOperators;	/* hash operators for each key */
-	Oid		   *collations;		/* cache keys */
+	Oid		   *hashOperators pg_node_attr(array_size(numKeys));	/* hash operators for each key */
+	Oid		   *collations pg_node_attr(array_size(numKeys));		/* cache keys */
 	List	   *param_exprs;	/* exprs containing parameters */
 	bool		singlerow;		/* true if the cache entry should be marked as
 								 * complete after we store the first tuple in
@@ -830,10 +830,10 @@ typedef struct Sort
 {
 	Plan		plan;
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 } Sort;
 
 /* ----------------
@@ -856,9 +856,9 @@ typedef struct Group
 {
 	Plan		plan;
 	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
-	Oid		   *grpCollations;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 } Group;
 
 /* ---------------
@@ -881,9 +881,9 @@ typedef struct Agg
 	AggStrategy aggstrategy;	/* basic strategy, see nodes.h */
 	AggSplit	aggsplit;		/* agg-splitting mode, see nodes.h */
 	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
-	Oid		   *grpCollations;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 	long		numGroups;		/* estimated number of groups in input */
 	uint64		transitionSpace;	/* for pass-by-ref transition data */
 	Bitmapset  *aggParams;		/* IDs of Params used in Aggref inputs */
@@ -901,13 +901,13 @@ typedef struct WindowAgg
 	Plan		plan;
 	Index		winref;			/* ID referenced by window functions */
 	int			partNumCols;	/* number of columns in partition clause */
-	AttrNumber *partColIdx;		/* their indexes in the target list */
-	Oid		   *partOperators;	/* equality operators for partition columns */
-	Oid		   *partCollations; /* collations for partition columns */
+	AttrNumber *partColIdx pg_node_attr(array_size(partNumCols));		/* their indexes in the target list */
+	Oid		   *partOperators pg_node_attr(array_size(partNumCols));	/* equality operators for partition columns */
+	Oid		   *partCollations pg_node_attr(array_size(partNumCols)); /* collations for partition columns */
 	int			ordNumCols;		/* number of columns in ordering clause */
-	AttrNumber *ordColIdx;		/* their indexes in the target list */
-	Oid		   *ordOperators;	/* equality operators for ordering columns */
-	Oid		   *ordCollations;	/* collations for ordering columns */
+	AttrNumber *ordColIdx pg_node_attr(array_size(ordNumCols));		/* their indexes in the target list */
+	Oid		   *ordOperators pg_node_attr(array_size(ordNumCols));	/* equality operators for ordering columns */
+	Oid		   *ordCollations pg_node_attr(array_size(ordNumCols));	/* collations for ordering columns */
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
@@ -927,9 +927,9 @@ typedef struct Unique
 {
 	Plan		plan;
 	int			numCols;		/* number of columns to check for uniqueness */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+	AttrNumber *uniqColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *uniqOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *uniqCollations pg_node_attr(array_size(numCols)); /* collations for equality comparisons */
 } Unique;
 
 /* ------------
@@ -965,10 +965,10 @@ typedef struct GatherMerge
 	int			rescan_param;	/* ID of Param that signals a rescan, or -1 */
 	/* remaining fields are just like the sort-key info in struct Sort */
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 	Bitmapset  *initParam;		/* param id's of initplans which are referred
 								 * at gather merge or one of it's child node */
 } GatherMerge;
@@ -1008,9 +1008,9 @@ typedef struct SetOp
 	SetOpStrategy strategy;		/* how to do it, see nodes.h */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
-	Oid		   *dupCollations;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 	AttrNumber	flagColIdx;		/* where is the flag column, if any */
 	int			firstFlag;		/* flag value for first input relation */
 	long		numGroups;		/* estimated number of groups in input */
@@ -1046,9 +1046,9 @@ typedef struct Limit
 	Node	   *limitCount;		/* COUNT parameter, or NULL if none */
 	LimitOption limitOption;	/* limit type */
 	int			uniqNumCols;	/* number of columns to check for similarity  */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+	AttrNumber *uniqColIdx pg_node_attr(array_size(uniqNumCols));		/* their indexes in the target list */
+	Oid		   *uniqOperators pg_node_attr(array_size(uniqNumCols));	/* equality operators to compare with */
+	Oid		   *uniqCollations pg_node_attr(array_size(uniqNumCols)); /* collations for equality comparisons */
 } Limit;
 
 
@@ -1207,9 +1207,9 @@ typedef struct PartitionedRelPruneInfo
 	Bitmapset  *present_parts;	/* Indexes of all partitions which subplans or
 								 * subparts are present for */
 	int			nparts;			/* Length of the following arrays: */
-	int		   *subplan_map;	/* subplan index by partition index, or -1 */
-	int		   *subpart_map;	/* subpart index by partition index, or -1 */
-	Oid		   *relid_map;		/* relation OID by partition index, or 0 */
+	int		   *subplan_map pg_node_attr(array_size(nparts));	/* subplan index by partition index, or -1 */
+	int		   *subpart_map pg_node_attr(array_size(nparts));	/* subpart index by partition index, or -1 */
+	Oid		   *relid_map pg_node_attr(array_size(nparts));		/* relation OID by partition index, or 0 */
 
 	/*
 	 * initial_pruning_steps shows how to prune during executor startup (i.e.,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index dab5c4ff5d..0e2c06a48a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -63,7 +63,7 @@ typedef enum OnCommitAction
 typedef struct RangeVar
 {
 	NodeTag		type;
-	char	   *catalogname;	/* the catalog (database) name, or NULL */
+	char	   *catalogname pg_node_attr(readwrite_ignore);	/* the catalog (database) name, or NULL */
 	char	   *schemaname;		/* the schema name, or NULL */
 	char	   *relname;		/* the relation/sequence name */
 	bool		inh;			/* expand rel by inheritance? recursively act
@@ -196,8 +196,8 @@ typedef struct Var
 	Index		varlevelsup;	/* for subquery variables referencing outer
 								 * relations; 0 in a normal var, >0 means N
 								 * levels up */
-	Index		varnosyn;		/* syntactic relation index (0 if unknown) */
-	AttrNumber	varattnosyn;	/* syntactic attribute number */
+	Index		varnosyn pg_node_attr(equal_ignore);		/* syntactic relation index (0 if unknown) */
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore);	/* syntactic attribute number */
 	int			location;		/* token location, or -1 if unknown */
 } Var;
 
@@ -324,7 +324,7 @@ typedef struct Aggref
 	Oid			aggtype;		/* type Oid of result of the aggregate */
 	Oid			aggcollid;		/* OID of collation of result */
 	Oid			inputcollid;	/* OID of collation that function should use */
-	Oid			aggtranstype;	/* type Oid of aggregate's transition value */
+	Oid			aggtranstype pg_node_attr(equal_ignore);	/* type Oid of aggregate's transition value */
 	List	   *aggargtypes;	/* type Oids of direct and aggregated args */
 	List	   *aggdirectargs;	/* direct arguments, if an ordered-set agg */
 	List	   *args;			/* aggregated arguments and sort expressions */
@@ -371,8 +371,8 @@ typedef struct GroupingFunc
 	Expr		xpr;
 	List	   *args;			/* arguments, not evaluated but kept for
 								 * benefit of EXPLAIN etc. */
-	List	   *refs;			/* ressortgrouprefs of arguments */
-	List	   *cols;			/* actual column positions set by planner */
+	List	   *refs pg_node_attr(equal_ignore);			/* ressortgrouprefs of arguments */
+	List	   *cols pg_node_attr(equal_ignore);			/* actual column positions set by planner */
 	Index		agglevelsup;	/* same as Aggref.agglevelsup */
 	int			location;		/* token location */
 } GroupingFunc;
@@ -540,7 +540,7 @@ typedef struct OpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of underlying function */
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of underlying function */
 	Oid			opresulttype;	/* PG_TYPE OID of result value */
 	bool		opretset;		/* true if operator returns set */
 	Oid			opcollid;		/* OID of collation of result */
@@ -597,9 +597,9 @@ typedef struct ScalarArrayOpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of comparison function */
-	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
-	Oid			negfuncid;		/* PG_PROC OID of negator of opfuncid function
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of comparison function */
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of hash func or InvalidOid */
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of negator of opfuncid function
 								 * or InvalidOid.  See above */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8d2e3e3a57..54a85f395d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -371,14 +371,14 @@
  * copyObject(), to facilitate catching errors and omissions in
  * copyObject().
  */
-/* #define COPY_PARSE_PLAN_TREES */
+#define COPY_PARSE_PLAN_TREES
 
 /*
  * Define this to force all parse and plan trees to be passed through
  * outfuncs.c/readfuncs.c, to facilitate catching errors and omissions in
  * those modules.
  */
-/* #define WRITE_READ_PARSE_PLAN_TREES */
+#define WRITE_READ_PARSE_PLAN_TREES
 
 /*
  * Define this to force all raw parse trees for DML statements to be scanned
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 6da1b220cd..459a64a992 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -273,9 +273,9 @@ typedef struct ForeignKeyCacheInfo
 	Oid			confrelid;		/* relation referenced by the foreign key */
 	int			nkeys;			/* number of columns in the foreign key */
 	/* these arrays each have nkeys valid entries: */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 } ForeignKeyCacheInfo;
 
 
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index e47c2d648c..fc3e3cf01c 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -833,6 +833,52 @@ EOF
 		close($chs);
 	}
 
+	if (IsNewer('src/backend/nodes/node-support-stamp',
+		'src/backend/nodes/gen_node_support.pl'))
+	{
+		# XXX duplicates src/backend/nodes/Makefile
+
+		my @node_headers = qw(
+			nodes/nodes.h
+			nodes/execnodes.h
+			nodes/plannodes.h
+			nodes/primnodes.h
+			nodes/pathnodes.h
+			nodes/extensible.h
+			nodes/parsenodes.h
+			nodes/replnodes.h
+			nodes/value.h
+			commands/trigger.h
+			commands/event_trigger.h
+			foreign/fdwapi.h
+			access/amapi.h
+			access/tableam.h
+			access/tsmapi.h
+			utils/rel.h
+			nodes/supportnodes.h
+			executor/tuptable.h
+			nodes/lockoptions.h
+			access/sdir.h
+		);
+
+		chdir('src/backend/nodes');
+
+		my @node_files = map { "../../../src/include/$_" } @node_headers;
+
+		system("perl gen_node_support.pl @node_files");
+		open(my $f, '>', 'node-support-stamp') || confess "Could not touch node-support-stamp";
+		close($f);
+		chdir('../../..');
+	}
+
+	if (IsNewer(
+			'src/include/nodes/nodetags.h',
+			'src/backend/nodes/nodetags.h'))
+	{
+		copyFile('src/backend/nodes/nodetags.h',
+			'src/include/nodes/nodetags.h');
+	}
+
 	open(my $o, '>', "doc/src/sgml/version.sgml")
 	  || croak "Could not write to version.sgml\n";
 	print $o <<EOF;

base-commit: f032f63e727c1ab07603b3d1cd88d50f850d5738
-- 
2.34.1

#20Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Peter Eisentraut (#19)
Re: automatically generating node support functions

What do people think about this patch now?

I have received some feedback on several small technical issues, which
have all been fixed. This patch has been around for several commit
fests now and AFAICT, nothing has broken it. This is just to indicate
that the parsing isn't as flimsy as one might fear.

One thing thing that is waiting behind this patch is that you currently
cannot put utility commands into parse-time SQL functions, because there
is no full out/read support for those. This patch would fix that
problem. (There is a little bit of additional work necessary, but I
have that mostly worked out in a separate branch.)

Show quoted text

On 24.01.22 16:15, Peter Eisentraut wrote:

Rebased patch to resolve some merge conflicts

On 29.12.21 12:08, Peter Eisentraut wrote:

On 12.10.21 15:52, Andrew Dunstan wrote:

I haven't been through the whole thing, but I did notice this: the
comment stripping code looks rather fragile. I think it would blow up if
there were a continuation line not starting with  qr/\s*\*/. It's a lot
simpler and more robust to do this if you slurp the file in whole.
Here's what we do in the buildfarm code:

     my $src = file_contents($_);
     # strip C comments
     # We used to use the recipe in perlfaq6 but there is actually no
point.
     # We don't need to keep the quoted string values anyway, and
     # on some platforms the complex regex causes perl to barf and
crash.
     $src =~ s{/\*.*?\*/}{}gs;

After you've done that splitting it into lines is pretty simple.

Here is an updated patch, with some general rebasing, and the above
improvement.  It now also generates #include lines necessary in
copyfuncs etc. to pull in all the node types it operates on.

Further, I have looked more into the "metadata" approach discussed in
[0].  It's pretty easy to generate that kind of output from the data
structures my script produces.  You just loop over all the node types
and print stuff and keep a few counters.  I don't plan to work on that
at this time, but I just wanted to point out that if people wanted to
move into that direction, my patch wouldn't be in the way.

[0]:
/messages/by-id/20190828234136.fk2ndqtld3onfrrp@alap3.anarazel.de

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#20)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

What do people think about this patch now?

I'm in favor of moving forward with this. I do not like the
libclang-based approach that Andres was pushing, because of the
jump in developer tooling requirements that it'd cause.

Eyeballing the patch a bit, I do have some comments:

* It's time for action on the business about extracting comments
from the to-be-deleted code.

* The Perl script is kind of under-commented for my taste.
It lacks a copyright notice, too.

* In the same vein, I should not have to reverse-engineer what
the available pg_node_attr() properties are or do. Perhaps they
could be documented in the comment for the pg_node_attr macro
in nodes.h.

* Maybe the generated file names could be chosen less opaquely,
say ".funcs" and ".switch" instead of ".inc1" and ".inc2".

* I don't understand why there are changes in the #include
lists in copyfuncs.c etc?

* I think that more thought needs to be put into the format
of the *nodes.h struct declarations, because I fear pgindent
is going to make a hash of what you've done here. When we
did similar stuff in the catalog headers, I think we ended
up moving a lot of end-of-line comments onto their own lines.

* I assume the pg_config_manual.h changes are not meant for
commit?

regards, tom lane

#22Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#21)
Re: automatically generating node support functions

Hi,

On 2022-02-14 12:09:47 -0500, Tom Lane wrote:

I'm in favor of moving forward with this. I do not like the
libclang-based approach that Andres was pushing, because of the
jump in developer tooling requirements that it'd cause.

FWIW, while I don't love the way the header parsing stuff in the patch (vs
using libclang or such), I don't have a real problem with it.

I do however not think it's a good idea to commit something generating
something like the existing node functions vs going for a metadata based
approach at dealing with node functions. That aspect of my patchset is
independent of the libclang vs script debate.

Greetings,

Andres Freund

#23Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#22)
Re: automatically generating node support functions

Andres Freund <andres@anarazel.de> writes:

I do however not think it's a good idea to commit something generating
something like the existing node functions vs going for a metadata based
approach at dealing with node functions. That aspect of my patchset is
independent of the libclang vs script debate.

I think that finishing out and committing this patch is a fine step
on the way to that. Either that, or you should go ahead and merge
your backend work onto what Peter's done ... but that seems like
it'll be bigger and harder to review.

regards, tom lane

#24Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#23)
Re: automatically generating node support functions

Hi,

On 2022-02-14 18:32:21 -0500, Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

I do however not think it's a good idea to commit something generating
something like the existing node functions vs going for a metadata based
approach at dealing with node functions. That aspect of my patchset is
independent of the libclang vs script debate.

I think that finishing out and committing this patch is a fine step
on the way to that.

I think most of gen_node_support.pl would change - a lot of that is generating
the node functions, which would not be needed anymore. And most of the
remainder would change as well.

Either that, or you should go ahead and merge your backend work onto what
Peter's done ...

I did offer to do part of that a while ago:

/messages/by-id/20210715012454.bvwg63farhmfwb47@alap3.anarazel.de

On 2021-07-14 18:24:54 -0700, Andres Freund wrote:

If Peter could generate something roughly like the metadata I emitted, I'd
rebase my node functions ontop of that.

Greetings,

Andres Freund

#25Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#24)
Re: automatically generating node support functions

Andres Freund <andres@anarazel.de> writes:

On 2022-02-14 18:32:21 -0500, Tom Lane wrote:

I think that finishing out and committing this patch is a fine step
on the way to that.

I think most of gen_node_support.pl would change - a lot of that is generating
the node functions, which would not be needed anymore. And most of the
remainder would change as well.

Well, yeah, we'd be throwing away some of that Perl code. So what?
I think that most of the intellectual content in this patch is getting
the data source nailed down, ie putting the annotations into the *nodes.h
files and building the code to parse that. I don't have a problem
with throwing away and rewriting the back-end part of the patch later.

And, TBH, I am not really convinced that a pure metadata approach is going
to work out, or that it will have sufficient benefit over just automating
the way we do it now. I notice that Peter's patch leaves a few
too-much-of-a-special-case functions unconverted, which is no real
problem for his approach; but it seems like you won't get to take such
shortcuts in a metadata-reading implementation.

The bottom line here is that I believe that Peter's patch could get us out
of the business of hand-maintaining the backend/nodes/*.c files in the
v15 timeframe, which would be a very nice thing. I don't see how your
patch will be ready on anywhere near the same schedule. When it is ready,
we can switch, but in the meantime I'd like the maintenance benefit.

regards, tom lane

#26Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#25)
Re: automatically generating node support functions

Hi,

On 2022-02-14 20:47:33 -0500, Tom Lane wrote:

I think that most of the intellectual content in this patch is getting
the data source nailed down, ie putting the annotations into the *nodes.h
files and building the code to parse that. I don't have a problem
with throwing away and rewriting the back-end part of the patch later.

Imo that cuts the other way - without going for a metadata based approach we
don't know if we made the annotations rich enough...

And, TBH, I am not really convinced that a pure metadata approach is going
to work out, or that it will have sufficient benefit over just automating
the way we do it now. I notice that Peter's patch leaves a few
too-much-of-a-special-case functions unconverted, which is no real
problem for his approach; but it seems like you won't get to take such
shortcuts in a metadata-reading implementation.

IMO my prototype of that approach pretty conclusively shows that it's feasible
and worthwhile.

The bottom line here is that I believe that Peter's patch could get us out
of the business of hand-maintaining the backend/nodes/*.c files in the v15
timeframe, which would be a very nice thing. I don't see how your patch
will be ready on anywhere near the same schedule. When it is ready, we can
switch, but in the meantime I'd like the maintenance benefit.

I'm not going to try to prevent the patch from going in. But I don't think
it's a great idea to this without even trying to ensure the annotations are
rich enough...

Greetings,

Andres Freund

#27Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#21)
1 attachment(s)
Re: automatically generating node support functions

On 14.02.22 18:09, Tom Lane wrote:

* It's time for action on the business about extracting comments
from the to-be-deleted code.

done

* The Perl script is kind of under-commented for my taste.
It lacks a copyright notice, too.

done

* In the same vein, I should not have to reverse-engineer what
the available pg_node_attr() properties are or do. Perhaps they
could be documented in the comment for the pg_node_attr macro
in nodes.h.

done

* Maybe the generated file names could be chosen less opaquely,
say ".funcs" and ".switch" instead of ".inc1" and ".inc2".

done

* I don't understand why there are changes in the #include
lists in copyfuncs.c etc?

Those are #include lines required for the definitions of various
structs. Since the generation script already knows which header files
are relevant (they are its input files), it can just generate the
required #include lines as well. That way, the remaining copyfuncs.c
only has #include lines for things that the (remaining) file itself
needs, not what the files included by it need, and if a new header file
were to be added, it doesn't have to be added in 4+ places.

* I think that more thought needs to be put into the format
of the *nodes.h struct declarations, because I fear pgindent
is going to make a hash of what you've done here. When we
did similar stuff in the catalog headers, I think we ended
up moving a lot of end-of-line comments onto their own lines.

I have tested pgindent repeatedly throughout this project, and it
doesn't look too bad. You are right that some manual curation of
comment formatting would be sensible, but I think that might be better
done as a separate patch.

* I assume the pg_config_manual.h changes are not meant for
commit?

right

Attachments:

v5-0001-Automatically-generate-node-support-functions.patchtext/plain; charset=UTF-8; name=v5-0001-Automatically-generate-node-support-functions.patchDownload
From cdd9f2a6738b8c85cec8ba6169f281ebdc4d2e4d Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Fri, 18 Feb 2022 07:39:42 +0100
Subject: [PATCH v5] Automatically generate node support functions

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

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

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

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

Subtyping (TidScan -> Scan) is supported.

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

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

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a02006788..821bef2694 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -143,11 +143,15 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
 submake-catalog-headers:
 	$(MAKE) -C catalog distprep generated-header-symlinks
 
+# run this unconditionally to avoid needing to know its dependencies here:
+submake-nodes-headers:
+	$(MAKE) -C nodes distprep generated-header-symlinks
+
 # run this unconditionally to avoid needing to know its dependencies here:
 submake-utils-headers:
 	$(MAKE) -C utils distprep generated-header-symlinks
 
-.PHONY: submake-catalog-headers submake-utils-headers
+.PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 # Make symlinks for these headers in the include directory. That way
 # we can cut down on the -I options. Also, a symlink is automatically
@@ -162,7 +166,7 @@ submake-utils-headers:
 
 .PHONY: generated-headers
 
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..0c14b5697b
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,4 @@
+/node-support-stamp
+/nodetags.h
+/*funcs.funcs.c
+/*funcs.switch.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..c7b8df4ec2 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,49 @@ OBJS = \
 	value.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+node_headers = \
+	nodes/nodes.h \
+	nodes/execnodes.h \
+	nodes/plannodes.h \
+	nodes/primnodes.h \
+	nodes/pathnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.h \
+	nodes/value.h \
+	commands/trigger.h \
+	commands/event_trigger.h \
+	foreign/fdwapi.h \
+	access/amapi.h \
+	access/tableam.h \
+	access/tsmapi.h \
+	utils/rel.h \
+	nodes/supportnodes.h \
+	executor/tuptable.h \
+	nodes/lockoptions.h \
+	access/sdir.h
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-support-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+node-support-stamp: gen_node_support.pl $(addprefix $(top_srcdir)/src/include/,$(node_headers))
+	$(PERL) $^
+	touch $@
+
+$(top_builddir)/src/include/nodes/header-stamp: node-support-stamp
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	cd '$(dir $@)' && for file in nodetags.h; do \
+	  rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \
+	done
+	touch $@
+
+maintainer-clean: clean
+	rm -f node-support-stamp *funcs.funcs.c *funcs.switch.c nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bc0d90b4b1..c43566721f 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
  * ****************************************************************
@@ -1457,6 +1456,7 @@ _copyVar(const Var *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * _copyConst
@@ -1496,6 +1496,7 @@ _copyConst(const Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -2731,6 +2732,7 @@ _copyParamRef(const ParamRef *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -2771,6 +2773,7 @@ _copyA_Const(const A_Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -4918,6 +4921,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *					extensible.h copy functions
@@ -4940,6 +4944,7 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /* ****************************************************************
  *					value.h copy functions
  * ****************************************************************
@@ -5010,6 +5015,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5030,6 +5036,8 @@ copyObjectImpl(const void *from)
 
 	switch (nodeTag(from))
 	{
+#include "copyfuncs.switch.c"
+#ifdef OBSOLETE
 			/*
 			 * PLAN NODES
 			 */
@@ -5390,6 +5398,7 @@ copyObjectImpl(const void *from)
 		case T_BitString:
 			retval = _copyBitString(from);
 			break;
+#endif /*OBSOLETE*/
 
 			/*
 			 * LIST NODES
@@ -5407,6 +5416,7 @@ copyObjectImpl(const void *from)
 			retval = list_copy(from);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -5949,6 +5959,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 2e7122ad2f..f6f9bc5244 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
  */
@@ -185,6 +183,7 @@ _equalVar(const Var *a, const Var *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -207,6 +206,7 @@ _equalConst(const Const *a, const Const *b)
 						a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -946,6 +946,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -967,6 +968,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const ExtensibleNode *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2441,6 +2443,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2458,6 +2461,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3068,6 +3072,7 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3128,6 +3133,7 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from value.h
  */
@@ -3171,6 +3177,7 @@ _equalBitString(const BitString *a, const BitString *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * equal
@@ -3201,6 +3208,8 @@ equal(const void *a, const void *b)
 
 	switch (nodeTag(a))
 	{
+#include "equalfuncs.switch.c"
+#ifdef OBSOLETE
 			/*
 			 * PRIMITIVE NODES
 			 */
@@ -3379,6 +3388,7 @@ equal(const void *a, const void *b)
 		case T_PlaceHolderInfo:
 			retval = _equalPlaceHolderInfo(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		case T_List:
 		case T_IntList:
@@ -3386,6 +3396,7 @@ equal(const void *a, const void *b)
 			retval = _equalList(a, b);
 			break;
 
+#ifdef OBSOLETE
 		case T_Integer:
 			retval = _equalInteger(a, b);
 			break;
@@ -3937,6 +3948,7 @@ equal(const void *a, const void *b)
 		case T_PublicationTable:
 			retval = _equalPublicationTable(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
new file mode 100644
index 0000000000..edbafd3e5b
--- /dev/null
+++ b/src/backend/nodes/gen_node_support.pl
@@ -0,0 +1,729 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/backend/nodes/gen_node_support.pl
+#
+#----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use File::Basename;
+
+use FindBin;
+use lib "$FindBin::RealBin/../catalog";
+
+use Catalog;  # for RenameTempFile
+
+
+# Test whether first argument is element of the list in the second
+# argument
+sub elem
+{
+	my $x = shift;
+	return grep { $_ eq $x } @_;
+}
+
+
+# collect node names
+my @node_types = qw(Node);
+# collect info for each node type
+my %node_type_info;
+
+# node types we don't want copy support for
+my @no_copy;
+# node types we don't want read/write support for
+my @no_read_write;
+
+# types that are copied by straight assignment
+my @scalar_types = qw(
+	bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+	AclMode AttrNumber Cardinality Cost Index Oid Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr
+);
+
+# collect enum types
+my @enum_types;
+
+# Abstract types are types that cannot be instantiated but that can be
+# supertypes of other types.  We track their fields, so that subtypes
+# can use them, but we don't emit a node tag, so you can't instantiate
+# them.
+my @abstract_types = qw(
+	Node Expr
+	BufferHeapTupleTableSlot HeapTupleTableSlot MinimalTupleTableSlot VirtualTupleTableSlot
+	JoinPath
+	PartitionPruneStep
+);
+
+# Special cases that either don't have their own struct or the struct
+# is not in a header file.  We just generate node tags for them, but
+# they otherwise don't participate in node support.
+my @extra_tags = qw(
+	IntList OidList
+	AllocSetContext GenerationContext SlabContext
+	TIDBitmap
+	WindowObjectData
+);
+
+# This is a regular node, but we skip parsing it from its header file
+# since we won't use its internal structure here anyway.
+push @node_types, qw(List);
+
+# pathnodes.h exceptions: We don't support copying RelOptInfo,
+# IndexOptInfo, or Path nodes.  There are some subsidiary structs that
+# are useful to copy, though.
+push @no_copy, qw(
+	RelOptInfo IndexOptInfo Path PlannerGlobal EquivalenceClass EquivalenceMember ForeignKeyOptInfo
+	GroupingSetData IncrementalSortPath IndexClause MinMaxAggInfo PathTarget PlannerInfo PlannerParamItem
+	ParamPathInfo RollupData RowIdentityVarInfo StatisticExtInfo
+);
+# EquivalenceClasses are never moved, so just shallow-copy the pointer
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember*);
+push @scalar_types, qw(QualCost);
+
+# See special treatment in outNode() and nodeRead() for these.
+push @no_read_write, qw(BitString Boolean Float Integer List String);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation);
+
+
+## read input
+
+foreach my $infile (@ARGV)
+{
+	my $in_struct;
+	my $subline;
+	my $is_node_struct;
+	my $supertype;
+	my $supertype_field;
+
+	my @my_fields;
+	my %my_field_types;
+	my %my_field_attrs;
+
+	open my $ifh, '<', $infile or die "could not open \"$infile\": $!";
+
+	my $file_content = do { local $/; <$ifh> };
+
+	# strip C comments
+	$file_content =~ s{/\*.*?\*/}{}gs;
+
+	foreach my $line (split /\n/, $file_content)
+	{
+		chomp $line;
+		$line =~ s/\s*$//;
+		next if $line eq '';
+		next if $line =~ /^#(define|ifdef|endif)/;
+
+		# we are analyzing a struct definition
+		if ($in_struct)
+		{
+			$subline++;
+
+			# first line should have opening brace
+			if ($subline == 1)
+			{
+				$is_node_struct = 0;
+				$supertype = undef;
+				next if $line eq '{';
+				die;
+			}
+			# second line should have node tag or supertype
+			elsif ($subline == 2)
+			{
+				if ($line =~ /^\s*NodeTag\s+type;/)
+				{
+					$is_node_struct = 1;
+					next;
+				}
+				elsif ($line =~ /\s*(\w+)\s+(\w+);/ and elem $1, @node_types)
+				{
+					$is_node_struct = 1;
+					$supertype = $1;
+					$supertype_field = $2;
+					next;
+				}
+			}
+
+			# end of struct
+			if ($line =~ /^\}\s*$in_struct;$/ || $line =~ /^\};$/)
+			{
+				if ($is_node_struct)
+				{
+					# This is the end of a node struct definition.
+					# Save everything we have collected.
+
+					# node name
+					push @node_types, $in_struct;
+
+					# field names, types, attributes
+					my @f = @my_fields;
+					my %ft = %my_field_types;
+					my %fa = %my_field_attrs;
+
+					# If there is a supertype, add those fields, too.
+					if ($supertype)
+					{
+						my @superfields;
+						foreach my $sf (@{$node_type_info{$supertype}->{fields}})
+						{
+							my $fn = "${supertype_field}.$sf";
+							push @superfields, $fn;
+							$ft{$fn} = $node_type_info{$supertype}->{field_types}{$sf};
+							$fa{$fn} = $node_type_info{$supertype}->{field_attrs}{$sf};
+							$fa{$fn} =~ s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/ if $fa{$fn};
+						}
+						unshift @f, @superfields;
+					}
+					# save in global info structure
+					$node_type_info{$in_struct}->{fields} = \@f;
+					$node_type_info{$in_struct}->{field_types} = \%ft;
+					$node_type_info{$in_struct}->{field_attrs} = \%fa;
+
+					# Nodes from these files don't need to be
+					# supported, except the node tags.
+					if (elem basename($infile),
+						qw(execnodes.h trigger.h event_trigger.h amapi.h tableam.h
+							tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h))
+					{
+						push @no_copy, $in_struct;
+						push @no_read_write, $in_struct;
+					}
+
+					# We do not support copying Path trees, mainly
+					# because the circular linkages between RelOptInfo
+					# and Path nodes can't be handled easily in a
+					# simple depth-first traversal.
+					if ($supertype && ($supertype eq 'Path' || $supertype eq 'JoinPath'))
+					{
+						push @no_copy, $in_struct;
+					}
+				}
+
+				# start new cycle
+				$in_struct = undef;
+				@my_fields = ();
+				%my_field_types = ();
+				%my_field_attrs = ();
+			}
+			# normal struct field
+			elsif ($line =~ /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w() ]*)\))?;/)
+			{
+				if ($is_node_struct)
+				{
+					my $type = $1;
+					my $name = $2;
+					my $array_size = $3;
+					my $attr = $4;
+
+					# strip "const"
+					$type =~ s/^const\s*//;
+					# strip trailing space
+					$type =~ s/\s*$//;
+					# strip space between type and "*" (pointer) */
+					$type =~ s/\s+\*$/*/;
+
+					die if $type eq '';
+
+					$type = $type . $array_size if $array_size;
+					push @my_fields, $name;
+					$my_field_types{$name} = $type;
+					$my_field_attrs{$name} = $attr;
+				}
+			}
+			else
+			{
+				if ($is_node_struct)
+				{
+					#warn "$infile:$.: could not parse \"$line\"\n";
+				}
+			}
+		}
+		# not in a struct
+		else
+		{
+			# start of a struct?
+			if ($line =~ /^(?:typedef )?struct (\w+)(\s*\/\*.*)?$/ && $1 ne 'Node')
+			{
+				$in_struct = $1;
+				$subline = 0;
+			}
+			# one node type typedef'ed directly from another
+			elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types)
+			{
+				my $alias_of = $1;
+				my $n = $2;
+
+				# copy everything over
+				push @node_types, $n;
+				my @f = @{$node_type_info{$alias_of}->{fields}};
+				my %ft = %{$node_type_info{$alias_of}->{field_types}};
+				my %fa = %{$node_type_info{$alias_of}->{field_attrs}};
+				$node_type_info{$n}->{fields} = \@f;
+				$node_type_info{$n}->{field_types} = \%ft;
+				$node_type_info{$n}->{field_attrs} = \%fa;
+			}
+			# collect enum names
+			elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/)
+			{
+				push @enum_types, $1;
+			}
+		}
+	}
+
+	if ($in_struct)
+	{
+		die "runaway \"$in_struct\" in file \"$infile\"\n";
+	}
+
+	close $ifh;
+} # for each file
+
+
+## write output
+
+my $tmpext  = ".tmp$$";
+
+# nodetags.h
+
+open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
+
+my $i = 1;
+foreach my $n (@node_types,@extra_tags)
+{
+	next if elem $n, @abstract_types;
+	print $nt "\tT_${n} = $i,\n";
+	$i++;
+}
+
+close $nt;
+
+
+# make #include lines necessary to pull in all the struct definitions
+my $node_includes = '';
+foreach my $infile (sort @ARGV)
+{
+	$infile =~ s!.*src/include/!!;
+	$node_includes .= qq{#include "$infile"\n};
+}
+
+
+# copyfuncs.c, equalfuncs.c
+
+open my $cff, '>', 'copyfuncs.funcs.c' . $tmpext or die $!;
+open my $eff, '>', 'equalfuncs.funcs.c' . $tmpext or die $!;
+open my $cfs, '>', 'copyfuncs.switch.c' . $tmpext or die $!;
+open my $efs, '>', 'equalfuncs.switch.c' . $tmpext or die $!;
+
+# add required #include lines to each file set
+print $cff $node_includes;
+print $eff $node_includes;
+
+# Nodes with custom copy implementations are skipped from .funcs.c but
+# need case statements in .switch.c.
+my @custom_copy = qw(A_Const Const ExtensibleNode);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_copy;
+	next if $n eq 'List';
+
+	print $cfs "
+\t\tcase T_${n}:
+\t\t\tretval = _copy${n}(from);
+\t\t\tbreak;";
+
+	print $efs "
+\t\tcase T_${n}:
+\t\t\tretval = _equal${n}(a, b);
+\t\t\tbreak;";
+
+	next if elem $n, @custom_copy;
+
+	print $cff "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+";
+
+	print $eff "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+";
+
+	# print instructions for each field
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $copy_ignore = ($a =~ /\bcopy_ignore\b/);
+		my $equal_ignore = ($a =~ /\bequal_ignore\b/);
+
+		# select instructions by field type
+		if ($t eq 'char*')
+		{
+			print $cff "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_STRING_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $cff "\tCOPY_BITMAPSET_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_BITMAPSET_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $cff "\tCOPY_LOCATION_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_LOCATION_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif (elem $t, @scalar_types or elem $t, @enum_types)
+		{
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			if ($a =~ /\bequal_ignore_if_zero\b/)
+			{
+				print $eff "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
+			}
+			else
+			{
+				print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+			}
+		}
+		# scalar type pointer
+		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
+		{
+			my $tt = $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $cff "\tCOPY_POINTER_FIELD($f, list_length(from->$array_size_field) * sizeof($tt));\n" unless $copy_ignore;
+				print $eff "\tCOMPARE_POINTER_FIELD($f, list_length(a->$array_size_field) * sizeof($tt));\n" unless $equal_ignore;
+			}
+			else
+			{
+				print $cff "\tCOPY_POINTER_FIELD($f, from->$array_size_field * sizeof($tt));\n" unless $copy_ignore;
+				print $eff "\tCOMPARE_POINTER_FIELD($f, a->$array_size_field * sizeof($tt));\n" unless $equal_ignore;
+			}
+		}
+		# node type
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $cff "\tCOPY_NODE_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
+		}
+		# array (inline)
+		elsif ($t =~ /\w+\[/)
+		{
+			print $cff "\tCOPY_ARRAY_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_ARRAY_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			# Fields of these types are required to be a pointer to a
+			# static table of callback functions.  So we don't copy
+			# the table itself, just reference the original one.
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $cff "
+\treturn newnode;
+}
+";
+	print $eff "
+\treturn true;
+}
+";
+}
+
+close $cff;
+close $eff;
+close $cfs;
+close $efs;
+
+
+# outfuncs.c, readfuncs.c
+
+open my $off, '>', 'outfuncs.funcs.c' . $tmpext or die $!;
+open my $rff, '>', 'readfuncs.funcs.c' . $tmpext or die $!;
+open my $ofs, '>', 'outfuncs.switch.c' . $tmpext or die $!;
+open my $rfs, '>', 'readfuncs.switch.c' . $tmpext or die $!;
+
+print $off $node_includes;
+print $rff $node_includes;
+
+my @custom_readwrite = qw(A_Const A_Expr BoolExpr Const Constraint EquivalenceClass ExtensibleNode ForeignKeyOptInfo Query RangeTblEntry);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_read_write;
+
+	# XXX For now, skip all "Stmt"s except that ones that were there before.
+	if ($n =~ /Stmt$/)
+	{
+		my @keep = qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt ImportForeignSchemaStmt IndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt);
+		next unless elem $n, @keep;
+	}
+
+	# XXX Also skip read support for those that didn't have it before.
+	my $no_read = ($n eq 'A_Star' || $n eq 'A_Const' || $n eq 'A_Expr' || $n eq 'Constraint' || $n =~ /Path$/ || $n eq 'EquivalenceClass' || $n eq 'ForeignKeyCacheInfo' || $n eq 'ForeignKeyOptInfo' || $n eq 'PathTarget');
+
+	# output format starts with upper case node type, underscores stripped
+	my $N = uc $n;
+	$N =~ s/_//g;
+
+	print $ofs "\t\t\tcase T_${n}:\n".
+	  "\t\t\t\t_out${n}(str, obj);\n".
+	  "\t\t\t\tbreak;\n";
+
+	print $rfs "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+
+	next if elem $n, @custom_readwrite;
+
+	print $off "
+static void
+_out${n}(StringInfo str, const $n *node)
+{
+\tWRITE_NODE_TYPE(\"$N\");
+
+";
+
+	print $rff "
+static $n *
+_read${n}(void)
+{
+\tREAD_LOCALS($n);
+
+" unless $no_read;
+
+	# print instructions for each field
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $readwrite_ignore = ($a =~ /\breadwrite_ignore\b/);
+		next if $readwrite_ignore;
+
+		# XXX Previously, for subtyping, only the leaf field name is
+		# used. Ponder whether we want to keep it that way.
+
+		# select instructions by field type
+		if ($t eq 'bool')
+		{
+			print $off "\tWRITE_BOOL_FIELD($f);\n";
+			print $rff "\tREAD_BOOL_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $off "\tWRITE_LOCATION_FIELD($f);\n";
+			print $rff "\tREAD_LOCATION_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' || $t eq 'int32' || $t eq 'AttrNumber' || $t eq 'StrategyNumber')
+		{
+			print $off "\tWRITE_INT_FIELD($f);\n";
+			print $rff "\tREAD_INT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint32' || $t eq 'bits32' || $t eq 'AclMode' || $t eq 'BlockNumber' || $t eq 'Index' || $t eq 'SubTransactionId')
+		{
+			print $off "\tWRITE_UINT_FIELD($f);\n";
+			print $rff "\tREAD_UINT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint64')
+		{
+			print $off "\tWRITE_UINT64_FIELD($f);\n";
+			print $rff "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Oid')
+		{
+			print $off "\tWRITE_OID_FIELD($f);\n";
+			print $rff "\tREAD_OID_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'long')
+		{
+			print $off "\tWRITE_LONG_FIELD($f);\n";
+			print $rff "\tREAD_LONG_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char')
+		{
+			print $off "\tWRITE_CHAR_FIELD($f);\n";
+			print $rff "\tREAD_CHAR_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'double')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.6f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cardinality')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.0f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cost')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.2f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'QualCost')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f.startup, \"%.2f\");\n";
+			print $off "\tWRITE_FLOAT_FIELD($f.per_tuple, \"%.2f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read;
+			print $rff "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
+		}
+		elsif ($t eq 'Selectivity')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.4f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $off "\tWRITE_STRING_FIELD($f);\n";
+			print $rff "\tREAD_STRING_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $off "\tWRITE_BITMAPSET_FIELD($f);\n";
+			print $rff "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
+		}
+		elsif (elem $t, @enum_types)
+		{
+			print $off "\tWRITE_ENUM_FIELD($f, $t);\n";
+			print $rff "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read;
+		}
+		# arrays
+		elsif ($t =~ /(\w+)(\*|\[)/ and elem $1, @scalar_types)
+		{
+			my $tt = uc $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $off "\tWRITE_${tt}_ARRAY($f, list_length(node->$array_size_field));\n";
+				print $rff "\tREAD_${tt}_ARRAY($f, list_length(local_node->$array_size_field));\n" unless $no_read;
+			}
+			else
+			{
+				print $off "\tWRITE_${tt}_ARRAY($f, node->$array_size_field);\n";
+				print $rff "\tREAD_${tt}_ARRAY($f, local_node->$array_size_field);\n" unless $no_read;
+			}
+		}
+		# Special treatments of several Path node fields
+		#
+		# We do not print the parent, else we'd be in infinite
+		# recursion.  We can print the parent's relids for
+		# identification purposes, though.  We print the pathtarget
+		# only if it's not the default one for the rel.  We also do
+		# not print the whole of param_info, since it's printed via
+		# RelOptInfo; it's sufficient and less cluttering to print
+		# just the required outer relids.
+		elsif ($t eq 'RelOptInfo*' && $a eq 'path_hack1')
+		{
+			print $off "\tappendStringInfoString(str, \" :parent_relids \");\n".
+			  "\toutBitmapset(str, node->$f->relids);\n";
+		}
+		elsif ($t eq 'PathTarget*' && $a eq 'path_hack2')
+		{
+			(my $f2 = $f) =~ s/pathtarget/parent/;
+			print $off "\tif (node->$f != node->$f2->reltarget)\n".
+			  "\t\tWRITE_NODE_FIELD($f);\n";
+		}
+		elsif ($t eq 'ParamPathInfo*' && $a eq 'path_hack3')
+		{
+			print $off "\tif (node->$f)\n".
+			  "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
+			  "\telse\n".
+			  "\t\toutBitmapset(str, NULL);\n";
+		}
+		# node type
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $off "\tWRITE_NODE_FIELD($f);\n";
+			print $rff "\tREAD_NODE_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $off q{
+	/* CustomName is a key to lookup CustomScanMethods */
+	appendStringInfoString(str, " :methods ");
+	outToken(str, node->methods->CustomName);
+};
+			print $rff q!
+	{
+		/* Lookup CustomScanMethods by CustomName */
+		char	   *custom_name;
+		const CustomScanMethods *methods;
+		token = pg_strtok(&length); /* skip methods: */
+		token = pg_strtok(&length); /* CustomName */
+		custom_name = nullable_string(token, length);
+		methods = GetCustomScanMethods(custom_name, false);
+		local_node->methods = methods;
+	}
+! unless $no_read;
+		}
+		# various field types to ignore
+		elsif ($t eq 'ParamListInfo' || $t =~ /PartitionBoundInfoData/ || $t eq 'PartitionDirectory' || $t eq 'PartitionScheme' || $t eq 'void*' || $t =~ /\*\*$/)
+		{
+			# ignore
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $off "}
+";
+	print $rff "
+\tREAD_DONE();
+}
+" unless $no_read;
+}
+
+close $off;
+close $rff;
+close $ofs;
+close $rfs;
+
+
+# now rename the temporary files to their final name
+foreach my $file (qw(nodetags.h copyfuncs.funcs.c copyfuncs.switch.c equalfuncs.funcs.c equalfuncs.switch.c outfuncs.funcs.c outfuncs.switch.c readfuncs.funcs.c readfuncs.switch.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6bdad462c7..f96cdfe612 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -31,11 +31,10 @@
 
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
-#include "nodes/plannodes.h"
+#include "nodes/bitmapset.h"
+#include "nodes/nodes.h"
+#include "nodes/pg_list.h"
 #include "utils/datum.h"
-#include "utils/rel.h"
 
 static void outChar(StringInfo str, char c);
 
@@ -295,6 +294,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
 }
 
 
+#include "outfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from plannodes.h
  */
@@ -1136,6 +1138,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)
@@ -1157,6 +1160,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)
 {
@@ -1327,6 +1331,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)
@@ -1355,6 +1360,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2425,6 +2431,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)
@@ -2452,6 +2459,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)
 {
@@ -2463,6 +2471,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)
@@ -2491,6 +2500,7 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
 	WRITE_UINT_FIELD(ec_max_security);
 }
 
+#ifdef OBSOLETE
 static void
 _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 {
@@ -2675,6 +2685,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
 	WRITE_NODE_FIELD(item);
 	WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2697,6 +2708,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -3030,6 +3042,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)
@@ -3102,6 +3115,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3240,6 +3254,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)
@@ -3320,6 +3335,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3343,6 +3359,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)
@@ -3461,6 +3478,7 @@ _outBitString(StringInfo str, const BitString *node)
 	appendStringInfoString(str, node->bsval);
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3492,6 +3510,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)
@@ -3508,6 +3527,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)
 {
@@ -3652,6 +3672,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)
@@ -3774,6 +3795,7 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -3834,6 +3856,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -3865,6 +3888,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;
@@ -4537,6 +4562,7 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionRangeDatum:
 				_outPartitionRangeDatum(str, obj);
 				break;
+#endif /*OBSOLETE*/
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3f68f7c18d..0b8ed866d9 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
  */
@@ -289,6 +289,7 @@ _readQuery(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readNotifyStmt
  */
@@ -587,6 +588,7 @@ _readVar(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readConst
@@ -613,6 +615,7 @@ _readConst(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readParam
  */
@@ -838,6 +841,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readBoolExpr
@@ -865,6 +869,7 @@ _readBoolExpr(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readSubLink
  */
@@ -1419,6 +1424,7 @@ _readAppendRelInfo(void)
 /*
  *	Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1514,6 +1520,7 @@ _readRangeTblEntry(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2637,6 +2644,7 @@ _readAlternativeSubPlan(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2668,6 +2676,7 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2702,6 +2711,7 @@ _readPartitionRangeDatum(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2726,7 +2736,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();
@@ -2974,6 +2988,7 @@ parseNodeString(void)
 		return_value = _readPartitionBoundSpec();
 	else if (MATCH("PARTITIONRANGEDATUM", 19))
 		return_value = _readPartitionRangeDatum();
+#endif /*OBSOLETE*/
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/include/nodes/.gitignore b/src/include/nodes/.gitignore
new file mode 100644
index 0000000000..99fb1d3787
--- /dev/null
+++ b/src/include/nodes/.gitignore
@@ -0,0 +1,2 @@
+/nodetags.h
+/header-stamp
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5d075f0c34..6d17315417 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -27,6 +27,8 @@ typedef enum NodeTag
 {
 	T_Invalid = 0,
 
+#include "nodes/nodetags.h"
+#ifdef OBSOLETE
 	/*
 	 * TAGS FOR EXECUTOR NODES (execnodes.h)
 	 */
@@ -528,8 +530,33 @@ typedef enum NodeTag
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * Used in node definitions to set extra information for gen_node_support.pl
+ *
+ * The argument is a space-separated list of attributes.  The following
+ * attributes are currently used:
+ *
+ * - array_size(OTHERFIELD): This field is a dynamically allocated array with
+ *   size indicated by the mentioned other field.  The other field is either a
+ *   scalar or a list, in which case the length of the list is used.
+ *
+ * - copy_ignore: Ignore the field for copy.
+ *
+ * - equal_ignore: Ignore the field for equality.
+ *
+ * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
+ *   (Otherwise, compare normally.)
+ *
+ * - readwrite_ignore: Ignore the field for read/write.
+ *
+ * Unknown attributes are ignored.  Some additional attributes are used for
+ * special "hack" cases.
+ */
+#define pg_node_attr(attrs)
+
 /*
  * The first field of a node of any type is guaranteed to be the NodeTag.
  * Hence the type of any node can be gotten by casting it to Node. Declaring
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 34218b718c..f4edc76d33 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -121,7 +121,8 @@ typedef struct Query
 
 	QuerySource querySource;	/* where did I come from? */
 
-	uint64		queryId;		/* query identifier (can be set by plugins) */
+	uint64		queryId pg_node_attr(equal_ignore);		/* query identifier (can be set by plugins);
+														   ignored for equal, might not be set */
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1f3845b3fe..a10a746158 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -227,7 +227,7 @@ struct PlannerInfo
 	 * GEQO.
 	 */
 	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
-	struct HTAB *join_rel_hash; /* optional hashtable for join relations */
+	struct HTAB *join_rel_hash pg_node_attr(readwrite_ignore); /* optional hashtable for join relations */
 
 	/*
 	 * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -329,10 +329,10 @@ struct PlannerInfo
 	List	   *update_colnos;
 
 	/* Fields filled during create_plan() for use in setrefs.c */
-	AttrNumber *grouping_map;	/* for GroupingFunc fixup */
+	AttrNumber *grouping_map pg_node_attr(array_size(update_colnos));	/* for GroupingFunc fixup */
 	List	   *minmax_aggs;	/* List of MinMaxAggInfos */
 
-	MemoryContext planner_cxt;	/* context holding PlannerInfo */
+	MemoryContext planner_cxt pg_node_attr(readwrite_ignore);	/* context holding PlannerInfo */
 
 	Cardinality	total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -369,8 +369,8 @@ struct PlannerInfo
 	List	   *curOuterParams; /* not-yet-assigned NestLoopParams */
 
 	/* These fields are workspace for setrefs.c */
-	bool	   *isAltSubplan;	/* array corresponding to glob->subplans */
-	bool	   *isUsedSubplan;	/* array corresponding to glob->subplans */
+	bool	   *isAltSubplan pg_node_attr(array_size(curOuterParams));	/* array corresponding to glob->subplans */
+	bool	   *isUsedSubplan pg_node_attr(array_size(curOuterParams));	/* array corresponding to glob->subplans */
 
 	/* optional private data for join_search_hook, e.g., GEQO */
 	void	   *join_search_private;
@@ -711,8 +711,8 @@ typedef struct RelOptInfo
 	RTEKind		rtekind;		/* RELATION, SUBQUERY, FUNCTION, etc */
 	AttrNumber	min_attr;		/* smallest attrno of rel (often <0) */
 	AttrNumber	max_attr;		/* largest attrno of rel */
-	Relids	   *attr_needed;	/* array indexed [min_attr .. max_attr] */
-	int32	   *attr_widths;	/* array indexed [min_attr .. max_attr] */
+	Relids	   *attr_needed pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
+	int32	   *attr_widths pg_node_attr(readwrite_ignore);	/* array indexed [min_attr .. max_attr] */
 	List	   *lateral_vars;	/* LATERAL Vars and PHVs referenced by rel */
 	Relids		lateral_referencers;	/* rels that reference me laterally */
 	List	   *indexlist;		/* list of IndexOptInfo */
@@ -733,13 +733,14 @@ typedef struct RelOptInfo
 	Oid			userid;			/* identifies user to check access as */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
-	struct FdwRoutine *fdwroutine;
-	void	   *fdw_private;
+	struct FdwRoutine *fdwroutine pg_node_attr(readwrite_ignore);
+	void	   *fdw_private pg_node_attr(readwrite_ignore);
 
-	/* cache space for remembering if we have proven this relation unique */
-	List	   *unique_for_rels;	/* known unique for these other relid
+	/* cache space for remembering if we have proven this relation unique;
+	   can't print, BMSes aren't Nodes */
+	List	   *unique_for_rels pg_node_attr(readwrite_ignore);	/* known unique for these other relid
 									 * set(s) */
-	List	   *non_unique_for_rels;	/* known not unique for these set(s) */
+	List	   *non_unique_for_rels pg_node_attr(readwrite_ignore);	/* known not unique for these set(s) */
 
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;	/* RestrictInfo structures (if base rel) */
@@ -837,7 +838,7 @@ struct IndexOptInfo
 
 	Oid			indexoid;		/* OID of the index relation */
 	Oid			reltablespace;	/* tablespace of index (not table) */
-	RelOptInfo *rel;			/* back-link to index's table */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to index's table; don't print, else infinite recursion */
 
 	/* index-size statistics (from pg_class and elsewhere) */
 	BlockNumber pages;			/* number of disk pages in index */
@@ -847,20 +848,22 @@ struct IndexOptInfo
 	/* index descriptor information */
 	int			ncolumns;		/* number of columns in index */
 	int			nkeycolumns;	/* number of key columns in index */
-	int		   *indexkeys;		/* column numbers of index's attributes both
+	/* array fields aren't really worth the trouble to print */
+	int		   *indexkeys pg_node_attr(readwrite_ignore);		/* column numbers of index's attributes both
 								 * key and included columns, or 0 */
-	Oid		   *indexcollations;	/* OIDs of collations of index columns */
-	Oid		   *opfamily;		/* OIDs of operator families for columns */
-	Oid		   *opcintype;		/* OIDs of opclass declared input data types */
-	Oid		   *sortopfamily;	/* OIDs of btree opfamilies, if orderable */
-	bool	   *reverse_sort;	/* is sort order descending? */
-	bool	   *nulls_first;	/* do NULLs come first in the sort order? */
-	bytea	  **opclassoptions; /* opclass-specific options for columns */
-	bool	   *canreturn;		/* which index cols can be returned in an
+	Oid		   *indexcollations pg_node_attr(readwrite_ignore);	/* OIDs of collations of index columns */
+	Oid		   *opfamily pg_node_attr(readwrite_ignore);		/* OIDs of operator families for columns */
+	Oid		   *opcintype pg_node_attr(readwrite_ignore);		/* OIDs of opclass declared input data types */
+	Oid		   *sortopfamily pg_node_attr(readwrite_ignore);	/* OIDs of btree opfamilies, if orderable */
+	bool	   *reverse_sort pg_node_attr(readwrite_ignore);	/* is sort order descending? */
+	bool	   *nulls_first pg_node_attr(readwrite_ignore);	/* do NULLs come first in the sort order? */
+	bytea	  **opclassoptions pg_node_attr(readwrite_ignore); /* opclass-specific options for columns */
+	bool	   *canreturn pg_node_attr(readwrite_ignore);		/* which index cols can be returned in an
 								 * index-only scan? */
 	Oid			relam;			/* OID of the access method (in pg_am) */
 
-	List	   *indexprs;		/* expressions for non-simple index columns */
+	/* indexprs is redundant to print since we print indextlist */
+	List	   *indexprs pg_node_attr(readwrite_ignore);		/* expressions for non-simple index columns */
 	List	   *indpred;		/* predicate if a partial index, else NIL */
 
 	List	   *indextlist;		/* targetlist representing index columns */
@@ -877,14 +880,14 @@ struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
-	bool		amoptionalkey;	/* can query omit key for the first column? */
-	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
-	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
-	bool		amhasgettuple;	/* does AM have amgettuple interface? */
-	bool		amhasgetbitmap; /* does AM have amgetbitmap interface? */
-	bool		amcanparallel;	/* does AM support parallel scan? */
-	bool		amcanmarkpos;	/* does AM support mark/restore? */
+	bool		amcanorderbyop pg_node_attr(readwrite_ignore); /* does AM support order by operator result? */
+	bool		amoptionalkey pg_node_attr(readwrite_ignore);	/* can query omit key for the first column? */
+	bool		amsearcharray pg_node_attr(readwrite_ignore);	/* can AM handle ScalarArrayOpExpr quals? */
+	bool		amsearchnulls pg_node_attr(readwrite_ignore);	/* can AM search for NULL/NOT NULL entries? */
+	bool		amhasgettuple pg_node_attr(readwrite_ignore);	/* does AM have amgettuple interface? */
+	bool		amhasgetbitmap pg_node_attr(readwrite_ignore); /* does AM have amgetbitmap interface? */
+	bool		amcanparallel pg_node_attr(readwrite_ignore);	/* does AM support parallel scan? */
+	bool		amcanmarkpos pg_node_attr(readwrite_ignore);	/* does AM support mark/restore? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
 };
@@ -905,9 +908,9 @@ typedef struct ForeignKeyOptInfo
 	Index		con_relid;		/* RT index of the referencing table */
 	Index		ref_relid;		/* RT index of the referenced table */
 	int			nkeys;			/* number of columns in the foreign key */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 
 	/* Derived info about whether FK's equality conditions match the query: */
 	int			nmatched_ec;	/* # of FK cols matched by ECs */
@@ -934,8 +937,9 @@ typedef struct StatisticExtInfo
 	NodeTag		type;
 
 	Oid			statOid;		/* OID of the statistics row */
-	bool		inherit;		/* includes child relations */
-	RelOptInfo *rel;			/* back-link to statistic's table */
+	bool		inherit pg_node_attr(readwrite_ignore);		/* includes child relations */
+	RelOptInfo *rel pg_node_attr(readwrite_ignore);			/* back-link to statistic's table;
+															   don't print, infinite recursion on plan tree dump */
 	char		kind;			/* statistics kind of this entry */
 	Bitmapset  *keys;			/* attnums of the columns covered */
 	List	   *exprs;			/* expressions */
@@ -1109,7 +1113,7 @@ typedef struct PathTarget
 {
 	NodeTag		type;
 	List	   *exprs;			/* list of expressions to be computed */
-	Index	   *sortgrouprefs;	/* corresponding sort/group refnos, or 0 */
+	Index	   *sortgrouprefs pg_node_attr(array_size(exprs));	/* corresponding sort/group refnos, or 0 */
 	QualCost	cost;			/* cost of evaluating the expressions */
 	int			width;			/* estimated avg width of result tuples */
 	VolatileFunctionStatus has_volatile_expr;	/* indicates if exprs contain
@@ -1180,10 +1184,10 @@ typedef struct Path
 
 	NodeTag		pathtype;		/* tag identifying scan/join method */
 
-	RelOptInfo *parent;			/* the relation this path can build */
-	PathTarget *pathtarget;		/* list of Vars/Exprs, cost, width */
+	RelOptInfo *parent pg_node_attr(path_hack1);			/* the relation this path can build */
+	PathTarget *pathtarget pg_node_attr(path_hack2);		/* list of Vars/Exprs, cost, width */
 
-	ParamPathInfo *param_info;	/* parameterization info, or NULL if none */
+	ParamPathInfo *param_info pg_node_attr(path_hack3);	/* parameterization info, or NULL if none */
 
 	bool		parallel_aware; /* engage parallel-aware logic? */
 	bool		parallel_safe;	/* OK to use as part of parallel plan? */
@@ -2050,6 +2054,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
@@ -2062,19 +2072,19 @@ typedef struct RestrictInfo
 
 	bool		outerjoin_delayed;	/* true if delayed by lower outer join */
 
-	bool		can_join;		/* see comment above */
+	bool		can_join pg_node_attr(equal_ignore);		/* see comment above */
 
-	bool		pseudoconstant; /* see comment above */
+	bool		pseudoconstant pg_node_attr(equal_ignore); /* see comment above */
 
-	bool		leakproof;		/* true if known to contain no leaked Vars */
+	bool		leakproof pg_node_attr(equal_ignore);		/* true if known to contain no leaked Vars */
 
-	VolatileFunctionStatus has_volatile;	/* to indicate if clause contains
+	VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);	/* to indicate if clause contains
 											 * any volatile functions. */
 
 	Index		security_level; /* see comment above */
 
 	/* The set of relids (varnos) actually referenced in the clause: */
-	Relids		clause_relids;
+	Relids		clause_relids pg_node_attr(equal_ignore);
 
 	/* The set of relids required to evaluate the clause: */
 	Relids		required_relids;
@@ -2086,48 +2096,54 @@ typedef struct RestrictInfo
 	Relids		nullable_relids;
 
 	/* These fields are set for any binary opclause: */
-	Relids		left_relids;	/* relids in left side of clause */
-	Relids		right_relids;	/* relids in right side of clause */
+	Relids		left_relids pg_node_attr(equal_ignore);	/* relids in left side of clause */
+	Relids		right_relids pg_node_attr(equal_ignore);	/* relids in right side of clause */
 
 	/* This field is NULL unless clause is an OR clause: */
-	Expr	   *orclause;		/* modified clause with RestrictInfos */
+	Expr	   *orclause pg_node_attr(equal_ignore);		/* modified clause with RestrictInfos */
 
 	/* This field is NULL unless clause is potentially redundant: */
-	EquivalenceClass *parent_ec;	/* generating EquivalenceClass */
+	EquivalenceClass *parent_ec pg_node_attr(equal_ignore readwrite_ignore);	/* generating EquivalenceClass */
 
 	/* cache space for cost and selectivity */
-	QualCost	eval_cost;		/* eval cost of clause; -1 if not yet set */
-	Selectivity norm_selec;		/* selectivity for "normal" (JOIN_INNER)
+	QualCost	eval_cost pg_node_attr(equal_ignore);		/* eval cost of clause; -1 if not yet set */
+	Selectivity norm_selec pg_node_attr(equal_ignore);		/* selectivity for "normal" (JOIN_INNER)
 								 * semantics; -1 if not yet set; >1 means a
 								 * redundant clause */
-	Selectivity outer_selec;	/* selectivity for outer join semantics; -1 if
+	Selectivity outer_selec pg_node_attr(equal_ignore);	/* selectivity for outer join semantics; -1 if
 								 * not yet set */
 
 	/* valid if clause is mergejoinable, else NIL */
-	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *mergeopfamilies pg_node_attr(equal_ignore);	/* opfamilies containing clause operator */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
-	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
-	EquivalenceClass *right_ec; /* EquivalenceClass containing righthand */
-	EquivalenceMember *left_em; /* EquivalenceMember for lefthand */
-	EquivalenceMember *right_em;	/* EquivalenceMember for righthand */
-	List	   *scansel_cache;	/* list of MergeScanSelCache structs */
+	EquivalenceClass *left_ec pg_node_attr(equal_ignore readwrite_ignore);	/* EquivalenceClass containing lefthand */
+	EquivalenceClass *right_ec pg_node_attr(equal_ignore readwrite_ignore); /* EquivalenceClass containing righthand */
+	EquivalenceMember *left_em pg_node_attr(equal_ignore); /* EquivalenceMember for lefthand */
+	EquivalenceMember *right_em pg_node_attr(equal_ignore);	/* EquivalenceMember for righthand */
+
+	/*
+	 * List of MergeScanSelCache structs.  Those aren't Nodes, so hard to
+	 * copy.  Ignoring it will have the effect that copying will just reset
+	 * the cache.
+	 */
+	List	   *scansel_cache pg_node_attr(copy_ignore equal_ignore);
 
 	/* transient workspace for use while considering a specific join path */
-	bool		outer_is_left;	/* T = outer var on left, F = on right */
+	bool		outer_is_left pg_node_attr(equal_ignore);	/* T = outer var on left, F = on right */
 
 	/* valid if clause is hashjoinable, else InvalidOid: */
-	Oid			hashjoinoperator;	/* copy of clause operator */
+	Oid			hashjoinoperator pg_node_attr(equal_ignore);	/* copy of clause operator */
 
 	/* cache space for hashclause processing; -1 if not yet set */
-	Selectivity left_bucketsize;	/* avg bucketsize of left side */
-	Selectivity right_bucketsize;	/* avg bucketsize of right side */
-	Selectivity left_mcvfreq;	/* left side's most common val's freq */
-	Selectivity right_mcvfreq;	/* right side's most common val's freq */
+	Selectivity left_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of left side */
+	Selectivity right_bucketsize pg_node_attr(equal_ignore);	/* avg bucketsize of right side */
+	Selectivity left_mcvfreq pg_node_attr(equal_ignore);	/* left side's most common val's freq */
+	Selectivity right_mcvfreq pg_node_attr(equal_ignore);	/* right side's most common val's freq */
 
 	/* hash equality operators used for memoize nodes, else InvalidOid */
-	Oid			left_hasheqoperator;
-	Oid			right_hasheqoperator;
+	Oid			left_hasheqoperator pg_node_attr(equal_ignore);
+	Oid			right_hasheqoperator pg_node_attr(equal_ignore);
 } RestrictInfo;
 
 /*
@@ -2177,13 +2193,24 @@ typedef struct MergeScanSelCache
  * Although the planner treats this as an expression node type, it is not
  * recognized by the parser or executor, so we declare it here rather than
  * in primnodes.h.
+ *
+ * We intentionally do not compare phexpr.  Two PlaceHolderVars with the
+ * same ID and levelsup should be considered equal even if the contained
+ * expressions have managed to mutate to different states.  This will
+ * happen during final plan construction when there are nested PHVs, since
+ * the inner PHV will get replaced by a Param in some copies of the outer
+ * PHV.  Another way in which it can happen is that initplan sublinks
+ * could get replaced by differently-numbered Params when sublink folding
+ * is done.  (The end result of such a situation would be some
+ * unreferenced initplans, which is annoying but not really a problem.) On
+ * the same reasoning, there is no need to examine phrels.
  */
 
 typedef struct PlaceHolderVar
 {
 	Expr		xpr;
-	Expr	   *phexpr;			/* the represented expression */
-	Relids		phrels;			/* base relids syntactically within expr src */
+	Expr	   *phexpr pg_node_attr(equal_ignore);			/* the represented expression */
+	Relids		phrels pg_node_attr(equal_ignore);			/* base relids syntactically within expr src */
 	Index		phid;			/* ID for PHV (unique within planner run) */
 	Index		phlevelsup;		/* > 0 if PHV belongs to outer query */
 } PlaceHolderVar;
@@ -2344,7 +2371,7 @@ typedef struct AppendRelInfo
 	 * child column is dropped or doesn't exist in the parent.
 	 */
 	int			num_child_cols; /* length of array */
-	AttrNumber *parent_colnos;	/* array of parent attnos, or zeroes */
+	AttrNumber *parent_colnos pg_node_attr(array_size(num_child_cols));	/* array of parent attnos, or zeroes */
 
 	/*
 	 * We store the parent table's OID here for inheritance, or InvalidOid for
@@ -2413,7 +2440,7 @@ typedef struct PlaceHolderInfo
 	NodeTag		type;
 
 	Index		phid;			/* ID for PH (unique within planner run) */
-	PlaceHolderVar *ph_var;		/* copy of PlaceHolderVar tree */
+	PlaceHolderVar *ph_var;		/* copy of PlaceHolderVar tree (should be redundant for comparison, could be ignored) */
 	Relids		ph_eval_at;		/* lowest level we can evaluate value at */
 	Relids		ph_lateral;		/* relids of contained lateral refs, if any */
 	Relids		ph_needed;		/* highest level the value is needed at */
@@ -2432,7 +2459,8 @@ typedef struct MinMaxAggInfo
 	Oid			aggfnoid;		/* pg_proc Oid of the aggregate */
 	Oid			aggsortop;		/* Oid of its sort operator */
 	Expr	   *target;			/* expression we are aggregating on */
-	PlannerInfo *subroot;		/* modified "root" for planning the subquery */
+	PlannerInfo *subroot pg_node_attr(readwrite_ignore);		/* modified "root" for planning the subquery;
+																   not printed, too large, not interesting enough */
 	Path	   *path;			/* access path for subquery */
 	Cost		pathcost;		/* estimated cost to fetch first row */
 	Param	   *param;			/* param for subplan's output */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0b518ce6b2..d2d372a5d0 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -275,10 +275,10 @@ typedef struct MergeAppend
 	List	   *mergeplans;
 	/* these fields are just like the sort-key info in struct Sort: */
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 	/* Info for run-time subplan pruning; NULL if we're not doing that */
 	struct PartitionPruneInfo *part_prune_info;
 } MergeAppend;
@@ -298,9 +298,9 @@ typedef struct RecursiveUnion
 	/* Remaining fields are zero/null in UNION ALL case */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
-	Oid		   *dupCollations;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 	long		numGroups;		/* estimated number of groups in input */
 } RecursiveUnion;
 
@@ -765,10 +765,10 @@ typedef struct MergeJoin
 	bool		skip_mark_restore;	/* Can we skip mark/restore calls? */
 	List	   *mergeclauses;	/* mergeclauses as expression trees */
 	/* these are arrays, but have the same length as the mergeclauses list: */
-	Oid		   *mergeFamilies;	/* per-clause OIDs of btree opfamilies */
-	Oid		   *mergeCollations;	/* per-clause OIDs of collations */
-	int		   *mergeStrategies;	/* per-clause ordering (ASC or DESC) */
-	bool	   *mergeNullsFirst;	/* per-clause nulls ordering */
+	Oid		   *mergeFamilies pg_node_attr(array_size(mergeclauses));	/* per-clause OIDs of btree opfamilies */
+	Oid		   *mergeCollations pg_node_attr(array_size(mergeclauses));	/* per-clause OIDs of collations */
+	int		   *mergeStrategies pg_node_attr(array_size(mergeclauses));	/* per-clause ordering (ASC or DESC) */
+	bool	   *mergeNullsFirst pg_node_attr(array_size(mergeclauses));	/* per-clause nulls ordering */
 } MergeJoin;
 
 /* ----------------
@@ -808,8 +808,8 @@ typedef struct Memoize
 
 	int			numKeys;		/* size of the two arrays below */
 
-	Oid		   *hashOperators;	/* hash operators for each key */
-	Oid		   *collations;		/* cache keys */
+	Oid		   *hashOperators pg_node_attr(array_size(numKeys));	/* hash operators for each key */
+	Oid		   *collations pg_node_attr(array_size(numKeys));		/* cache keys */
 	List	   *param_exprs;	/* exprs containing parameters */
 	bool		singlerow;		/* true if the cache entry should be marked as
 								 * complete after we store the first tuple in
@@ -830,10 +830,10 @@ typedef struct Sort
 {
 	Plan		plan;
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 } Sort;
 
 /* ----------------
@@ -856,9 +856,9 @@ typedef struct Group
 {
 	Plan		plan;
 	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
-	Oid		   *grpCollations;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 } Group;
 
 /* ---------------
@@ -881,9 +881,9 @@ typedef struct Agg
 	AggStrategy aggstrategy;	/* basic strategy, see nodes.h */
 	AggSplit	aggsplit;		/* agg-splitting mode, see nodes.h */
 	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
-	Oid		   *grpCollations;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 	long		numGroups;		/* estimated number of groups in input */
 	uint64		transitionSpace;	/* for pass-by-ref transition data */
 	Bitmapset  *aggParams;		/* IDs of Params used in Aggref inputs */
@@ -901,13 +901,13 @@ typedef struct WindowAgg
 	Plan		plan;
 	Index		winref;			/* ID referenced by window functions */
 	int			partNumCols;	/* number of columns in partition clause */
-	AttrNumber *partColIdx;		/* their indexes in the target list */
-	Oid		   *partOperators;	/* equality operators for partition columns */
-	Oid		   *partCollations; /* collations for partition columns */
+	AttrNumber *partColIdx pg_node_attr(array_size(partNumCols));		/* their indexes in the target list */
+	Oid		   *partOperators pg_node_attr(array_size(partNumCols));	/* equality operators for partition columns */
+	Oid		   *partCollations pg_node_attr(array_size(partNumCols)); /* collations for partition columns */
 	int			ordNumCols;		/* number of columns in ordering clause */
-	AttrNumber *ordColIdx;		/* their indexes in the target list */
-	Oid		   *ordOperators;	/* equality operators for ordering columns */
-	Oid		   *ordCollations;	/* collations for ordering columns */
+	AttrNumber *ordColIdx pg_node_attr(array_size(ordNumCols));		/* their indexes in the target list */
+	Oid		   *ordOperators pg_node_attr(array_size(ordNumCols));	/* equality operators for ordering columns */
+	Oid		   *ordCollations pg_node_attr(array_size(ordNumCols));	/* collations for ordering columns */
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
@@ -927,9 +927,9 @@ typedef struct Unique
 {
 	Plan		plan;
 	int			numCols;		/* number of columns to check for uniqueness */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+	AttrNumber *uniqColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *uniqOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *uniqCollations pg_node_attr(array_size(numCols)); /* collations for equality comparisons */
 } Unique;
 
 /* ------------
@@ -965,10 +965,10 @@ typedef struct GatherMerge
 	int			rescan_param;	/* ID of Param that signals a rescan, or -1 */
 	/* remaining fields are just like the sort-key info in struct Sort */
 	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));	/* OIDs of operators to sort them by */
+	Oid		   *collations pg_node_attr(array_size(numCols));		/* OIDs of collations */
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));		/* NULLS FIRST/LAST directions */
 	Bitmapset  *initParam;		/* param id's of initplans which are referred
 								 * at gather merge or one of it's child node */
 } GatherMerge;
@@ -1008,9 +1008,9 @@ typedef struct SetOp
 	SetOpStrategy strategy;		/* how to do it, see nodes.h */
 	int			numCols;		/* number of columns to check for
 								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
-	Oid		   *dupCollations;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));		/* their indexes in the target list */
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));	/* equality operators to compare with */
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 	AttrNumber	flagColIdx;		/* where is the flag column, if any */
 	int			firstFlag;		/* flag value for first input relation */
 	long		numGroups;		/* estimated number of groups in input */
@@ -1046,9 +1046,9 @@ typedef struct Limit
 	Node	   *limitCount;		/* COUNT parameter, or NULL if none */
 	LimitOption limitOption;	/* limit type */
 	int			uniqNumCols;	/* number of columns to check for similarity  */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+	AttrNumber *uniqColIdx pg_node_attr(array_size(uniqNumCols));		/* their indexes in the target list */
+	Oid		   *uniqOperators pg_node_attr(array_size(uniqNumCols));	/* equality operators to compare with */
+	Oid		   *uniqCollations pg_node_attr(array_size(uniqNumCols)); /* collations for equality comparisons */
 } Limit;
 
 
@@ -1207,9 +1207,9 @@ typedef struct PartitionedRelPruneInfo
 	Bitmapset  *present_parts;	/* Indexes of all partitions which subplans or
 								 * subparts are present for */
 	int			nparts;			/* Length of the following arrays: */
-	int		   *subplan_map;	/* subplan index by partition index, or -1 */
-	int		   *subpart_map;	/* subpart index by partition index, or -1 */
-	Oid		   *relid_map;		/* relation OID by partition index, or 0 */
+	int		   *subplan_map pg_node_attr(array_size(nparts));	/* subplan index by partition index, or -1 */
+	int		   *subpart_map pg_node_attr(array_size(nparts));	/* subpart index by partition index, or -1 */
+	Oid		   *relid_map pg_node_attr(array_size(nparts));		/* relation OID by partition index, or 0 */
 
 	/*
 	 * initial_pruning_steps shows how to prune during executor startup (i.e.,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index dab5c4ff5d..eb003a7bd7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -63,7 +63,9 @@ typedef enum OnCommitAction
 typedef struct RangeVar
 {
 	NodeTag		type;
-	char	   *catalogname;	/* the catalog (database) name, or NULL */
+	/* the catalog (database) name, or NULL; ignored for read/write, since it
+	 * is presently not semantically meaningful */
+	char	   *catalogname pg_node_attr(readwrite_ignore);
 	char	   *schemaname;		/* the schema name, or NULL */
 	char	   *relname;		/* the relation/sequence name */
 	bool		inh;			/* expand rel by inheritance? recursively act
@@ -196,8 +198,15 @@ typedef struct Var
 	Index		varlevelsup;	/* for subquery variables referencing outer
 								 * relations; 0 in a normal var, >0 means N
 								 * levels up */
-	Index		varnosyn;		/* syntactic relation index (0 if unknown) */
-	AttrNumber	varattnosyn;	/* syntactic attribute number */
+
+	/*
+	 * varnosyn/varattnosyn are ignored for equality, because Vars with
+	 * different syntactic identifiers are semantically the same as long as
+	 * their varno/varattno match.
+	 */
+	Index		varnosyn pg_node_attr(equal_ignore);		/* syntactic relation index (0 if unknown) */
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore);	/* syntactic attribute number */
+
 	int			location;		/* token location, or -1 if unknown */
 } Var;
 
@@ -324,7 +333,7 @@ typedef struct Aggref
 	Oid			aggtype;		/* type Oid of result of the aggregate */
 	Oid			aggcollid;		/* OID of collation of result */
 	Oid			inputcollid;	/* OID of collation that function should use */
-	Oid			aggtranstype;	/* type Oid of aggregate's transition value */
+	Oid			aggtranstype pg_node_attr(equal_ignore);	/* type Oid of aggregate's transition value; ignored for equal since it might not be set yet */
 	List	   *aggargtypes;	/* type Oids of direct and aggregated args */
 	List	   *aggdirectargs;	/* direct arguments, if an ordered-set agg */
 	List	   *args;			/* aggregated arguments and sort expressions */
@@ -371,8 +380,8 @@ typedef struct GroupingFunc
 	Expr		xpr;
 	List	   *args;			/* arguments, not evaluated but kept for
 								 * benefit of EXPLAIN etc. */
-	List	   *refs;			/* ressortgrouprefs of arguments */
-	List	   *cols;			/* actual column positions set by planner */
+	List	   *refs pg_node_attr(equal_ignore);			/* ressortgrouprefs of arguments */
+	List	   *cols pg_node_attr(equal_ignore);			/* actual column positions set by planner */
 	Index		agglevelsup;	/* same as Aggref.agglevelsup */
 	int			location;		/* token location */
 } GroupingFunc;
@@ -540,7 +549,7 @@ typedef struct OpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of underlying function */
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of underlying function */
 	Oid			opresulttype;	/* PG_TYPE OID of result value */
 	bool		opretset;		/* true if operator returns set */
 	Oid			opcollid;		/* OID of collation of result */
@@ -592,14 +601,18 @@ typedef OpExpr NullIfExpr;
  * corresponding function and won't be used during execution.  For
  * non-hashtable based NOT INs, negfuncid will be set to InvalidOid.  See
  * convert_saop_to_hashed_saop().
+ *
+ * Similar to OpExpr, opfuncid, hashfuncid, and negfuncid are not necessarily
+ * filled in right away, so will be ignored for equality if they are not set
+ * yet.
  */
 typedef struct ScalarArrayOpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of comparison function */
-	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
-	Oid			negfuncid;		/* PG_PROC OID of negator of opfuncid function
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of comparison function */
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of hash func or InvalidOid */
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);		/* PG_PROC OID of negator of opfuncid function
 								 * or InvalidOid.  See above */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 6da1b220cd..459a64a992 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -273,9 +273,9 @@ typedef struct ForeignKeyCacheInfo
 	Oid			confrelid;		/* relation referenced by the foreign key */
 	int			nkeys;			/* number of columns in the foreign key */
 	/* these arrays each have nkeys valid entries: */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 } ForeignKeyCacheInfo;
 
 
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index e6f20679dc..87d6c0bec1 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -833,6 +833,52 @@ EOF
 		close($chs);
 	}
 
+	if (IsNewer('src/backend/nodes/node-support-stamp',
+		'src/backend/nodes/gen_node_support.pl'))
+	{
+		# XXX duplicates src/backend/nodes/Makefile
+
+		my @node_headers = qw(
+			nodes/nodes.h
+			nodes/execnodes.h
+			nodes/plannodes.h
+			nodes/primnodes.h
+			nodes/pathnodes.h
+			nodes/extensible.h
+			nodes/parsenodes.h
+			nodes/replnodes.h
+			nodes/value.h
+			commands/trigger.h
+			commands/event_trigger.h
+			foreign/fdwapi.h
+			access/amapi.h
+			access/tableam.h
+			access/tsmapi.h
+			utils/rel.h
+			nodes/supportnodes.h
+			executor/tuptable.h
+			nodes/lockoptions.h
+			access/sdir.h
+		);
+
+		chdir('src/backend/nodes');
+
+		my @node_files = map { "../../../src/include/$_" } @node_headers;
+
+		system("perl gen_node_support.pl @node_files");
+		open(my $f, '>', 'node-support-stamp') || confess "Could not touch node-support-stamp";
+		close($f);
+		chdir('../../..');
+	}
+
+	if (IsNewer(
+			'src/include/nodes/nodetags.h',
+			'src/backend/nodes/nodetags.h'))
+	{
+		copyFile('src/backend/nodes/nodetags.h',
+			'src/include/nodes/nodetags.h');
+	}
+
 	open(my $o, '>', "doc/src/sgml/version.sgml")
 	  || croak "Could not write to version.sgml\n";
 	print $o <<EOF;

base-commit: 19252e8ec938bf07897c1519f367d0467a39242c
-- 
2.35.1

#28David Rowley
dgrowleyml@gmail.com
In reply to: Peter Eisentraut (#27)
Re: automatically generating node support functions

On Fri, 18 Feb 2022 at 19:52, Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:

[ v5-0001-Automatically-generate-node-support-functions.patch ]

I've been looking over the patch and wondering the best way to move
this forward.

But first a couple of things I noted down from reading the patch:

1. You're written:

* Unknown attributes are ignored. Some additional attributes are used for
* special "hack" cases.

I think these really should all be documented. If someone needs to
use one of these hacks then they're going to need to trawl through
Perl code to see if you've implemented something that matches the
requirements. I'd personally rather not have to look at the Perl code
to find out which attributes I need to use for my new field. I'd bet
I'm not the only one.

2. Some of these comment lines have become pretty long after having
added the attribute macro.

e.g.

PlannerInfo *subroot pg_node_attr(readwrite_ignore); /* modified
"root" for planning the subquery;
not printed, too large, not interesting enough */

I wonder if you'd be better to add a blank line above, then put the
comment on its own line, i.e:

/* modified "root" for planning the subquery; not printed, too large,
not interesting enough */
PlannerInfo *subroot pg_node_attr(readwrite_ignore);

3. My biggest concern with this patch is it introducing some change in
behaviour with node copy/equal/read/write. I spent some time in my
diff tool comparing the files the Perl script built to the existing
code. Unfortunately, that job is pretty hard due to various order
changes in the outputted functions. I wonder if it's worth making a
pass in master and changing the function order to match what the
script outputs so that a proper comparison can be done just before
committing the patch. The problem I see is that master is currently
a very fast-moving target and a detailed comparison would be much
easier to do if the functions were in the same order. I'd be a bit
worried that someone might commit something that requires some special
behaviour and that commit goes in sometime between when you've done a
detailed and when you commit the full patch.

Although, perhaps you've just been copying and pasting code into the
correct order before comparing, which might be good enough if it's
simple enough to do.

I've not really done any detailed review of the Perl code. I'm not the
best person for that, but I do feel like the important part is making
sure the outputted files logically match the existing files.

Also, I'm quite keen to see this work make it into v15. Do you think
you'll get time to do that? Thanks for working on it.

David

#29Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: David Rowley (#28)
Re: automatically generating node support functions

On 24.03.22 22:57, David Rowley wrote:

* Unknown attributes are ignored. Some additional attributes are used for
* special "hack" cases.

I think these really should all be documented. If someone needs to
use one of these hacks then they're going to need to trawl through
Perl code to see if you've implemented something that matches the
requirements. I'd personally rather not have to look at the Perl code
to find out which attributes I need to use for my new field. I'd bet
I'm not the only one.

The only such hacks are the three path_hack[1-3] cases that correspond
to the current _outPathInfo(). I've been thinking long and hard about
how to generalize any of these but couldn't come up with much yet. I
suppose we could replace the names "path_hackN" with something more
descriptive like "reloptinfo_light" and document those in nodes.h, which
might address your concern on paper. But I think you'd still need to
understand all of that by looking at the definition of Path and its
uses, so documenting those in nodes.h wouldn't really help, I think.
Other ideas welcome.

2. Some of these comment lines have become pretty long after having
added the attribute macro.

e.g.

PlannerInfo *subroot pg_node_attr(readwrite_ignore); /* modified
"root" for planning the subquery;
not printed, too large, not interesting enough */

I wonder if you'd be better to add a blank line above, then put the
comment on its own line, i.e:

/* modified "root" for planning the subquery; not printed, too large,
not interesting enough */
PlannerInfo *subroot pg_node_attr(readwrite_ignore);

Yes, my idea was to make a separate patch first that reformats many of
the structs and comments in that way.

3. My biggest concern with this patch is it introducing some change in
behaviour with node copy/equal/read/write. I spent some time in my
diff tool comparing the files the Perl script built to the existing
code. Unfortunately, that job is pretty hard due to various order
changes in the outputted functions. I wonder if it's worth making a
pass in master and changing the function order to match what the
script outputs so that a proper comparison can be done just before
committing the patch.

Just reordering won't really help. The content of the functions will be
different, for example because nodes that include Path will include its
fields inline instead of calling out to _outPathInfo().

IMO, the confirmation that it works is in COPY_PARSE_PLAN_TREES etc.

The problem I see is that master is currently
a very fast-moving target and a detailed comparison would be much
easier to do if the functions were in the same order. I'd be a bit
worried that someone might commit something that requires some special
behaviour and that commit goes in sometime between when you've done a
detailed and when you commit the full patch.

Also, I'm quite keen to see this work make it into v15. Do you think
you'll get time to do that? Thanks for working on it.

My thinking right now is to wait for the PG16 branch to open and then
consider putting it in early. That would avoid creating massive
conflicts with concurrent patches that change node types, and it would
also relax some concerns about undiscovered behavior changes.

If there is interest in getting it into PG15, I do have capacity to work
on it. But in my estimation, this feature is more useful for future
development, so squeezing in just before feature freeze wouldn't provide
additional benefit.

#30Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#29)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

On 24.03.22 22:57, David Rowley wrote:

Also, I'm quite keen to see this work make it into v15. Do you think
you'll get time to do that? Thanks for working on it.

My thinking right now is to wait for the PG16 branch to open and then
consider putting it in early.

+1. However, as noted by David (and I think I made similar points awhile
ago), the patch could still use a lot of mop-up work. It'd be prudent to
continue working on it so it will actually be ready to go when the branch
is made.

regards, tom lane

#31Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#30)
Re: automatically generating node support functions

On 25.03.22 14:32, Tom Lane wrote:

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

On 24.03.22 22:57, David Rowley wrote:

Also, I'm quite keen to see this work make it into v15. Do you think
you'll get time to do that? Thanks for working on it.

My thinking right now is to wait for the PG16 branch to open and then
consider putting it in early.

+1. However, as noted by David (and I think I made similar points awhile
ago), the patch could still use a lot of mop-up work. It'd be prudent to
continue working on it so it will actually be ready to go when the branch
is made.

The v5 patch was intended to address all the comments you made in your
Feb. 14 mail. I'm not aware of any open issues from that.

#32Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#27)
2 attachment(s)
Re: automatically generating node support functions

I rebased this mostly out of curiousity. I fixed some smallish
conflicts and fixed a typedef problem new in JSON support; however, even
with these fixes it doesn't compile, because JsonPathSpec uses a novel
typedef pattern that apparently will need bespoke handling in the
gen_nodes_support.pl script. It seemed better to post this even without
that, though.

--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"El miedo atento y previsor es la madre de la seguridad" (E. Burke)

Attachments:

0001-Complete-JsonTableColumnType-typedef.patchtext/x-diff; charset=utf-8Download
From 56cf5918f1a2abcb285ad2d4d880779eaed388d0 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 19 Apr 2022 13:36:21 +0200
Subject: [PATCH 1/2] Complete JsonTableColumnType typedef

---
 src/include/nodes/parsenodes.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index da02658c81..b1f81feb46 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1610,7 +1610,7 @@ typedef enum JsonQuotes
  * JsonTableColumnType -
  *		enumeration of JSON_TABLE column types
  */
-typedef enum
+typedef enum JsonTableColumnType
 {
 	JTC_FOR_ORDINALITY,
 	JTC_REGULAR,
-- 
2.30.2

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

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

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

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

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

Subtyping (TidScan -> Scan) is supported.

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

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

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

#33Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#32)
Re: automatically generating node support functions

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

I rebased this mostly out of curiousity. I fixed some smallish
conflicts and fixed a typedef problem new in JSON support; however, even
with these fixes it doesn't compile, because JsonPathSpec uses a novel
typedef pattern that apparently will need bespoke handling in the
gen_nodes_support.pl script. It seemed better to post this even without
that, though.

Maybe we should fix JsonPathSpec to be less creative while we
still can? It's not real clear to me why that typedef even exists,
rather than using a String node, or just a plain char * field.

regards, tom lane

#34Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#33)
Re: automatically generating node support functions

On 19.04.22 16:39, Tom Lane wrote:

Maybe we should fix JsonPathSpec to be less creative while we
still can? It's not real clear to me why that typedef even exists,
rather than using a String node, or just a plain char * field.

Yeah, let's get rid of it and use char *.

I see in JsonCommon a pathspec is converted to a String node, so it's
not like JsonPathSpec is some kind of universal representation of the
thing anyway.

#35Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Alvaro Herrera (#32)
Re: automatically generating node support functions

On 19.04.22 13:40, Alvaro Herrera wrote:

I rebased this mostly out of curiousity. I fixed some smallish
conflicts and fixed a typedef problem new in JSON support; however, even
with these fixes it doesn't compile, because JsonPathSpec uses a novel
typedef pattern that apparently will need bespoke handling in the
gen_nodes_support.pl script. It seemed better to post this even without
that, though.

I have committed your change to the JsonTableColumnType enum and the
removal of JsonPathSpec. Other than that and some whitespace changes, I
didn't find anything in your 0002 patch that was different from my last
submitted patch. Did I miss anything?

#36Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#35)
Re: automatically generating node support functions

On 2022-May-04, Peter Eisentraut wrote:

I have committed your change to the JsonTableColumnType enum and the removal
of JsonPathSpec.

Thanks!

Other than that and some whitespace changes, I didn't find anything in
your 0002 patch that was different from my last submitted patch. Did
I miss anything?

No, I had just fixed one simple conflict IIRC, but I had made no other
changes.

--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"Porque francamente, si para saber manejarse a uno mismo hubiera que
rendir examen... ¿Quién es el machito que tendría carnet?" (Mafalda)

#37Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Peter Eisentraut (#29)
1 attachment(s)
Re: automatically generating node support functions

On 25.03.22 14:08, Peter Eisentraut wrote:

2. Some of these comment lines have become pretty long after having
added the attribute macro.

e.g.

PlannerInfo *subroot pg_node_attr(readwrite_ignore); /* modified
"root" for planning the subquery;
    not printed, too large, not interesting enough */

I wonder if you'd be better to add a blank line above, then put the
comment on its own line, i.e:

  /* modified "root" for planning the subquery; not printed, too large,
not interesting enough */
PlannerInfo *subroot pg_node_attr(readwrite_ignore);

Yes, my idea was to make a separate patch first that reformats many of
the structs and comments in that way.

Here is a patch that reformats the relevant (and a few more) comments
that way. This has been run through pgindent, so the formatting should
be stable.

Attachments:

0001-Reformat-node-comments.patchtext/plain; charset=UTF-8; name=0001-Reformat-node-comments.patchDownload
From 5eea69417e524779e2e3cc5164966646cb2c2c0e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 23 May 2022 07:40:12 +0200
Subject: [PATCH] Reformat node comments

---
 src/include/nodes/parsenodes.h |   3 +-
 src/include/nodes/pathnodes.h  | 686 ++++++++++++++++++++++-----------
 src/include/nodes/plannodes.h  | 423 ++++++++++++++------
 src/include/nodes/primnodes.h  | 166 +++++---
 4 files changed, 899 insertions(+), 379 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 73f635b455..f93d866548 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -123,7 +123,8 @@ typedef struct Query
 
 	QuerySource querySource;	/* where did I come from? */
 
-	uint64		queryId;		/* query identifier (can be set by plugins) */
+	/* query identifier (can be set by plugins) */
+	uint64		queryId;
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index a6e5db4eec..b88cfb8dc0 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -226,8 +226,8 @@ struct PlannerInfo
 	 * even when using the hash table for lookups; this simplifies life for
 	 * GEQO.
 	 */
-	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
-	struct HTAB *join_rel_hash; /* optional hashtable for join relations */
+	List	   *join_rel_list;
+	struct HTAB *join_rel_hash;
 
 	/*
 	 * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -329,11 +329,16 @@ struct PlannerInfo
 	 */
 	List	   *update_colnos;
 
-	/* Fields filled during create_plan() for use in setrefs.c */
-	AttrNumber *grouping_map;	/* for GroupingFunc fixup */
-	List	   *minmax_aggs;	/* List of MinMaxAggInfos */
+	/*
+	 * Fields filled during create_plan() for use in setrefs.c
+	 */
+	/* for GroupingFunc fixup */
+	AttrNumber *grouping_map;
+	/* List of MinMaxAggInfos */
+	List	   *minmax_aggs;
 
-	MemoryContext planner_cxt;	/* context holding PlannerInfo */
+	/* context holding PlannerInfo */
+	MemoryContext planner_cxt;
 
 	Cardinality total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -369,9 +374,12 @@ struct PlannerInfo
 	Relids		curOuterRels;	/* outer rels above current node */
 	List	   *curOuterParams; /* not-yet-assigned NestLoopParams */
 
-	/* These fields are workspace for setrefs.c */
-	bool	   *isAltSubplan;	/* array corresponding to glob->subplans */
-	bool	   *isUsedSubplan;	/* array corresponding to glob->subplans */
+	/*
+	 * These fields are workspace for setrefs.c.  Each is an array
+	 * corresponding to glob->subplans.
+	 */
+	bool	   *isAltSubplan;
+	bool	   *isUsedSubplan;
 
 	/* optional private data for join_search_hook, e.g., GEQO */
 	void	   *join_search_private;
@@ -678,21 +686,37 @@ typedef struct RelOptInfo
 
 	RelOptKind	reloptkind;
 
-	/* all relations included in this RelOptInfo */
-	Relids		relids;			/* set of base relids (rangetable indexes) */
+	/*
+	 * all relations included in this RelOptInfo; set of base relids
+	 * (rangetable indexes)
+	 */
+	Relids		relids;
 
-	/* size estimates generated by planner */
-	Cardinality rows;			/* estimated number of result tuples */
+	/*
+	 * size estimates generated by planner
+	 */
+	/* estimated number of result tuples */
+	Cardinality rows;
 
-	/* per-relation planner control flags */
-	bool		consider_startup;	/* keep cheap-startup-cost paths? */
-	bool		consider_param_startup; /* ditto, for parameterized paths? */
-	bool		consider_parallel;	/* consider parallel paths? */
+	/*
+	 * per-relation planner control flags
+	 */
+	/* keep cheap-startup-cost paths? */
+	bool		consider_startup;
+	/* ditto, for parameterized paths? */
+	bool		consider_param_startup;
+	/* consider parallel paths? */
+	bool		consider_parallel;
 
-	/* default result targetlist for Paths scanning this relation */
-	struct PathTarget *reltarget;	/* list of Vars/Exprs, cost, width */
+	/*
+	 * default result targetlist for Paths scanning this relation; list of
+	 * Vars/Exprs, cost, width
+	 */
+	struct PathTarget *reltarget;
 
-	/* materialization information */
+	/*
+	 * materialization information
+	 */
 	List	   *pathlist;		/* Path structures */
 	List	   *ppilist;		/* ParamPathInfos used in pathlist */
 	List	   *partial_pathlist;	/* partial Paths */
@@ -701,79 +725,132 @@ typedef struct RelOptInfo
 	struct Path *cheapest_unique_path;
 	List	   *cheapest_parameterized_paths;
 
-	/* parameterization information needed for both base rels and join rels */
-	/* (see also lateral_vars and lateral_referencers) */
-	Relids		direct_lateral_relids;	/* rels directly laterally referenced */
-	Relids		lateral_relids; /* minimum parameterization of rel */
+	/*
+	 * parameterization information needed for both base rels and join rels
+	 * (see also lateral_vars and lateral_referencers)
+	 */
+	/* rels directly laterally referenced */
+	Relids		direct_lateral_relids;
+	/* minimum parameterization of rel */
+	Relids		lateral_relids;
 
-	/* information about a base rel (not set for join rels!) */
+	/*
+	 * information about a base rel (not set for join rels!)
+	 */
 	Index		relid;
-	Oid			reltablespace;	/* containing tablespace */
-	RTEKind		rtekind;		/* RELATION, SUBQUERY, FUNCTION, etc */
-	AttrNumber	min_attr;		/* smallest attrno of rel (often <0) */
-	AttrNumber	max_attr;		/* largest attrno of rel */
-	Relids	   *attr_needed;	/* array indexed [min_attr .. max_attr] */
-	int32	   *attr_widths;	/* array indexed [min_attr .. max_attr] */
-	List	   *lateral_vars;	/* LATERAL Vars and PHVs referenced by rel */
-	Relids		lateral_referencers;	/* rels that reference me laterally */
-	List	   *indexlist;		/* list of IndexOptInfo */
-	List	   *statlist;		/* list of StatisticExtInfo */
-	BlockNumber pages;			/* size estimates derived from pg_class */
+	/* containing tablespace */
+	Oid			reltablespace;
+	/* RELATION, SUBQUERY, FUNCTION, etc */
+	RTEKind		rtekind;
+	/* smallest attrno of rel (often <0) */
+	AttrNumber	min_attr;
+	/* largest attrno of rel */
+	AttrNumber	max_attr;
+	/* array indexed [min_attr .. max_attr] */
+	Relids	   *attr_needed;
+	/* array indexed [min_attr .. max_attr] */
+	int32	   *attr_widths;
+	/* LATERAL Vars and PHVs referenced by rel */
+	List	   *lateral_vars;
+	/* rels that reference me laterally */
+	Relids		lateral_referencers;
+	/* list of IndexOptInfo */
+	List	   *indexlist;
+	/* list of StatisticExtInfo */
+	List	   *statlist;
+	/* size estimates derived from pg_class */
+	BlockNumber pages;
 	Cardinality tuples;
 	double		allvisfrac;
-	Bitmapset  *eclass_indexes; /* Indexes in PlannerInfo's eq_classes list of
-								 * ECs that mention this rel */
+
+	/*
+	 * Indexes in PlannerInfo's eq_classes list of ECs that mention this rel
+	 */
+	Bitmapset  *eclass_indexes;
 	PlannerInfo *subroot;		/* if subquery */
 	List	   *subplan_params; /* if subquery */
-	int			rel_parallel_workers;	/* wanted number of parallel workers */
-	uint32		amflags;		/* Bitmask of optional features supported by
-								 * the table AM */
-
-	/* Information about foreign tables and foreign joins */
-	Oid			serverid;		/* identifies server for the table or join */
-	Oid			userid;			/* identifies user to check access as */
-	bool		useridiscurrent;	/* join is only valid for current user */
+	/* wanted number of parallel workers */
+	int			rel_parallel_workers;
+	/* Bitmask of optional features supported by the table AM */
+	uint32		amflags;
+
+	/*
+	 * Information about foreign tables and foreign joins
+	 */
+	/* identifies server for the table or join */
+	Oid			serverid;
+	/* identifies user to check access as */
+	Oid			userid;
+	/* join is only valid for current user */
+	bool		useridiscurrent;
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
 	struct FdwRoutine *fdwroutine;
 	void	   *fdw_private;
 
-	/* cache space for remembering if we have proven this relation unique */
-	List	   *unique_for_rels;	/* known unique for these other relid
-									 * set(s) */
-	List	   *non_unique_for_rels;	/* known not unique for these set(s) */
-
-	/* used by various scans and joins: */
-	List	   *baserestrictinfo;	/* RestrictInfo structures (if base rel) */
-	QualCost	baserestrictcost;	/* cost of evaluating the above */
-	Index		baserestrict_min_security;	/* min security_level found in
-											 * baserestrictinfo */
-	List	   *joininfo;		/* RestrictInfo structures for join clauses
-								 * involving this rel */
-	bool		has_eclass_joins;	/* T means joininfo is incomplete */
-
-	/* used by partitionwise joins: */
-	bool		consider_partitionwise_join;	/* consider partitionwise join
-												 * paths? (if partitioned rel) */
-	Relids		top_parent_relids;	/* Relids of topmost parents (if "other"
-									 * rel) */
-
-	/* used for partitioned relations: */
-	PartitionScheme part_scheme;	/* Partitioning scheme */
-	int			nparts;			/* Number of partitions; -1 if not yet set; in
-								 * case of a join relation 0 means it's
-								 * considered unpartitioned */
-	struct PartitionBoundInfoData *boundinfo;	/* Partition bounds */
-	bool		partbounds_merged;	/* True if partition bounds were created
-									 * by partition_bounds_merge() */
-	List	   *partition_qual; /* Partition constraint, if not the root */
-	struct RelOptInfo **part_rels;	/* Array of RelOptInfos of partitions,
-									 * stored in the same order as bounds */
-	Bitmapset  *live_parts;		/* Bitmap with members acting as indexes into
-								 * the part_rels[] array to indicate which
-								 * partitions survived partition pruning. */
-	Relids		all_partrels;	/* Relids set of all partition relids */
-	List	  **partexprs;		/* Non-nullable partition key expressions */
-	List	  **nullable_partexprs; /* Nullable partition key expressions */
+	/*
+	 * cache space for remembering if we have proven this relation unique
+	 */
+	/* known unique for these other relid set(s) */
+	List	   *unique_for_rels;
+	/* known not unique for these set(s) */
+	List	   *non_unique_for_rels;
+
+	/*
+	 * used by various scans and joins:
+	 */
+	/* RestrictInfo structures (if base rel) */
+	List	   *baserestrictinfo;
+	/* cost of evaluating the above */
+	QualCost	baserestrictcost;
+	/* min security_level found in baserestrictinfo */
+	Index		baserestrict_min_security;
+	/* RestrictInfo structures for join clauses involving this rel */
+	List	   *joininfo;
+	/* T means joininfo is incomplete */
+	bool		has_eclass_joins;
+
+	/*
+	 * used by partitionwise joins:
+	 */
+	/* consider partitionwise join paths? (if partitioned rel) */
+	bool		consider_partitionwise_join;
+	/* Relids of topmost parents (if "other" rel) */
+	Relids		top_parent_relids;
+
+	/*
+	 * used for partitioned relations:
+	 */
+	/* Partitioning scheme */
+	PartitionScheme part_scheme;
+
+	/*
+	 * Number of partitions; -1 if not yet set; in case of a join relation 0
+	 * means it's considered unpartitioned
+	 */
+	int			nparts;
+	/* Partition bounds */
+	struct PartitionBoundInfoData *boundinfo;
+	/* True if partition bounds were created by partition_bounds_merge() */
+	bool		partbounds_merged;
+	/* Partition constraint, if not the root */
+	List	   *partition_qual;
+
+	/*
+	 * Array of RelOptInfos of partitions, stored in the same order as bounds
+	 */
+	struct RelOptInfo **part_rels;
+
+	/*
+	 * Bitmap with members acting as indexes into the part_rels[] array to
+	 * indicate which partitions survived partition pruning.
+	 */
+	Bitmapset  *live_parts;
+	/* Relids set of all partition relids */
+	Relids		all_partrels;
+	/* Non-nullable partition key expressions */
+	List	  **partexprs;
+	/* Nullable partition key expressions */
+	List	  **nullable_partexprs;
 } RelOptInfo;
 
 /*
@@ -836,56 +913,93 @@ struct IndexOptInfo
 {
 	NodeTag		type;
 
-	Oid			indexoid;		/* OID of the index relation */
-	Oid			reltablespace;	/* tablespace of index (not table) */
-	RelOptInfo *rel;			/* back-link to index's table */
-
-	/* index-size statistics (from pg_class and elsewhere) */
-	BlockNumber pages;			/* number of disk pages in index */
-	Cardinality tuples;			/* number of index tuples in index */
-	int			tree_height;	/* index tree height, or -1 if unknown */
-
-	/* index descriptor information */
-	int			ncolumns;		/* number of columns in index */
-	int			nkeycolumns;	/* number of key columns in index */
-	int		   *indexkeys;		/* column numbers of index's attributes both
-								 * key and included columns, or 0 */
-	Oid		   *indexcollations;	/* OIDs of collations of index columns */
-	Oid		   *opfamily;		/* OIDs of operator families for columns */
-	Oid		   *opcintype;		/* OIDs of opclass declared input data types */
-	Oid		   *sortopfamily;	/* OIDs of btree opfamilies, if orderable */
-	bool	   *reverse_sort;	/* is sort order descending? */
-	bool	   *nulls_first;	/* do NULLs come first in the sort order? */
-	bytea	  **opclassoptions; /* opclass-specific options for columns */
-	bool	   *canreturn;		/* which index cols can be returned in an
-								 * index-only scan? */
-	Oid			relam;			/* OID of the access method (in pg_am) */
-
-	List	   *indexprs;		/* expressions for non-simple index columns */
-	List	   *indpred;		/* predicate if a partial index, else NIL */
-
-	List	   *indextlist;		/* targetlist representing index columns */
-
-	List	   *indrestrictinfo;	/* parent relation's baserestrictinfo
-									 * list, less any conditions implied by
-									 * the index's predicate (unless it's a
-									 * target rel, see comments in
-									 * check_index_predicates()) */
-
-	bool		predOK;			/* true if index predicate matches query */
-	bool		unique;			/* true if a unique index */
-	bool		immediate;		/* is uniqueness enforced immediately? */
-	bool		hypothetical;	/* true if index doesn't really exist */
-
-	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
-	bool		amoptionalkey;	/* can query omit key for the first column? */
-	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
-	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
-	bool		amhasgettuple;	/* does AM have amgettuple interface? */
-	bool		amhasgetbitmap; /* does AM have amgetbitmap interface? */
-	bool		amcanparallel;	/* does AM support parallel scan? */
-	bool		amcanmarkpos;	/* does AM support mark/restore? */
+	/* OID of the index relation */
+	Oid			indexoid;
+	/* tablespace of index (not table) */
+	Oid			reltablespace;
+	/* back-link to index's table */
+	RelOptInfo *rel;
+
+	/*
+	 * index-size statistics (from pg_class and elsewhere)
+	 */
+	/* number of disk pages in index */
+	BlockNumber pages;
+	/* number of index tuples in index */
+	Cardinality tuples;
+	/* index tree height, or -1 if unknown */
+	int			tree_height;
+
+	/*
+	 * index descriptor information
+	 */
+	/* number of columns in index */
+	int			ncolumns;
+	/* number of key columns in index */
+	int			nkeycolumns;
+
+	/*
+	 * column numbers of index's attributes both key and included columns, or
+	 * 0
+	 */
+	int		   *indexkeys;
+	/* OIDs of collations of index columns */
+	Oid		   *indexcollations;
+	/* OIDs of operator families for columns */
+	Oid		   *opfamily;
+	/* OIDs of opclass declared input data types */
+	Oid		   *opcintype;
+	/* OIDs of btree opfamilies, if orderable */
+	Oid		   *sortopfamily;
+	/* is sort order descending? */
+	bool	   *reverse_sort;
+	/* do NULLs come first in the sort order? */
+	bool	   *nulls_first;
+	/* opclass-specific options for columns */
+	bytea	  **opclassoptions;
+	/* which index cols can be returned in an index-only scan? */
+	bool	   *canreturn;
+	/* OID of the access method (in pg_am) */
+	Oid			relam;
+	/* expressions for non-simple index columns */
+	List	   *indexprs;
+	/* predicate if a partial index, else NIL */
+	List	   *indpred;
+
+	/* targetlist representing index columns */
+	List	   *indextlist;
+
+	/*
+	 * parent relation's baserestrictinfo list, less any conditions implied by
+	 * the index's predicate (unless it's a target rel, see comments in
+	 * check_index_predicates())
+	 */
+	List	   *indrestrictinfo;
+
+	/* true if index predicate matches query */
+	bool		predOK;
+	/* true if a unique index */
+	bool		unique;
+	/* is uniqueness enforced immediately? */
+	bool		immediate;
+	/* true if index doesn't really exist */
+	bool		hypothetical;
+
+	/*
+	 * Remaining fields are copied from the index AM's API struct
+	 * (IndexAmRoutine)
+	 */
+	bool		amcanorderbyop;
+	bool		amoptionalkey;
+	bool		amsearcharray;
+	bool		amsearchnulls;
+	/* does AM have amgettuple interface? */
+	bool		amhasgettuple;
+	/* does AM have amgetbitmap interface? */
+	bool		amhasgetbitmap;
+	bool		amcanparallel;
+	/* does AM have ammarkpos interface? */
+	bool		amcanmarkpos;
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
 };
@@ -902,19 +1016,35 @@ typedef struct ForeignKeyOptInfo
 {
 	NodeTag		type;
 
-	/* Basic data about the foreign key (fetched from catalogs): */
-	Index		con_relid;		/* RT index of the referencing table */
-	Index		ref_relid;		/* RT index of the referenced table */
-	int			nkeys;			/* number of columns in the foreign key */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
-
-	/* Derived info about whether FK's equality conditions match the query: */
-	int			nmatched_ec;	/* # of FK cols matched by ECs */
-	int			nconst_ec;		/* # of these ECs that are ec_has_const */
-	int			nmatched_rcols; /* # of FK cols matched by non-EC rinfos */
-	int			nmatched_ri;	/* total # of non-EC rinfos matched to FK */
+	/*
+	 * Basic data about the foreign key (fetched from catalogs):
+	 */
+
+	/* RT index of the referencing table */
+	Index		con_relid;
+	/* RT index of the referenced table */
+	Index		ref_relid;
+	/* number of columns in the foreign key */
+	int			nkeys;
+	/* cols in referencing table */
+	AttrNumber	conkey[INDEX_MAX_KEYS];
+	/* cols in referenced table */
+	AttrNumber	confkey[INDEX_MAX_KEYS];
+	/* PK = FK operator OIDs */
+	Oid			conpfeqop[INDEX_MAX_KEYS];
+
+	/*
+	 * Derived info about whether FK's equality conditions match the query:
+	 */
+
+	/* # of FK cols matched by ECs */
+	int			nmatched_ec;
+	/* # of these ECs that are ec_has_const */
+	int			nconst_ec;
+	/* # of FK cols matched by non-EC rinfos */
+	int			nmatched_rcols;
+	/* total # of non-EC rinfos matched to FK */
+	int			nmatched_ri;
 	/* Pointer to eclass matching each column's condition, if there is one */
 	struct EquivalenceClass *eclass[INDEX_MAX_KEYS];
 	/* Pointer to eclass member for the referencing Var, if there is one */
@@ -934,12 +1064,23 @@ typedef struct StatisticExtInfo
 {
 	NodeTag		type;
 
-	Oid			statOid;		/* OID of the statistics row */
-	bool		inherit;		/* includes child relations */
-	RelOptInfo *rel;			/* back-link to statistic's table */
-	char		kind;			/* statistics kind of this entry */
-	Bitmapset  *keys;			/* attnums of the columns covered */
-	List	   *exprs;			/* expressions */
+	/* OID of the statistics row */
+	Oid			statOid;
+
+	/* includes child relations */
+	bool		inherit;
+
+	/* back-link to statistic's table */
+	RelOptInfo *rel;
+
+	/* statistics kind of this entry */
+	char		kind;
+
+	/* attnums of the columns covered */
+	Bitmapset  *keys;
+
+	/* expressions */
+	List	   *exprs;
 } StatisticExtInfo;
 
 /*
@@ -1119,12 +1260,21 @@ typedef enum VolatileFunctionStatus
 typedef struct PathTarget
 {
 	NodeTag		type;
-	List	   *exprs;			/* list of expressions to be computed */
-	Index	   *sortgrouprefs;	/* corresponding sort/group refnos, or 0 */
-	QualCost	cost;			/* cost of evaluating the expressions */
-	int			width;			/* estimated avg width of result tuples */
-	VolatileFunctionStatus has_volatile_expr;	/* indicates if exprs contain
-												 * any volatile functions. */
+
+	/* list of expressions to be computed */
+	List	   *exprs;
+
+	/* corresponding sort/group refnos, or 0 */
+	Index	   *sortgrouprefs;
+
+	/* cost of evaluating the expressions */
+	QualCost	cost;
+
+	/* estimated avg width of result tuples */
+	int			width;
+
+	/* indicates if exprs contain any volatile functions */
+	VolatileFunctionStatus has_volatile_expr;
 } PathTarget;
 
 /* Convenience macro to get a sort/group refno from a PathTarget */
@@ -1189,24 +1339,32 @@ typedef struct Path
 {
 	NodeTag		type;
 
-	NodeTag		pathtype;		/* tag identifying scan/join method */
+	/* tag identifying scan/join method */
+	NodeTag		pathtype;
 
-	RelOptInfo *parent;			/* the relation this path can build */
-	PathTarget *pathtarget;		/* list of Vars/Exprs, cost, width */
+	/* the relation this path can build */
+	RelOptInfo *parent;
 
-	ParamPathInfo *param_info;	/* parameterization info, or NULL if none */
+	/* list of Vars/Exprs, cost, width */
+	PathTarget *pathtarget;
 
-	bool		parallel_aware; /* engage parallel-aware logic? */
-	bool		parallel_safe;	/* OK to use as part of parallel plan? */
-	int			parallel_workers;	/* desired # of workers; 0 = not parallel */
+	/* parameterization info, or NULL if none */
+	ParamPathInfo *param_info;
+
+	/* engage parallel-aware logic? */
+	bool		parallel_aware;
+	/* OK to use as part of parallel plan? */
+	bool		parallel_safe;
+	/* desired # of workers; 0 = not parallel */
+	int			parallel_workers;
 
 	/* estimated size/costs for path (see costsize.c for more info) */
 	Cardinality rows;			/* estimated number of result tuples */
 	Cost		startup_cost;	/* cost expended before fetching any tuples */
 	Cost		total_cost;		/* total cost (assuming all tuples fetched) */
 
-	List	   *pathkeys;		/* sort ordering of path's output */
-	/* pathkeys is a List of PathKey nodes; see above */
+	/* sort ordering of path's output; a List of PathKey nodes; see above */
+	List	   *pathkeys;
 } Path;
 
 /* Macro for extracting a path's parameterization relids; beware double eval */
@@ -2072,22 +2230,29 @@ typedef struct RestrictInfo
 {
 	NodeTag		type;
 
-	Expr	   *clause;			/* the represented clause of WHERE or JOIN */
+	/* the represented clause of WHERE or JOIN */
+	Expr	   *clause;
 
-	bool		is_pushed_down; /* true if clause was pushed down in level */
+	/* true if clause was pushed down in level */
+	bool		is_pushed_down;
 
-	bool		outerjoin_delayed;	/* true if delayed by lower outer join */
+	/* true if delayed by lower outer join */
+	bool		outerjoin_delayed;
 
-	bool		can_join;		/* see comment above */
+	/* see comment above */
+	bool		can_join;
 
-	bool		pseudoconstant; /* see comment above */
+	/* see comment above */
+	bool		pseudoconstant;
 
-	bool		leakproof;		/* true if known to contain no leaked Vars */
+	/* true if known to contain no leaked Vars */
+	bool		leakproof;
 
-	VolatileFunctionStatus has_volatile;	/* to indicate if clause contains
-											 * any volatile functions. */
+	/* to indicate if clause contains any volatile functions. */
+	VolatileFunctionStatus has_volatile;
 
-	Index		security_level; /* see comment above */
+	/* see comment above */
+	Index		security_level;
 
 	/* The set of relids (varnos) actually referenced in the clause: */
 	Relids		clause_relids;
@@ -2101,45 +2266,84 @@ typedef struct RestrictInfo
 	/* The relids used in the clause that are nullable by lower outer joins: */
 	Relids		nullable_relids;
 
-	/* These fields are set for any binary opclause: */
-	Relids		left_relids;	/* relids in left side of clause */
-	Relids		right_relids;	/* relids in right side of clause */
+	/*
+	 * Relids in the left/right side of the clause.  These fields are set for
+	 * any binary opclause.
+	 */
+	Relids		left_relids;
+	Relids		right_relids;
 
-	/* This field is NULL unless clause is an OR clause: */
-	Expr	   *orclause;		/* modified clause with RestrictInfos */
+	/*
+	 * Modified clause with RestrictInfos.  This field is NULL unless clause
+	 * is an OR clause.
+	 */
+	Expr	   *orclause;
 
-	/* This field is NULL unless clause is potentially redundant: */
-	EquivalenceClass *parent_ec;	/* generating EquivalenceClass */
+	/*
+	 * Generating EquivalenceClass.  This field is NULL unless clause is
+	 * potentially redundant.
+	 */
+	EquivalenceClass *parent_ec;
 
-	/* cache space for cost and selectivity */
-	QualCost	eval_cost;		/* eval cost of clause; -1 if not yet set */
-	Selectivity norm_selec;		/* selectivity for "normal" (JOIN_INNER)
-								 * semantics; -1 if not yet set; >1 means a
-								 * redundant clause */
-	Selectivity outer_selec;	/* selectivity for outer join semantics; -1 if
-								 * not yet set */
+	/*
+	 * cache space for cost and selectivity
+	 */
 
-	/* valid if clause is mergejoinable, else NIL */
-	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	/* eval cost of clause; -1 if not yet set */
+	QualCost	eval_cost;
 
-	/* cache space for mergeclause processing; NULL if not yet set */
-	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
-	EquivalenceClass *right_ec; /* EquivalenceClass containing righthand */
-	EquivalenceMember *left_em; /* EquivalenceMember for lefthand */
-	EquivalenceMember *right_em;	/* EquivalenceMember for righthand */
-	List	   *scansel_cache;	/* list of MergeScanSelCache structs */
+	/*
+	 * selectivity for "normal" (JOIN_INNER) semantics; -1 if not yet set; >1
+	 * means a redundant clause
+	 */
+	Selectivity norm_selec;
+	/* selectivity for outer join semantics; -1 if not yet set */
+	Selectivity outer_selec;
+
+	/*
+	 * opfamilies containing clause operator; valid if clause is
+	 * mergejoinable, else NIL
+	 */
+	List	   *mergeopfamilies;
 
-	/* transient workspace for use while considering a specific join path */
-	bool		outer_is_left;	/* T = outer var on left, F = on right */
+	/*
+	 * cache space for mergeclause processing; NULL if not yet set
+	 */
 
-	/* valid if clause is hashjoinable, else InvalidOid: */
-	Oid			hashjoinoperator;	/* copy of clause operator */
+	/* EquivalenceClass containing lefthand */
+	EquivalenceClass *left_ec;
+	/* EquivalenceClass containing righthand */
+	EquivalenceClass *right_ec;
+	/* EquivalenceMember for lefthand */
+	EquivalenceMember *left_em;
+	/* EquivalenceMember for righthand */
+	EquivalenceMember *right_em;
+	/* list of MergeScanSelCache structs */
+	List	   *scansel_cache;
 
-	/* cache space for hashclause processing; -1 if not yet set */
-	Selectivity left_bucketsize;	/* avg bucketsize of left side */
-	Selectivity right_bucketsize;	/* avg bucketsize of right side */
-	Selectivity left_mcvfreq;	/* left side's most common val's freq */
-	Selectivity right_mcvfreq;	/* right side's most common val's freq */
+	/*
+	 * transient workspace for use while considering a specific join path; T =
+	 * outer var on left, F = on right
+	 */
+	bool		outer_is_left;
+
+	/*
+	 * copy of clause operator; valid if clause is hashjoinable, else
+	 * InvalidOid
+	 */
+	Oid			hashjoinoperator;
+
+	/*
+	 * cache space for hashclause processing; -1 if not yet set
+	 */
+	/* avg bucketsize of left side */
+	Selectivity left_bucketsize;
+	/* avg bucketsize of right side */
+	Selectivity right_bucketsize;
+	/* left side's most common val's freq */
+	Selectivity left_mcvfreq;
+	/* right side's most common val's freq */
+	Selectivity right_mcvfreq;
 
 	/* hash equality operators used for memoize nodes, else InvalidOid */
 	Oid			left_hasheqoperator;
@@ -2198,10 +2402,18 @@ typedef struct MergeScanSelCache
 typedef struct PlaceHolderVar
 {
 	Expr		xpr;
-	Expr	   *phexpr;			/* the represented expression */
-	Relids		phrels;			/* base relids syntactically within expr src */
-	Index		phid;			/* ID for PHV (unique within planner run) */
-	Index		phlevelsup;		/* > 0 if PHV belongs to outer query */
+
+	/* the represented expression */
+	Expr	   *phexpr;
+
+	/* base relids syntactically within expr src */
+	Relids		phrels;
+
+	/* ID for PHV (unique within planner run) */
+	Index		phid;
+
+	/* > 0 if PHV belongs to outer query */
+	Index		phlevelsup;
 } PlaceHolderVar;
 
 /*
@@ -2360,7 +2572,7 @@ typedef struct AppendRelInfo
 	 * child column is dropped or doesn't exist in the parent.
 	 */
 	int			num_child_cols; /* length of array */
-	AttrNumber *parent_colnos;	/* array of parent attnos, or zeroes */
+	AttrNumber *parent_colnos;
 
 	/*
 	 * We store the parent table's OID here for inheritance, or InvalidOid for
@@ -2428,12 +2640,23 @@ typedef struct PlaceHolderInfo
 {
 	NodeTag		type;
 
-	Index		phid;			/* ID for PH (unique within planner run) */
-	PlaceHolderVar *ph_var;		/* copy of PlaceHolderVar tree */
-	Relids		ph_eval_at;		/* lowest level we can evaluate value at */
-	Relids		ph_lateral;		/* relids of contained lateral refs, if any */
-	Relids		ph_needed;		/* highest level the value is needed at */
-	int32		ph_width;		/* estimated attribute width */
+	/* ID for PH (unique within planner run) */
+	Index		phid;
+
+	/* copy of PlaceHolderVar tree */
+	PlaceHolderVar *ph_var;
+
+	/* lowest level we can evaluate value at */
+	Relids		ph_eval_at;
+
+	/* relids of contained lateral refs, if any */
+	Relids		ph_lateral;
+
+	/* highest level the value is needed at */
+	Relids		ph_needed;
+
+	/* estimated attribute width */
+	int32		ph_width;
 } PlaceHolderInfo;
 
 /*
@@ -2445,13 +2668,26 @@ typedef struct MinMaxAggInfo
 {
 	NodeTag		type;
 
-	Oid			aggfnoid;		/* pg_proc Oid of the aggregate */
-	Oid			aggsortop;		/* Oid of its sort operator */
-	Expr	   *target;			/* expression we are aggregating on */
-	PlannerInfo *subroot;		/* modified "root" for planning the subquery */
-	Path	   *path;			/* access path for subquery */
-	Cost		pathcost;		/* estimated cost to fetch first row */
-	Param	   *param;			/* param for subplan's output */
+	/* pg_proc Oid of the aggregate */
+	Oid			aggfnoid;
+
+	/* Oid of its sort operator */
+	Oid			aggsortop;
+
+	/* expression we are aggregating on */
+	Expr	   *target;
+
+	/* modified "root" for planning the subquery */
+	PlannerInfo *subroot;
+
+	/* access path for subquery */
+	Path	   *path;
+
+	/* estimated cost to fetch first row */
+	Cost		pathcost;
+
+	/* param for subplan's output */
+	Param	   *param;
 } MinMaxAggInfo;
 
 /*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0ea9a22dfb..d5c0ebe859 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -274,14 +274,29 @@ typedef struct Append
 typedef struct MergeAppend
 {
 	Plan		plan;
-	Bitmapset  *apprelids;		/* RTIs of appendrel(s) formed by this node */
+
+	/* RTIs of appendrel(s) formed by this node */
+	Bitmapset  *apprelids;
+
 	List	   *mergeplans;
+
 	/* these fields are just like the sort-key info in struct Sort: */
-	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+
+	/* number of sort-key columns */
+	int			numCols;
+
+	/* their indexes in the target list */
+	AttrNumber *sortColIdx;
+
+	/* OIDs of operators to sort them by */
+	Oid		   *sortOperators;
+
+	/* OIDs of collations */
+	Oid		   *collations;
+
+	/* NULLS FIRST/LAST directions */
+	bool	   *nullsFirst;
+
 	/* Info for run-time subplan pruning; NULL if we're not doing that */
 	struct PartitionPruneInfo *part_prune_info;
 } MergeAppend;
@@ -297,14 +312,24 @@ typedef struct MergeAppend
 typedef struct RecursiveUnion
 {
 	Plan		plan;
-	int			wtParam;		/* ID of Param representing work table */
+
+	/* ID of Param representing work table */
+	int			wtParam;
+
 	/* Remaining fields are zero/null in UNION ALL case */
-	int			numCols;		/* number of columns to check for
-								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
+
+	/* number of columns to check for duplicate-ness */
+	int			numCols;
+
+	/* their indexes in the target list */
+	AttrNumber *dupColIdx;
+
+	/* equality operators to compare with */
+	Oid		   *dupOperators;
 	Oid		   *dupCollations;
-	long		numGroups;		/* estimated number of groups in input */
+
+	/* estimated number of groups in input */
+	long		numGroups;
 } RecursiveUnion;
 
 /* ----------------
@@ -777,13 +802,26 @@ typedef struct NestLoopParam
 typedef struct MergeJoin
 {
 	Join		join;
-	bool		skip_mark_restore;	/* Can we skip mark/restore calls? */
-	List	   *mergeclauses;	/* mergeclauses as expression trees */
+
+	/* Can we skip mark/restore calls? */
+	bool		skip_mark_restore;
+
+	/* mergeclauses as expression trees */
+	List	   *mergeclauses;
+
 	/* these are arrays, but have the same length as the mergeclauses list: */
-	Oid		   *mergeFamilies;	/* per-clause OIDs of btree opfamilies */
-	Oid		   *mergeCollations;	/* per-clause OIDs of collations */
-	int		   *mergeStrategies;	/* per-clause ordering (ASC or DESC) */
-	bool	   *mergeNullsFirst;	/* per-clause nulls ordering */
+
+	/* per-clause OIDs of btree opfamilies */
+	Oid		   *mergeFamilies;
+
+	/* per-clause OIDs of collations */
+	Oid		   *mergeCollations;
+
+	/* per-clause ordering (ASC or DESC) */
+	int		   *mergeStrategies;
+
+	/* per-clause nulls ordering */
+	bool	   *mergeNullsFirst;
 } MergeJoin;
 
 /* ----------------
@@ -821,21 +859,38 @@ typedef struct Memoize
 {
 	Plan		plan;
 
-	int			numKeys;		/* size of the two arrays below */
-
-	Oid		   *hashOperators;	/* hash operators for each key */
-	Oid		   *collations;		/* collations for each key */
-	List	   *param_exprs;	/* cache keys in the form of exprs containing
-								 * parameters */
-	bool		singlerow;		/* true if the cache entry should be marked as
-								 * complete after we store the first tuple in
-								 * it. */
-	bool		binary_mode;	/* true when cache key should be compared bit
-								 * by bit, false when using hash equality ops */
-	uint32		est_entries;	/* The maximum number of entries that the
-								 * planner expects will fit in the cache, or 0
-								 * if unknown */
-	Bitmapset  *keyparamids;	/* paramids from param_exprs */
+	/* size of the two arrays below */
+	int			numKeys;
+
+	/* hash operators for each key */
+	Oid		   *hashOperators;
+
+	/* collations for each key */
+	Oid		   *collations;
+
+	/* cache keys in the form of exprs containing parameters */
+	List	   *param_exprs;
+
+	/*
+	 * true if the cache entry should be marked as complete after we store the
+	 * first tuple in it.
+	 */
+	bool		singlerow;
+
+	/*
+	 * true when cache key should be compared bit by bit, false when using
+	 * hash equality ops
+	 */
+	bool		binary_mode;
+
+	/*
+	 * The maximum number of entries that the planner expects will fit in the
+	 * cache, or 0 if unknown
+	 */
+	uint32		est_entries;
+
+	/* paramids from param_exprs */
+	Bitmapset  *keyparamids;
 } Memoize;
 
 /* ----------------
@@ -845,11 +900,21 @@ typedef struct Memoize
 typedef struct Sort
 {
 	Plan		plan;
-	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
+
+	/* number of sort-key columns */
+	int			numCols;
+
+	/* their indexes in the target list */
+	AttrNumber *sortColIdx;
+
+	/* OIDs of operators to sort them by */
+	Oid		   *sortOperators;
+
+	/* OIDs of collations */
+	Oid		   *collations;
+
+	/* NULLS FIRST/LAST directions */
+	bool	   *nullsFirst;
 } Sort;
 
 /* ----------------
@@ -871,9 +936,15 @@ typedef struct IncrementalSort
 typedef struct Group
 {
 	Plan		plan;
-	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
+
+	/* number of grouping columns */
+	int			numCols;
+
+	/* their indexes in the target list */
+	AttrNumber *grpColIdx;
+
+	/* equality operators to compare with */
+	Oid		   *grpOperators;
 	Oid		   *grpCollations;
 } Group;
 
@@ -894,18 +965,39 @@ typedef struct Group
 typedef struct Agg
 {
 	Plan		plan;
-	AggStrategy aggstrategy;	/* basic strategy, see nodes.h */
-	AggSplit	aggsplit;		/* agg-splitting mode, see nodes.h */
-	int			numCols;		/* number of grouping columns */
-	AttrNumber *grpColIdx;		/* their indexes in the target list */
-	Oid		   *grpOperators;	/* equality operators to compare with */
+
+	/* basic strategy, see nodes.h */
+	AggStrategy aggstrategy;
+
+	/* agg-splitting mode, see nodes.h */
+	AggSplit	aggsplit;
+
+	/* number of grouping columns */
+	int			numCols;
+
+	/* their indexes in the target list */
+	AttrNumber *grpColIdx;
+
+	/* equality operators to compare with */
+	Oid		   *grpOperators;
 	Oid		   *grpCollations;
-	long		numGroups;		/* estimated number of groups in input */
-	uint64		transitionSpace;	/* for pass-by-ref transition data */
-	Bitmapset  *aggParams;		/* IDs of Params used in Aggref inputs */
+
+	/* estimated number of groups in input */
+	long		numGroups;
+
+	/* for pass-by-ref transition data */
+	uint64		transitionSpace;
+
+	/* IDs of Params used in Aggref inputs */
+	Bitmapset  *aggParams;
+
 	/* Note: planner provides numGroups & aggParams only in HASHED/MIXED case */
-	List	   *groupingSets;	/* grouping sets to use */
-	List	   *chain;			/* chained Agg/Sort nodes */
+
+	/* grouping sets to use */
+	List	   *groupingSets;
+
+	/* chained Agg/Sort nodes */
+	List	   *chain;
 } Agg;
 
 /* ----------------
@@ -915,28 +1007,71 @@ typedef struct Agg
 typedef struct WindowAgg
 {
 	Plan		plan;
-	Index		winref;			/* ID referenced by window functions */
-	int			partNumCols;	/* number of columns in partition clause */
-	AttrNumber *partColIdx;		/* their indexes in the target list */
-	Oid		   *partOperators;	/* equality operators for partition columns */
-	Oid		   *partCollations; /* collations for partition columns */
-	int			ordNumCols;		/* number of columns in ordering clause */
-	AttrNumber *ordColIdx;		/* their indexes in the target list */
-	Oid		   *ordOperators;	/* equality operators for ordering columns */
-	Oid		   *ordCollations;	/* collations for ordering columns */
-	int			frameOptions;	/* frame_clause options, see WindowDef */
-	Node	   *startOffset;	/* expression for starting bound, if any */
-	Node	   *endOffset;		/* expression for ending bound, if any */
-	List	   *runCondition;	/* qual to help short-circuit execution */
-	List	   *runConditionOrig;	/* runCondition for display in EXPLAIN */
+
+	/* ID referenced by window functions */
+	Index		winref;
+
+	/* number of columns in partition clause */
+	int			partNumCols;
+
+	/* their indexes in the target list */
+	AttrNumber *partColIdx;
+
+	/* equality operators for partition columns */
+	Oid		   *partOperators;
+
+	/* collations for partition columns */
+	Oid		   *partCollations;
+
+	/* number of columns in ordering clause */
+	int			ordNumCols;
+
+	/* their indexes in the target list */
+	AttrNumber *ordColIdx;
+
+	/* equality operators for ordering columns */
+	Oid		   *ordOperators;
+
+	/* collations for ordering columns */
+	Oid		   *ordCollations;
+
+	/* frame_clause options, see WindowDef */
+	int			frameOptions;
+
+	/* expression for starting bound, if any */
+	Node	   *startOffset;
+
+	/* expression for ending bound, if any */
+	Node	   *endOffset;
+
+	/* qual to help short-circuit execution */
+	List	   *runCondition;
+
+	/* runCondition for display in EXPLAIN */
+	List	   *runConditionOrig;
+
 	/* these fields are used with RANGE offset PRECEDING/FOLLOWING: */
-	Oid			startInRangeFunc;	/* in_range function for startOffset */
-	Oid			endInRangeFunc; /* in_range function for endOffset */
-	Oid			inRangeColl;	/* collation for in_range tests */
-	bool		inRangeAsc;		/* use ASC sort order for in_range tests? */
-	bool		inRangeNullsFirst;	/* nulls sort first for in_range tests? */
-	bool		topWindow;		/* false for all apart from the WindowAgg
-								 * that's closest to the root of the plan */
+
+	/* in_range function for startOffset */
+	Oid			startInRangeFunc;
+
+	/* in_range function for endOffset */
+	Oid			endInRangeFunc;
+
+	/* collation for in_range tests */
+	Oid			inRangeColl;
+
+	/* use ASC sort order for in_range tests? */
+	bool		inRangeAsc;
+
+	/* nulls sort first for in_range tests? */
+	bool		inRangeNullsFirst;
+
+	/*
+	 * false for all apart from the WindowAgg that's closest to the root of
+	 * the plan
+	 */
+	bool		topWindow;
 } WindowAgg;
 
 /* ----------------
@@ -946,10 +1081,18 @@ typedef struct WindowAgg
 typedef struct Unique
 {
 	Plan		plan;
-	int			numCols;		/* number of columns to check for uniqueness */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+
+	/* number of columns to check for uniqueness */
+	int			numCols;
+
+	/* their indexes in the target list */
+	AttrNumber *uniqColIdx;
+
+	/* equality operators to compare with */
+	Oid		   *uniqOperators;
+
+	/* collations for equality comparisons */
+	Oid		   *uniqCollations;
 } Unique;
 
 /* ------------
@@ -981,16 +1124,35 @@ typedef struct Gather
 typedef struct GatherMerge
 {
 	Plan		plan;
-	int			num_workers;	/* planned number of worker processes */
-	int			rescan_param;	/* ID of Param that signals a rescan, or -1 */
+
+	/* planned number of worker processes */
+	int			num_workers;
+
+	/* ID of Param that signals a rescan, or -1 */
+	int			rescan_param;
+
 	/* remaining fields are just like the sort-key info in struct Sort */
-	int			numCols;		/* number of sort-key columns */
-	AttrNumber *sortColIdx;		/* their indexes in the target list */
-	Oid		   *sortOperators;	/* OIDs of operators to sort them by */
-	Oid		   *collations;		/* OIDs of collations */
-	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
-	Bitmapset  *initParam;		/* param id's of initplans which are referred
-								 * at gather merge or one of it's child node */
+
+	/* number of sort-key columns */
+	int			numCols;
+
+	/* their indexes in the target list */
+	AttrNumber *sortColIdx;
+
+	/* OIDs of operators to sort them by */
+	Oid		   *sortOperators;
+
+	/* OIDs of collations */
+	Oid		   *collations;
+
+	/* NULLS FIRST/LAST directions */
+	bool	   *nullsFirst;
+
+	/*
+	 * param id's of initplans which are referred at gather merge or one of
+	 * it's child node
+	 */
+	Bitmapset  *initParam;
 } GatherMerge;
 
 /* ----------------
@@ -1024,16 +1186,31 @@ typedef struct Hash
 typedef struct SetOp
 {
 	Plan		plan;
-	SetOpCmd	cmd;			/* what to do, see nodes.h */
-	SetOpStrategy strategy;		/* how to do it, see nodes.h */
-	int			numCols;		/* number of columns to check for
-								 * duplicate-ness */
-	AttrNumber *dupColIdx;		/* their indexes in the target list */
-	Oid		   *dupOperators;	/* equality operators to compare with */
+
+	/* what to do, see nodes.h */
+	SetOpCmd	cmd;
+
+	/* how to do it, see nodes.h */
+	SetOpStrategy strategy;
+
+	/* number of columns to check for duplicate-ness */
+	int			numCols;
+
+	/* their indexes in the target list */
+	AttrNumber *dupColIdx;
+
+	/* equality operators to compare with */
+	Oid		   *dupOperators;
 	Oid		   *dupCollations;
-	AttrNumber	flagColIdx;		/* where is the flag column, if any */
-	int			firstFlag;		/* flag value for first input relation */
-	long		numGroups;		/* estimated number of groups in input */
+
+	/* where is the flag column, if any */
+	AttrNumber	flagColIdx;
+
+	/* flag value for first input relation */
+	int			firstFlag;
+
+	/* estimated number of groups in input */
+	long		numGroups;
 } SetOp;
 
 /* ----------------
@@ -1062,13 +1239,27 @@ typedef struct LockRows
 typedef struct Limit
 {
 	Plan		plan;
-	Node	   *limitOffset;	/* OFFSET parameter, or NULL if none */
-	Node	   *limitCount;		/* COUNT parameter, or NULL if none */
-	LimitOption limitOption;	/* limit type */
-	int			uniqNumCols;	/* number of columns to check for similarity  */
-	AttrNumber *uniqColIdx;		/* their indexes in the target list */
-	Oid		   *uniqOperators;	/* equality operators to compare with */
-	Oid		   *uniqCollations; /* collations for equality comparisons */
+
+	/* OFFSET parameter, or NULL if none */
+	Node	   *limitOffset;
+
+	/* COUNT parameter, or NULL if none */
+	Node	   *limitCount;
+
+	/* limit type */
+	LimitOption limitOption;
+
+	/* number of columns to check for similarity  */
+	int			uniqNumCols;
+
+	/* their indexes in the target list */
+	AttrNumber *uniqColIdx;
+
+	/* equality operators to compare with */
+	Oid		   *uniqOperators;
+
+	/* collations for equality comparisons */
+	Oid		   *uniqCollations;
 } Limit;
 
 
@@ -1223,13 +1414,24 @@ typedef struct PartitionPruneInfo
 typedef struct PartitionedRelPruneInfo
 {
 	NodeTag		type;
-	Index		rtindex;		/* RT index of partition rel for this level */
-	Bitmapset  *present_parts;	/* Indexes of all partitions which subplans or
-								 * subparts are present for */
-	int			nparts;			/* Length of the following arrays: */
-	int		   *subplan_map;	/* subplan index by partition index, or -1 */
-	int		   *subpart_map;	/* subpart index by partition index, or -1 */
-	Oid		   *relid_map;		/* relation OID by partition index, or 0 */
+
+	/* RT index of partition rel for this level */
+	Index		rtindex;
+
+	/* Indexes of all partitions which subplans or subparts are present for */
+	Bitmapset  *present_parts;
+
+	/* Length of the following arrays: */
+	int			nparts;
+
+	/* subplan index by partition index, or -1 */
+	int		   *subplan_map;
+
+	/* subpart index by partition index, or -1 */
+	int		   *subpart_map;
+
+	/* relation OID by partition index, or 0 */
+	Oid		   *relid_map;
 
 	/*
 	 * initial_pruning_steps shows how to prune during executor startup (i.e.,
@@ -1239,8 +1441,9 @@ typedef struct PartitionedRelPruneInfo
 	 */
 	List	   *initial_pruning_steps;	/* List of PartitionPruneStep */
 	List	   *exec_pruning_steps; /* List of PartitionPruneStep */
-	Bitmapset  *execparamids;	/* All PARAM_EXEC Param IDs in
-								 * exec_pruning_steps */
+
+	/* All PARAM_EXEC Param IDs in exec_pruning_steps */
+	Bitmapset  *execparamids;
 } PartitionedRelPruneInfo;
 
 /*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 51505eee85..54f942e0fd 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -329,26 +329,66 @@ typedef struct Param
 typedef struct Aggref
 {
 	Expr		xpr;
-	Oid			aggfnoid;		/* pg_proc Oid of the aggregate */
-	Oid			aggtype;		/* type Oid of result of the aggregate */
-	Oid			aggcollid;		/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
-	Oid			aggtranstype;	/* type Oid of aggregate's transition value */
-	List	   *aggargtypes;	/* type Oids of direct and aggregated args */
-	List	   *aggdirectargs;	/* direct arguments, if an ordered-set agg */
-	List	   *args;			/* aggregated arguments and sort expressions */
-	List	   *aggorder;		/* ORDER BY (list of SortGroupClause) */
-	List	   *aggdistinct;	/* DISTINCT (list of SortGroupClause) */
-	Expr	   *aggfilter;		/* FILTER expression, if any */
-	bool		aggstar;		/* true if argument list was really '*' */
-	bool		aggvariadic;	/* true if variadic arguments have been
-								 * combined into an array last argument */
-	char		aggkind;		/* aggregate kind (see pg_aggregate.h) */
-	Index		agglevelsup;	/* > 0 if agg belongs to outer query */
-	AggSplit	aggsplit;		/* expected agg-splitting mode of parent Agg */
-	int			aggno;			/* unique ID within the Agg node */
-	int			aggtransno;		/* unique ID of transition state in the Agg */
-	int			location;		/* token location, or -1 if unknown */
+
+	/* pg_proc Oid of the aggregate */
+	Oid			aggfnoid;
+
+	/* type Oid of result of the aggregate */
+	Oid			aggtype;
+
+	/* OID of collation of result */
+	Oid			aggcollid;
+
+	/* OID of collation that function should use */
+	Oid			inputcollid;
+
+	/* type Oid of aggregate's transition value */
+	Oid			aggtranstype;
+
+	/* type Oids of direct and aggregated args */
+	List	   *aggargtypes;
+
+	/* direct arguments, if an ordered-set agg */
+	List	   *aggdirectargs;
+
+	/* aggregated arguments and sort expressions */
+	List	   *args;
+
+	/* ORDER BY (list of SortGroupClause) */
+	List	   *aggorder;
+
+	/* DISTINCT (list of SortGroupClause) */
+	List	   *aggdistinct;
+
+	/* FILTER expression, if any */
+	Expr	   *aggfilter;
+
+	/* true if argument list was really '*' */
+	bool		aggstar;
+
+	/*
+	 * true if variadic arguments have been combined into an array last
+	 * argument
+	 */
+	bool		aggvariadic;
+
+	/* aggregate kind (see pg_aggregate.h) */
+	char		aggkind;
+
+	/* > 0 if agg belongs to outer query */
+	Index		agglevelsup;
+
+	/* expected agg-splitting mode of parent Agg */
+	AggSplit	aggsplit;
+
+	/* unique ID within the Agg node */
+	int			aggno;
+
+	/* unique ID of transition state in the Agg */
+	int			aggtransno;
+
+	/* token location, or -1 if unknown */
+	int			location;
 } Aggref;
 
 /*
@@ -378,12 +418,21 @@ typedef struct Aggref
 typedef struct GroupingFunc
 {
 	Expr		xpr;
-	List	   *args;			/* arguments, not evaluated but kept for
-								 * benefit of EXPLAIN etc. */
-	List	   *refs;			/* ressortgrouprefs of arguments */
-	List	   *cols;			/* actual column positions set by planner */
-	Index		agglevelsup;	/* same as Aggref.agglevelsup */
-	int			location;		/* token location */
+
+	/* arguments, not evaluated but kept for benefit of EXPLAIN etc. */
+	List	   *args;
+
+	/* ressortgrouprefs of arguments */
+	List	   *refs;
+
+	/* actual column positions set by planner */
+	List	   *cols;
+
+	/* same as Aggref.agglevelsup */
+	Index		agglevelsup;
+
+	/* token location */
+	int			location;
 } GroupingFunc;
 
 /*
@@ -548,14 +597,30 @@ typedef struct NamedArgExpr
 typedef struct OpExpr
 {
 	Expr		xpr;
-	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of underlying function */
-	Oid			opresulttype;	/* PG_TYPE OID of result value */
-	bool		opretset;		/* true if operator returns set */
-	Oid			opcollid;		/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that operator should use */
-	List	   *args;			/* arguments to the operator (1 or 2) */
-	int			location;		/* token location, or -1 if unknown */
+
+	/* PG_OPERATOR OID of the operator */
+	Oid			opno;
+
+	/* PG_PROC OID of underlying function */
+	Oid			opfuncid;
+
+	/* PG_TYPE OID of result value */
+	Oid			opresulttype;
+
+	/* true if operator returns set */
+	bool		opretset;
+
+	/* OID of collation of result */
+	Oid			opcollid;
+
+	/* OID of collation that operator should use */
+	Oid			inputcollid;
+
+	/* arguments to the operator (1 or 2) */
+	List	   *args;
+
+	/* token location, or -1 if unknown */
+	int			location;
 } OpExpr;
 
 /*
@@ -605,15 +670,30 @@ typedef OpExpr NullIfExpr;
 typedef struct ScalarArrayOpExpr
 {
 	Expr		xpr;
-	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of comparison function */
-	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
-	Oid			negfuncid;		/* PG_PROC OID of negator of opfuncid function
-								 * or InvalidOid.  See above */
-	bool		useOr;			/* true for ANY, false for ALL */
-	Oid			inputcollid;	/* OID of collation that operator should use */
-	List	   *args;			/* the scalar and array operands */
-	int			location;		/* token location, or -1 if unknown */
+
+	/* PG_OPERATOR OID of the operator */
+	Oid			opno;
+
+	/* PG_PROC OID of comparison function */
+	Oid			opfuncid;
+
+	/* PG_PROC OID of hash func or InvalidOid */
+	Oid			hashfuncid;
+
+	/* PG_PROC OID of negator of opfuncid function or InvalidOid.  See above */
+	Oid			negfuncid;
+
+	/* true for ANY, false for ALL */
+	bool		useOr;
+
+	/* OID of collation that operator should use */
+	Oid			inputcollid;
+
+	/* the scalar and array operands */
+	List	   *args;
+
+	/* token location, or -1 if unknown */
+	int			location;
 } ScalarArrayOpExpr;
 
 /*
-- 
2.36.1

#38Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#37)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

Here is a patch that reformats the relevant (and a few more) comments
that way. This has been run through pgindent, so the formatting should
be stable.

Now that that's been pushed, the main patch is of course quite broken.
Are you working on a rebase?

I looked through the last published version of the main patch (Alvaro's
0002 from 2022-04-19), without trying to actually test it, and found
a couple of things that look wrong in the Makefiles:

* AFAICT, the infrastructure for removing the generated files at
"make *clean" is incomplete. In particular I don't see any code
for removing the symlinks or the associated stamp file during
"make clean". It looks like the existing header symlinks are
all cleaned up in src/include/Makefile's "clean" rule, so you
could do likewise for these. Also, the "make maintainer-clean"
infrastructure seems incomplete --- shouldn't src/backend/Makefile's
maintainer-clean rule now also do
$(MAKE) -C nodes $@
?

* There are some useful comments in backend/utils/Makefile that
I think should be carried over along with the make rules that
(it looks like) you cribbed from there, notably

# fmgr-stamp records the last time we ran Gen_fmgrtab.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).

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

and something similar to this for the "clean" rule:
# fmgroids.h, fmgrprotos.h, fmgrtab.c, fmgr-stamp, and errcodes.h are in the
# distribution tarball, so they are not cleaned here.

Also, I share David's upthread allergy to the option names "path_hackN"
and to documenting those only inside the conversion script. I think
the existing text that you moved into the script, such as this bit:

# We do not print the parent, else we'd be in infinite
# recursion. We can print the parent's relids for
# identification purposes, though. We print the pathtarget
# only if it's not the default one for the rel. We also do
# not print the whole of param_info, since it's printed via
# RelOptInfo; it's sufficient and less cluttering to print
# just the required outer relids.

is perfectly adequate as documentation, it just needs to be somewhere else
(pathnodes.h seems fine, if not nodes.h) and labeled as to exactly which
pg_node_attr option invokes which behavior.

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

regards, tom lane

#39Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#38)
1 attachment(s)
Re: automatically generating node support functions

On 03.07.22 21:14, Tom Lane wrote:

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

Here is a patch that reformats the relevant (and a few more) comments
that way. This has been run through pgindent, so the formatting should
be stable.

Now that that's been pushed, the main patch is of course quite broken.
Are you working on a rebase?

attached

* AFAICT, the infrastructure for removing the generated files at
"make *clean" is incomplete.

I have fixed all the makefiles per your suggestions.

and something similar to this for the "clean" rule:
# fmgroids.h, fmgrprotos.h, fmgrtab.c, fmgr-stamp, and errcodes.h are in the
# distribution tarball, so they are not cleaned here.

Except this one, since there is no clean rule. I think seeing that
files are listed under a maintainer-clean target conveys that same message.

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

I'll look into that again.

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

good point

Attachments:

v6-0001-Automatically-generate-node-support-functions.patchtext/plain; charset=UTF-8; name=v6-0001-Automatically-generate-node-support-functions.patchDownload
From 0ad86183662b6637c9ec9a29374fecb4d11202e2 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 4 Jul 2022 14:17:02 +0200
Subject: [PATCH v6] Automatically generate node support functions

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

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

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

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

Subtyping (TidScan -> Scan) is supported.

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

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

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a02006788..953c80db5a 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -143,11 +143,15 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
 submake-catalog-headers:
 	$(MAKE) -C catalog distprep generated-header-symlinks
 
+# run this unconditionally to avoid needing to know its dependencies here:
+submake-nodes-headers:
+	$(MAKE) -C nodes distprep generated-header-symlinks
+
 # run this unconditionally to avoid needing to know its dependencies here:
 submake-utils-headers:
 	$(MAKE) -C utils distprep generated-header-symlinks
 
-.PHONY: submake-catalog-headers submake-utils-headers
+.PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 # Make symlinks for these headers in the include directory. That way
 # we can cut down on the -I options. Also, a symlink is automatically
@@ -162,7 +166,7 @@ submake-utils-headers:
 
 .PHONY: generated-headers
 
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
@@ -185,6 +189,7 @@ distprep:
 	$(MAKE) -C parser	gram.c gram.h scan.c
 	$(MAKE) -C bootstrap	bootparse.c bootscanner.c
 	$(MAKE) -C catalog	distprep
+	$(MAKE) -C nodes	distprep
 	$(MAKE) -C replication	repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
 	$(MAKE) -C storage/lmgr	lwlocknames.h lwlocknames.c
 	$(MAKE) -C utils	distprep
@@ -297,6 +302,7 @@ distclean: clean
 
 maintainer-clean: distclean
 	$(MAKE) -C catalog $@
+	$(MAKE) -C nodes $@
 	$(MAKE) -C utils $@
 	rm -f bootstrap/bootparse.c \
 	      bootstrap/bootscanner.c \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..0c14b5697b
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,4 @@
+/node-support-stamp
+/nodetags.h
+/*funcs.funcs.c
+/*funcs.switch.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..20c1ec08e8 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,57 @@ 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 $@
+
+maintainer-clean: clean
+	rm -f node-support-stamp *funcs.funcs.c *funcs.switch.c nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 51d630fa89..b756744502 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -23,11 +23,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
-#include "nodes/plannodes.h"
 #include "utils/datum.h"
-#include "utils/rel.h"
 
 
 /*
@@ -73,6 +69,9 @@
 	(newnode->fldname = from->fldname)
 
 
+#include "copyfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /* ****************************************************************
  *					 plannodes.h copy functions
  * ****************************************************************
@@ -1465,6 +1464,7 @@ _copyVar(const Var *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * _copyConst
@@ -1504,6 +1504,7 @@ _copyConst(const Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -3247,6 +3248,7 @@ _copyParamRef(const ParamRef *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -3287,6 +3289,7 @@ _copyA_Const(const A_Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -5452,6 +5455,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *					extensible.h copy functions
@@ -5474,6 +5478,7 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /* ****************************************************************
  *					value.h copy functions
  * ****************************************************************
@@ -5544,6 +5549,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5564,6 +5570,8 @@ copyObjectImpl(const void *from)
 
 	switch (nodeTag(from))
 	{
+#include "copyfuncs.switch.c"
+#ifdef OBSOLETE
 			/*
 			 * PLAN NODES
 			 */
@@ -6008,6 +6016,7 @@ copyObjectImpl(const void *from)
 		case T_BitString:
 			retval = _copyBitString(from);
 			break;
+#endif /*OBSOLETE*/
 
 			/*
 			 * LIST NODES
@@ -6025,6 +6034,7 @@ copyObjectImpl(const void *from)
 			retval = list_copy(from);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -6576,6 +6586,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 e747e1667d..f9f39d782f 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
  */
@@ -242,6 +240,7 @@ _equalVar(const Var *a, const Var *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -264,6 +263,7 @@ _equalConst(const Const *a, const Const *b)
 						a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -1288,6 +1288,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -1309,6 +1310,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const ExtensibleNode *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2799,6 +2801,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2815,6 +2818,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3452,6 +3456,7 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3512,6 +3517,7 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from value.h
  */
@@ -3555,6 +3561,7 @@ _equalBitString(const BitString *a, const BitString *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * equal
@@ -3585,6 +3592,8 @@ equal(const void *a, const void *b)
 
 	switch (nodeTag(a))
 	{
+#include "equalfuncs.switch.c"
+#ifdef OBSOLETE
 			/*
 			 * PRIMITIVE NODES
 			 */
@@ -3805,6 +3814,7 @@ equal(const void *a, const void *b)
 		case T_PlaceHolderInfo:
 			retval = _equalPlaceHolderInfo(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		case T_List:
 		case T_IntList:
@@ -3812,6 +3822,7 @@ equal(const void *a, const void *b)
 			retval = _equalList(a, b);
 			break;
 
+#ifdef OBSOLETE
 		case T_Integer:
 			retval = _equalInteger(a, b);
 			break;
@@ -4411,6 +4422,7 @@ equal(const void *a, const void *b)
 		case T_JsonTableColumn:
 			retval = _equalJsonTableColumn(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
new file mode 100644
index 0000000000..edbafd3e5b
--- /dev/null
+++ b/src/backend/nodes/gen_node_support.pl
@@ -0,0 +1,729 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/backend/nodes/gen_node_support.pl
+#
+#----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use File::Basename;
+
+use FindBin;
+use lib "$FindBin::RealBin/../catalog";
+
+use Catalog;  # for RenameTempFile
+
+
+# Test whether first argument is element of the list in the second
+# argument
+sub elem
+{
+	my $x = shift;
+	return grep { $_ eq $x } @_;
+}
+
+
+# collect node names
+my @node_types = qw(Node);
+# collect info for each node type
+my %node_type_info;
+
+# node types we don't want copy support for
+my @no_copy;
+# node types we don't want read/write support for
+my @no_read_write;
+
+# types that are copied by straight assignment
+my @scalar_types = qw(
+	bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+	AclMode AttrNumber Cardinality Cost Index Oid Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr
+);
+
+# collect enum types
+my @enum_types;
+
+# Abstract types are types that cannot be instantiated but that can be
+# supertypes of other types.  We track their fields, so that subtypes
+# can use them, but we don't emit a node tag, so you can't instantiate
+# them.
+my @abstract_types = qw(
+	Node Expr
+	BufferHeapTupleTableSlot HeapTupleTableSlot MinimalTupleTableSlot VirtualTupleTableSlot
+	JoinPath
+	PartitionPruneStep
+);
+
+# Special cases that either don't have their own struct or the struct
+# is not in a header file.  We just generate node tags for them, but
+# they otherwise don't participate in node support.
+my @extra_tags = qw(
+	IntList OidList
+	AllocSetContext GenerationContext SlabContext
+	TIDBitmap
+	WindowObjectData
+);
+
+# This is a regular node, but we skip parsing it from its header file
+# since we won't use its internal structure here anyway.
+push @node_types, qw(List);
+
+# pathnodes.h exceptions: We don't support copying RelOptInfo,
+# IndexOptInfo, or Path nodes.  There are some subsidiary structs that
+# are useful to copy, though.
+push @no_copy, qw(
+	RelOptInfo IndexOptInfo Path PlannerGlobal EquivalenceClass EquivalenceMember ForeignKeyOptInfo
+	GroupingSetData IncrementalSortPath IndexClause MinMaxAggInfo PathTarget PlannerInfo PlannerParamItem
+	ParamPathInfo RollupData RowIdentityVarInfo StatisticExtInfo
+);
+# EquivalenceClasses are never moved, so just shallow-copy the pointer
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember*);
+push @scalar_types, qw(QualCost);
+
+# See special treatment in outNode() and nodeRead() for these.
+push @no_read_write, qw(BitString Boolean Float Integer List String);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation);
+
+
+## read input
+
+foreach my $infile (@ARGV)
+{
+	my $in_struct;
+	my $subline;
+	my $is_node_struct;
+	my $supertype;
+	my $supertype_field;
+
+	my @my_fields;
+	my %my_field_types;
+	my %my_field_attrs;
+
+	open my $ifh, '<', $infile or die "could not open \"$infile\": $!";
+
+	my $file_content = do { local $/; <$ifh> };
+
+	# strip C comments
+	$file_content =~ s{/\*.*?\*/}{}gs;
+
+	foreach my $line (split /\n/, $file_content)
+	{
+		chomp $line;
+		$line =~ s/\s*$//;
+		next if $line eq '';
+		next if $line =~ /^#(define|ifdef|endif)/;
+
+		# we are analyzing a struct definition
+		if ($in_struct)
+		{
+			$subline++;
+
+			# first line should have opening brace
+			if ($subline == 1)
+			{
+				$is_node_struct = 0;
+				$supertype = undef;
+				next if $line eq '{';
+				die;
+			}
+			# second line should have node tag or supertype
+			elsif ($subline == 2)
+			{
+				if ($line =~ /^\s*NodeTag\s+type;/)
+				{
+					$is_node_struct = 1;
+					next;
+				}
+				elsif ($line =~ /\s*(\w+)\s+(\w+);/ and elem $1, @node_types)
+				{
+					$is_node_struct = 1;
+					$supertype = $1;
+					$supertype_field = $2;
+					next;
+				}
+			}
+
+			# end of struct
+			if ($line =~ /^\}\s*$in_struct;$/ || $line =~ /^\};$/)
+			{
+				if ($is_node_struct)
+				{
+					# This is the end of a node struct definition.
+					# Save everything we have collected.
+
+					# node name
+					push @node_types, $in_struct;
+
+					# field names, types, attributes
+					my @f = @my_fields;
+					my %ft = %my_field_types;
+					my %fa = %my_field_attrs;
+
+					# If there is a supertype, add those fields, too.
+					if ($supertype)
+					{
+						my @superfields;
+						foreach my $sf (@{$node_type_info{$supertype}->{fields}})
+						{
+							my $fn = "${supertype_field}.$sf";
+							push @superfields, $fn;
+							$ft{$fn} = $node_type_info{$supertype}->{field_types}{$sf};
+							$fa{$fn} = $node_type_info{$supertype}->{field_attrs}{$sf};
+							$fa{$fn} =~ s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/ if $fa{$fn};
+						}
+						unshift @f, @superfields;
+					}
+					# save in global info structure
+					$node_type_info{$in_struct}->{fields} = \@f;
+					$node_type_info{$in_struct}->{field_types} = \%ft;
+					$node_type_info{$in_struct}->{field_attrs} = \%fa;
+
+					# Nodes from these files don't need to be
+					# supported, except the node tags.
+					if (elem basename($infile),
+						qw(execnodes.h trigger.h event_trigger.h amapi.h tableam.h
+							tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h))
+					{
+						push @no_copy, $in_struct;
+						push @no_read_write, $in_struct;
+					}
+
+					# We do not support copying Path trees, mainly
+					# because the circular linkages between RelOptInfo
+					# and Path nodes can't be handled easily in a
+					# simple depth-first traversal.
+					if ($supertype && ($supertype eq 'Path' || $supertype eq 'JoinPath'))
+					{
+						push @no_copy, $in_struct;
+					}
+				}
+
+				# start new cycle
+				$in_struct = undef;
+				@my_fields = ();
+				%my_field_types = ();
+				%my_field_attrs = ();
+			}
+			# normal struct field
+			elsif ($line =~ /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w() ]*)\))?;/)
+			{
+				if ($is_node_struct)
+				{
+					my $type = $1;
+					my $name = $2;
+					my $array_size = $3;
+					my $attr = $4;
+
+					# strip "const"
+					$type =~ s/^const\s*//;
+					# strip trailing space
+					$type =~ s/\s*$//;
+					# strip space between type and "*" (pointer) */
+					$type =~ s/\s+\*$/*/;
+
+					die if $type eq '';
+
+					$type = $type . $array_size if $array_size;
+					push @my_fields, $name;
+					$my_field_types{$name} = $type;
+					$my_field_attrs{$name} = $attr;
+				}
+			}
+			else
+			{
+				if ($is_node_struct)
+				{
+					#warn "$infile:$.: could not parse \"$line\"\n";
+				}
+			}
+		}
+		# not in a struct
+		else
+		{
+			# start of a struct?
+			if ($line =~ /^(?:typedef )?struct (\w+)(\s*\/\*.*)?$/ && $1 ne 'Node')
+			{
+				$in_struct = $1;
+				$subline = 0;
+			}
+			# one node type typedef'ed directly from another
+			elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types)
+			{
+				my $alias_of = $1;
+				my $n = $2;
+
+				# copy everything over
+				push @node_types, $n;
+				my @f = @{$node_type_info{$alias_of}->{fields}};
+				my %ft = %{$node_type_info{$alias_of}->{field_types}};
+				my %fa = %{$node_type_info{$alias_of}->{field_attrs}};
+				$node_type_info{$n}->{fields} = \@f;
+				$node_type_info{$n}->{field_types} = \%ft;
+				$node_type_info{$n}->{field_attrs} = \%fa;
+			}
+			# collect enum names
+			elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/)
+			{
+				push @enum_types, $1;
+			}
+		}
+	}
+
+	if ($in_struct)
+	{
+		die "runaway \"$in_struct\" in file \"$infile\"\n";
+	}
+
+	close $ifh;
+} # for each file
+
+
+## write output
+
+my $tmpext  = ".tmp$$";
+
+# nodetags.h
+
+open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
+
+my $i = 1;
+foreach my $n (@node_types,@extra_tags)
+{
+	next if elem $n, @abstract_types;
+	print $nt "\tT_${n} = $i,\n";
+	$i++;
+}
+
+close $nt;
+
+
+# make #include lines necessary to pull in all the struct definitions
+my $node_includes = '';
+foreach my $infile (sort @ARGV)
+{
+	$infile =~ s!.*src/include/!!;
+	$node_includes .= qq{#include "$infile"\n};
+}
+
+
+# copyfuncs.c, equalfuncs.c
+
+open my $cff, '>', 'copyfuncs.funcs.c' . $tmpext or die $!;
+open my $eff, '>', 'equalfuncs.funcs.c' . $tmpext or die $!;
+open my $cfs, '>', 'copyfuncs.switch.c' . $tmpext or die $!;
+open my $efs, '>', 'equalfuncs.switch.c' . $tmpext or die $!;
+
+# add required #include lines to each file set
+print $cff $node_includes;
+print $eff $node_includes;
+
+# Nodes with custom copy implementations are skipped from .funcs.c but
+# need case statements in .switch.c.
+my @custom_copy = qw(A_Const Const ExtensibleNode);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_copy;
+	next if $n eq 'List';
+
+	print $cfs "
+\t\tcase T_${n}:
+\t\t\tretval = _copy${n}(from);
+\t\t\tbreak;";
+
+	print $efs "
+\t\tcase T_${n}:
+\t\t\tretval = _equal${n}(a, b);
+\t\t\tbreak;";
+
+	next if elem $n, @custom_copy;
+
+	print $cff "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+";
+
+	print $eff "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+";
+
+	# print instructions for each field
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $copy_ignore = ($a =~ /\bcopy_ignore\b/);
+		my $equal_ignore = ($a =~ /\bequal_ignore\b/);
+
+		# select instructions by field type
+		if ($t eq 'char*')
+		{
+			print $cff "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_STRING_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $cff "\tCOPY_BITMAPSET_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_BITMAPSET_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $cff "\tCOPY_LOCATION_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_LOCATION_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif (elem $t, @scalar_types or elem $t, @enum_types)
+		{
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			if ($a =~ /\bequal_ignore_if_zero\b/)
+			{
+				print $eff "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
+			}
+			else
+			{
+				print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+			}
+		}
+		# scalar type pointer
+		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
+		{
+			my $tt = $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $cff "\tCOPY_POINTER_FIELD($f, list_length(from->$array_size_field) * sizeof($tt));\n" unless $copy_ignore;
+				print $eff "\tCOMPARE_POINTER_FIELD($f, list_length(a->$array_size_field) * sizeof($tt));\n" unless $equal_ignore;
+			}
+			else
+			{
+				print $cff "\tCOPY_POINTER_FIELD($f, from->$array_size_field * sizeof($tt));\n" unless $copy_ignore;
+				print $eff "\tCOMPARE_POINTER_FIELD($f, a->$array_size_field * sizeof($tt));\n" unless $equal_ignore;
+			}
+		}
+		# node type
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $cff "\tCOPY_NODE_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
+		}
+		# array (inline)
+		elsif ($t =~ /\w+\[/)
+		{
+			print $cff "\tCOPY_ARRAY_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_ARRAY_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			# Fields of these types are required to be a pointer to a
+			# static table of callback functions.  So we don't copy
+			# the table itself, just reference the original one.
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $cff "
+\treturn newnode;
+}
+";
+	print $eff "
+\treturn true;
+}
+";
+}
+
+close $cff;
+close $eff;
+close $cfs;
+close $efs;
+
+
+# outfuncs.c, readfuncs.c
+
+open my $off, '>', 'outfuncs.funcs.c' . $tmpext or die $!;
+open my $rff, '>', 'readfuncs.funcs.c' . $tmpext or die $!;
+open my $ofs, '>', 'outfuncs.switch.c' . $tmpext or die $!;
+open my $rfs, '>', 'readfuncs.switch.c' . $tmpext or die $!;
+
+print $off $node_includes;
+print $rff $node_includes;
+
+my @custom_readwrite = qw(A_Const A_Expr BoolExpr Const Constraint EquivalenceClass ExtensibleNode ForeignKeyOptInfo Query RangeTblEntry);
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_read_write;
+
+	# XXX For now, skip all "Stmt"s except that ones that were there before.
+	if ($n =~ /Stmt$/)
+	{
+		my @keep = qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt ImportForeignSchemaStmt IndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt);
+		next unless elem $n, @keep;
+	}
+
+	# XXX Also skip read support for those that didn't have it before.
+	my $no_read = ($n eq 'A_Star' || $n eq 'A_Const' || $n eq 'A_Expr' || $n eq 'Constraint' || $n =~ /Path$/ || $n eq 'EquivalenceClass' || $n eq 'ForeignKeyCacheInfo' || $n eq 'ForeignKeyOptInfo' || $n eq 'PathTarget');
+
+	# output format starts with upper case node type, underscores stripped
+	my $N = uc $n;
+	$N =~ s/_//g;
+
+	print $ofs "\t\t\tcase T_${n}:\n".
+	  "\t\t\t\t_out${n}(str, obj);\n".
+	  "\t\t\t\tbreak;\n";
+
+	print $rfs "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+
+	next if elem $n, @custom_readwrite;
+
+	print $off "
+static void
+_out${n}(StringInfo str, const $n *node)
+{
+\tWRITE_NODE_TYPE(\"$N\");
+
+";
+
+	print $rff "
+static $n *
+_read${n}(void)
+{
+\tREAD_LOCALS($n);
+
+" unless $no_read;
+
+	# print instructions for each field
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my $a = $node_type_info{$n}->{field_attrs}{$f} || '';
+		my $readwrite_ignore = ($a =~ /\breadwrite_ignore\b/);
+		next if $readwrite_ignore;
+
+		# XXX Previously, for subtyping, only the leaf field name is
+		# used. Ponder whether we want to keep it that way.
+
+		# select instructions by field type
+		if ($t eq 'bool')
+		{
+			print $off "\tWRITE_BOOL_FIELD($f);\n";
+			print $rff "\tREAD_BOOL_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $off "\tWRITE_LOCATION_FIELD($f);\n";
+			print $rff "\tREAD_LOCATION_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' || $t eq 'int32' || $t eq 'AttrNumber' || $t eq 'StrategyNumber')
+		{
+			print $off "\tWRITE_INT_FIELD($f);\n";
+			print $rff "\tREAD_INT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint32' || $t eq 'bits32' || $t eq 'AclMode' || $t eq 'BlockNumber' || $t eq 'Index' || $t eq 'SubTransactionId')
+		{
+			print $off "\tWRITE_UINT_FIELD($f);\n";
+			print $rff "\tREAD_UINT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint64')
+		{
+			print $off "\tWRITE_UINT64_FIELD($f);\n";
+			print $rff "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Oid')
+		{
+			print $off "\tWRITE_OID_FIELD($f);\n";
+			print $rff "\tREAD_OID_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'long')
+		{
+			print $off "\tWRITE_LONG_FIELD($f);\n";
+			print $rff "\tREAD_LONG_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char')
+		{
+			print $off "\tWRITE_CHAR_FIELD($f);\n";
+			print $rff "\tREAD_CHAR_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'double')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.6f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cardinality')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.0f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cost')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.2f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'QualCost')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f.startup, \"%.2f\");\n";
+			print $off "\tWRITE_FLOAT_FIELD($f.per_tuple, \"%.2f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read;
+			print $rff "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
+		}
+		elsif ($t eq 'Selectivity')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.4f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $off "\tWRITE_STRING_FIELD($f);\n";
+			print $rff "\tREAD_STRING_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $off "\tWRITE_BITMAPSET_FIELD($f);\n";
+			print $rff "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
+		}
+		elsif (elem $t, @enum_types)
+		{
+			print $off "\tWRITE_ENUM_FIELD($f, $t);\n";
+			print $rff "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read;
+		}
+		# arrays
+		elsif ($t =~ /(\w+)(\*|\[)/ and elem $1, @scalar_types)
+		{
+			my $tt = uc $1;
+			my $array_size_field;
+			if ($a =~ /\barray_size.([\w.]+)/)
+			{
+				$array_size_field = $1;
+			}
+			else
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $off "\tWRITE_${tt}_ARRAY($f, list_length(node->$array_size_field));\n";
+				print $rff "\tREAD_${tt}_ARRAY($f, list_length(local_node->$array_size_field));\n" unless $no_read;
+			}
+			else
+			{
+				print $off "\tWRITE_${tt}_ARRAY($f, node->$array_size_field);\n";
+				print $rff "\tREAD_${tt}_ARRAY($f, local_node->$array_size_field);\n" unless $no_read;
+			}
+		}
+		# Special treatments of several Path node fields
+		#
+		# We do not print the parent, else we'd be in infinite
+		# recursion.  We can print the parent's relids for
+		# identification purposes, though.  We print the pathtarget
+		# only if it's not the default one for the rel.  We also do
+		# not print the whole of param_info, since it's printed via
+		# RelOptInfo; it's sufficient and less cluttering to print
+		# just the required outer relids.
+		elsif ($t eq 'RelOptInfo*' && $a eq 'path_hack1')
+		{
+			print $off "\tappendStringInfoString(str, \" :parent_relids \");\n".
+			  "\toutBitmapset(str, node->$f->relids);\n";
+		}
+		elsif ($t eq 'PathTarget*' && $a eq 'path_hack2')
+		{
+			(my $f2 = $f) =~ s/pathtarget/parent/;
+			print $off "\tif (node->$f != node->$f2->reltarget)\n".
+			  "\t\tWRITE_NODE_FIELD($f);\n";
+		}
+		elsif ($t eq 'ParamPathInfo*' && $a eq 'path_hack3')
+		{
+			print $off "\tif (node->$f)\n".
+			  "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
+			  "\telse\n".
+			  "\t\toutBitmapset(str, NULL);\n";
+		}
+		# node type
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $off "\tWRITE_NODE_FIELD($f);\n";
+			print $rff "\tREAD_NODE_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $off q{
+	/* CustomName is a key to lookup CustomScanMethods */
+	appendStringInfoString(str, " :methods ");
+	outToken(str, node->methods->CustomName);
+};
+			print $rff q!
+	{
+		/* Lookup CustomScanMethods by CustomName */
+		char	   *custom_name;
+		const CustomScanMethods *methods;
+		token = pg_strtok(&length); /* skip methods: */
+		token = pg_strtok(&length); /* CustomName */
+		custom_name = nullable_string(token, length);
+		methods = GetCustomScanMethods(custom_name, false);
+		local_node->methods = methods;
+	}
+! unless $no_read;
+		}
+		# various field types to ignore
+		elsif ($t eq 'ParamListInfo' || $t =~ /PartitionBoundInfoData/ || $t eq 'PartitionDirectory' || $t eq 'PartitionScheme' || $t eq 'void*' || $t =~ /\*\*$/)
+		{
+			# ignore
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $off "}
+";
+	print $rff "
+\tREAD_DONE();
+}
+" unless $no_read;
+}
+
+close $off;
+close $rff;
+close $ofs;
+close $rfs;
+
+
+# now rename the temporary files to their final name
+foreach my $file (qw(nodetags.h copyfuncs.funcs.c copyfuncs.switch.c equalfuncs.funcs.c equalfuncs.switch.c outfuncs.funcs.c outfuncs.switch.c readfuncs.funcs.c readfuncs.switch.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ce12915592..58ffc9c811 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);
 
@@ -302,6 +301,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
 }
 
 
+#include "outfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from plannodes.h
  */
@@ -1151,6 +1153,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)
@@ -1172,6 +1175,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)
 {
@@ -1342,6 +1346,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)
@@ -1370,6 +1375,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2582,6 +2588,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)
@@ -2609,6 +2616,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)
 {
@@ -2620,6 +2628,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)
@@ -2648,6 +2657,7 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
 	WRITE_UINT_FIELD(ec_max_security);
 }
 
+#ifdef OBSOLETE
 static void
 _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 {
@@ -2832,6 +2842,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
 	WRITE_NODE_FIELD(item);
 	WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2854,6 +2865,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -3187,6 +3199,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)
@@ -3261,6 +3274,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3426,6 +3440,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)
@@ -3506,6 +3521,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3529,6 +3545,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)
@@ -3647,6 +3664,7 @@ _outBitString(StringInfo str, const BitString *node)
 	appendStringInfoString(str, node->bsval);
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3678,6 +3696,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)
@@ -3694,6 +3713,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)
 {
@@ -3838,6 +3858,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)
@@ -3960,6 +3981,7 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -4020,6 +4042,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -4051,6 +4074,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;
@@ -4762,6 +4787,7 @@ outNode(StringInfo str, const void *obj)
 			case T_JsonTableSibling:
 				_outJsonTableSibling(str, obj);
 				break;
+#endif /*OBSOLETE*/
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6a05b69415..f427aa05ec 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -33,9 +33,7 @@
 #include <math.h>
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/parsenodes.h"
-#include "nodes/plannodes.h"
+#include "nodes/bitmapset.h"
 #include "nodes/readfuncs.h"
 
 
@@ -238,6 +236,8 @@ readBitmapset(void)
 	return _readBitmapset();
 }
 
+#include "readfuncs.funcs.c"
+
 /*
  * _readQuery
  */
@@ -291,6 +291,7 @@ _readQuery(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readNotifyStmt
  */
@@ -629,6 +630,7 @@ _readVar(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readConst
@@ -655,6 +657,7 @@ _readConst(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readParam
  */
@@ -880,6 +883,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readBoolExpr
@@ -907,6 +911,7 @@ _readBoolExpr(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readSubLink
  */
@@ -1649,6 +1654,7 @@ _readAppendRelInfo(void)
 /*
  *	Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1744,6 +1750,7 @@ _readRangeTblEntry(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2872,6 +2879,7 @@ _readAlternativeSubPlan(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2903,6 +2911,7 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2937,6 +2946,7 @@ _readPartitionRangeDatum(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2961,7 +2971,11 @@ parseNodeString(void)
 #define MATCH(tokname, namelen) \
 	(length == namelen && memcmp(token, tokname, namelen) == 0)
 
-	if (MATCH("QUERY", 5))
+	if (false)
+		;
+#include "readfuncs.switch.c"
+#ifdef OBSOLETE
+	else if (MATCH("QUERY", 5))
 		return_value = _readQuery();
 	else if (MATCH("WITHCHECKOPTION", 15))
 		return_value = _readWithCheckOption();
@@ -3235,6 +3249,7 @@ parseNodeString(void)
 		return_value = _readJsonTableParent();
 	else if (MATCH("JSONTABLESIBLING", 16))
 		return_value = _readJsonTableSibling();
+#endif /*OBSOLETE*/
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/include/Makefile b/src/include/Makefile
index 5f257a958c..17cfd268b8 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -81,6 +81,7 @@ clean:
 	rm -f parser/gram.h storage/lwlocknames.h utils/probes.h
 	rm -f catalog/schemapg.h catalog/system_fk_info.h
 	rm -f catalog/pg_*_d.h catalog/header-stamp
+	rm -f nodes/nodetags.h nodes/header-stamp
 
 distclean maintainer-clean: clean
 	rm -f pg_config.h pg_config_ext.h pg_config_os.h stamp-h stamp-ext-h
diff --git a/src/include/nodes/.gitignore b/src/include/nodes/.gitignore
new file mode 100644
index 0000000000..99fb1d3787
--- /dev/null
+++ b/src/include/nodes/.gitignore
@@ -0,0 +1,2 @@
+/nodetags.h
+/header-stamp
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b3b407579b..8a62a2fee5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -27,6 +27,8 @@ typedef enum NodeTag
 {
 	T_Invalid = 0,
 
+#include "nodes/nodetags.h"
+#ifdef OBSOLETE
 	/*
 	 * TAGS FOR EXECUTOR NODES (execnodes.h)
 	 */
@@ -562,8 +564,33 @@ typedef enum NodeTag
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition, /* in nodes/supportnodes.h */
 	T_SupportRequestWFuncMonotonic	/* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * Used in node definitions to set extra information for gen_node_support.pl
+ *
+ * The argument is a space-separated list of attributes.  The following
+ * attributes are currently used:
+ *
+ * - array_size(OTHERFIELD): This field is a dynamically allocated array with
+ *   size indicated by the mentioned other field.  The other field is either a
+ *   scalar or a list, in which case the length of the list is used.
+ *
+ * - copy_ignore: Ignore the field for copy.
+ *
+ * - equal_ignore: Ignore the field for equality.
+ *
+ * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
+ *   (Otherwise, compare normally.)
+ *
+ * - readwrite_ignore: Ignore the field for read/write.
+ *
+ * Unknown attributes are ignored.  Some additional attributes are used for
+ * special "hack" cases.
+ */
+#define pg_node_attr(attrs)
+
 /*
  * The first field of a node of any type is guaranteed to be the NodeTag.
  * Hence the type of any node can be gotten by casting it to Node. Declaring
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f93d866548..9a9a3e841c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -123,8 +123,8 @@ typedef struct Query
 
 	QuerySource querySource;	/* where did I come from? */
 
-	/* query identifier (can be set by plugins) */
-	uint64		queryId;
+	/* query identifier (can be set by plugins); ignored for equal, might not be set */
+	uint64		queryId pg_node_attr(equal_ignore);
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b88cfb8dc0..a4fb9ce8ef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -227,7 +227,7 @@ struct PlannerInfo
 	 * GEQO.
 	 */
 	List	   *join_rel_list;
-	struct HTAB *join_rel_hash;
+	struct HTAB *join_rel_hash pg_node_attr(readwrite_ignore);
 
 	/*
 	 * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -333,12 +333,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));
 	/* List of MinMaxAggInfos */
 	List	   *minmax_aggs;
 
 	/* context holding PlannerInfo */
-	MemoryContext planner_cxt;
+	MemoryContext planner_cxt pg_node_attr(readwrite_ignore);
 
 	Cardinality total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -378,8 +378,8 @@ 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(readwrite_ignore);
+	bool	   *isUsedSubplan pg_node_attr(readwrite_ignore);
 
 	/* optional private data for join_search_hook, e.g., GEQO */
 	void	   *join_search_private;
@@ -747,9 +747,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(readwrite_ignore);
 	/* array indexed [min_attr .. max_attr] */
-	int32	   *attr_widths;
+	int32	   *attr_widths pg_node_attr(readwrite_ignore);
 	/* LATERAL Vars and PHVs referenced by rel */
 	List	   *lateral_vars;
 	/* rels that reference me laterally */
@@ -784,16 +784,16 @@ 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(readwrite_ignore);
+	void	   *fdw_private pg_node_attr(readwrite_ignore);
 
 	/*
 	 * cache space for remembering if we have proven this relation unique
 	 */
 	/* known unique for these other relid set(s) */
-	List	   *unique_for_rels;
+	List	   *unique_for_rels pg_node_attr(readwrite_ignore);
 	/* known not unique for these set(s) */
-	List	   *non_unique_for_rels;
+	List	   *non_unique_for_rels pg_node_attr(readwrite_ignore);
 
 	/*
 	 * used by various scans and joins:
@@ -917,8 +917,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(readwrite_ignore);
 
 	/*
 	 * index-size statistics (from pg_class and elsewhere)
@@ -938,31 +938,34 @@ 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(readwrite_ignore);
 	/* OIDs of collations of index columns */
-	Oid		   *indexcollations;
+	Oid		   *indexcollations pg_node_attr(readwrite_ignore);
 	/* OIDs of operator families for columns */
-	Oid		   *opfamily;
+	Oid		   *opfamily pg_node_attr(readwrite_ignore);
 	/* OIDs of opclass declared input data types */
-	Oid		   *opcintype;
+	Oid		   *opcintype pg_node_attr(readwrite_ignore);
 	/* OIDs of btree opfamilies, if orderable */
-	Oid		   *sortopfamily;
+	Oid		   *sortopfamily pg_node_attr(readwrite_ignore);
 	/* is sort order descending? */
-	bool	   *reverse_sort;
+	bool	   *reverse_sort pg_node_attr(readwrite_ignore);
 	/* do NULLs come first in the sort order? */
-	bool	   *nulls_first;
+	bool	   *nulls_first pg_node_attr(readwrite_ignore);
 	/* opclass-specific options for columns */
-	bytea	  **opclassoptions;
+	bytea	  **opclassoptions pg_node_attr(readwrite_ignore);
 	/* which index cols can be returned in an index-only scan? */
-	bool	   *canreturn;
+	bool	   *canreturn pg_node_attr(readwrite_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(readwrite_ignore);
 	/* predicate if a partial index, else NIL */
 	List	   *indpred;
 
@@ -989,17 +992,17 @@ struct IndexOptInfo
 	 * Remaining fields are copied from the index AM's API struct
 	 * (IndexAmRoutine)
 	 */
-	bool		amcanorderbyop;
-	bool		amoptionalkey;
-	bool		amsearcharray;
-	bool		amsearchnulls;
+	bool		amcanorderbyop pg_node_attr(readwrite_ignore);
+	bool		amoptionalkey pg_node_attr(readwrite_ignore);
+	bool		amsearcharray pg_node_attr(readwrite_ignore);
+	bool		amsearchnulls pg_node_attr(readwrite_ignore);
 	/* does AM have amgettuple interface? */
-	bool		amhasgettuple;
+	bool		amhasgettuple pg_node_attr(readwrite_ignore);
 	/* does AM have amgetbitmap interface? */
-	bool		amhasgetbitmap;
-	bool		amcanparallel;
+	bool		amhasgetbitmap pg_node_attr(readwrite_ignore);
+	bool		amcanparallel pg_node_attr(readwrite_ignore);
 	/* does AM have ammarkpos interface? */
-	bool		amcanmarkpos;
+	bool		amcanmarkpos pg_node_attr(readwrite_ignore);
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
 };
@@ -1027,11 +1030,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:
@@ -1068,10 +1071,10 @@ typedef struct StatisticExtInfo
 	Oid			statOid;
 
 	/* includes child relations */
-	bool		inherit;
+	bool		inherit pg_node_attr(readwrite_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(readwrite_ignore);
 
 	/* statistics kind of this entry */
 	char		kind;
@@ -1265,7 +1268,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;
@@ -1343,13 +1346,13 @@ typedef struct Path
 	NodeTag		pathtype;
 
 	/* the relation this path can build */
-	RelOptInfo *parent;
+	RelOptInfo *parent pg_node_attr(path_hack1);
 
 	/* list of Vars/Exprs, cost, width */
-	PathTarget *pathtarget;
+	PathTarget *pathtarget pg_node_attr(path_hack2);
 
 	/* parameterization info, or NULL if none */
-	ParamPathInfo *param_info;
+	ParamPathInfo *param_info pg_node_attr(path_hack3);
 
 	/* engage parallel-aware logic? */
 	bool		parallel_aware;
@@ -2224,6 +2227,12 @@ typedef struct LimitPath
  * apply only one.  We mark clauses of this kind by setting parent_ec to
  * point to the generating EquivalenceClass.  Multiple clauses with the same
  * parent_ec in the same join are redundant.
+ *
+ * Most fields are ignored for equality, since they may not be set yet, and
+ * should be derivable from the clause anyway.
+ *
+ * parent_ec, left_ec, right_ec are not printed, lest it lead to infinite
+ * recursion in plan tree dump.
  */
 
 typedef struct RestrictInfo
@@ -2240,22 +2249,22 @@ typedef struct RestrictInfo
 	bool		outerjoin_delayed;
 
 	/* see comment above */
-	bool		can_join;
+	bool		can_join pg_node_attr(equal_ignore);
 
 	/* see comment above */
-	bool		pseudoconstant;
+	bool		pseudoconstant pg_node_attr(equal_ignore);
 
 	/* true if known to contain no leaked Vars */
-	bool		leakproof;
+	bool		leakproof pg_node_attr(equal_ignore);
 
 	/* to indicate if clause contains any volatile functions. */
-	VolatileFunctionStatus has_volatile;
+	VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);
 
 	/* see comment above */
 	Index		security_level;
 
 	/* The set of relids (varnos) actually referenced in the clause: */
-	Relids		clause_relids;
+	Relids		clause_relids pg_node_attr(equal_ignore);
 
 	/* The set of relids required to evaluate the clause: */
 	Relids		required_relids;
@@ -2270,84 +2279,89 @@ typedef struct RestrictInfo
 	 * Relids in the left/right side of the clause.  These fields are set for
 	 * any binary opclause.
 	 */
-	Relids		left_relids;
-	Relids		right_relids;
+	Relids		left_relids pg_node_attr(equal_ignore);
+	Relids		right_relids pg_node_attr(equal_ignore);
 
 	/*
 	 * Modified clause with RestrictInfos.  This field is NULL unless clause
 	 * is an OR clause.
 	 */
-	Expr	   *orclause;
+	Expr	   *orclause pg_node_attr(equal_ignore);
 
 	/*
 	 * Generating EquivalenceClass.  This field is NULL unless clause is
 	 * potentially redundant.
 	 */
-	EquivalenceClass *parent_ec;
+	EquivalenceClass *parent_ec pg_node_attr(equal_ignore readwrite_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 readwrite_ignore);
 	/* EquivalenceClass containing righthand */
-	EquivalenceClass *right_ec;
+	EquivalenceClass *right_ec pg_node_attr(equal_ignore readwrite_ignore);
 	/* EquivalenceMember for lefthand */
-	EquivalenceMember *left_em;
+	EquivalenceMember *left_em pg_node_attr(equal_ignore);
 	/* EquivalenceMember for righthand */
-	EquivalenceMember *right_em;
-	/* list of MergeScanSelCache structs */
-	List	   *scansel_cache;
+	EquivalenceMember *right_em pg_node_attr(equal_ignore);
+
+	/*
+	 * List of MergeScanSelCache structs.  Those aren't Nodes, so hard to
+	 * copy.  Ignoring it will have the effect that copying will just reset
+	 * the cache.
+	 */
+	List	   *scansel_cache pg_node_attr(copy_ignore equal_ignore);
 
 	/*
 	 * transient workspace for use while considering a specific join path; T =
 	 * outer var on left, F = on right
 	 */
-	bool		outer_is_left;
+	bool		outer_is_left pg_node_attr(equal_ignore);
 
 	/*
 	 * copy of clause operator; valid if clause is hashjoinable, else
 	 * InvalidOid
 	 */
-	Oid			hashjoinoperator;
+	Oid			hashjoinoperator pg_node_attr(equal_ignore);
 
 	/*
 	 * cache space for hashclause processing; -1 if not yet set
 	 */
 	/* avg bucketsize of left side */
-	Selectivity left_bucketsize;
+	Selectivity left_bucketsize pg_node_attr(equal_ignore);
 	/* avg bucketsize of right side */
-	Selectivity right_bucketsize;
+	Selectivity right_bucketsize pg_node_attr(equal_ignore);
 	/* left side's most common val's freq */
-	Selectivity left_mcvfreq;
+	Selectivity left_mcvfreq pg_node_attr(equal_ignore);
 	/* right side's most common val's freq */
-	Selectivity right_mcvfreq;
+	Selectivity right_mcvfreq pg_node_attr(equal_ignore);
 
 	/* hash equality operators used for memoize nodes, else InvalidOid */
-	Oid			left_hasheqoperator;
-	Oid			right_hasheqoperator;
+	Oid			left_hasheqoperator pg_node_attr(equal_ignore);
+	Oid			right_hasheqoperator pg_node_attr(equal_ignore);
 } RestrictInfo;
 
 /*
@@ -2397,6 +2411,17 @@ typedef struct MergeScanSelCache
  * Although the planner treats this as an expression node type, it is not
  * recognized by the parser or executor, so we declare it here rather than
  * in primnodes.h.
+ *
+ * We intentionally do not compare phexpr.  Two PlaceHolderVars with the
+ * same ID and levelsup should be considered equal even if the contained
+ * expressions have managed to mutate to different states.  This will
+ * happen during final plan construction when there are nested PHVs, since
+ * the inner PHV will get replaced by a Param in some copies of the outer
+ * PHV.  Another way in which it can happen is that initplan sublinks
+ * could get replaced by differently-numbered Params when sublink folding
+ * is done.  (The end result of such a situation would be some
+ * unreferenced initplans, which is annoying but not really a problem.) On
+ * the same reasoning, there is no need to examine phrels.
  */
 
 typedef struct PlaceHolderVar
@@ -2404,10 +2429,10 @@ typedef struct PlaceHolderVar
 	Expr		xpr;
 
 	/* the represented expression */
-	Expr	   *phexpr;
+	Expr	   *phexpr pg_node_attr(equal_ignore);
 
 	/* base relids syntactically within expr src */
-	Relids		phrels;
+	Relids		phrels pg_node_attr(equal_ignore);
 
 	/* ID for PHV (unique within planner run) */
 	Index		phid;
@@ -2572,7 +2597,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
@@ -2643,7 +2668,7 @@ 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 */
@@ -2677,8 +2702,8 @@ 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(readwrite_ignore);
 
 	/* access path for subquery */
 	Path	   *path;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d5c0ebe859..04f01d5e8f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -286,16 +286,16 @@ typedef struct MergeAppend
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *sortColIdx;
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
 	/* OIDs of operators to sort them by */
-	Oid		   *sortOperators;
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));
 
 	/* OIDs of collations */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numCols));
 
 	/* NULLS FIRST/LAST directions */
-	bool	   *nullsFirst;
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));
 
 	/* Info for run-time subplan pruning; NULL if we're not doing that */
 	struct PartitionPruneInfo *part_prune_info;
@@ -322,11 +322,11 @@ typedef struct RecursiveUnion
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *dupColIdx;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *dupOperators;
-	Oid		   *dupCollations;
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 
 	/* estimated number of groups in input */
 	long		numGroups;
@@ -812,16 +812,16 @@ typedef struct MergeJoin
 	/* these are arrays, but have the same length as the mergeclauses list: */
 
 	/* per-clause OIDs of btree opfamilies */
-	Oid		   *mergeFamilies;
+	Oid		   *mergeFamilies pg_node_attr(array_size(mergeclauses));
 
 	/* per-clause OIDs of collations */
-	Oid		   *mergeCollations;
+	Oid		   *mergeCollations pg_node_attr(array_size(mergeclauses));
 
 	/* per-clause ordering (ASC or DESC) */
-	int		   *mergeStrategies;
+	int		   *mergeStrategies pg_node_attr(array_size(mergeclauses));
 
 	/* per-clause nulls ordering */
-	bool	   *mergeNullsFirst;
+	bool	   *mergeNullsFirst pg_node_attr(array_size(mergeclauses));
 } MergeJoin;
 
 /* ----------------
@@ -863,10 +863,10 @@ typedef struct Memoize
 	int			numKeys;
 
 	/* hash operators for each key */
-	Oid		   *hashOperators;
+	Oid		   *hashOperators pg_node_attr(array_size(numKeys));
 
 	/* collations for each key */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numKeys));
 
 	/* cache keys in the form of exprs containing parameters */
 	List	   *param_exprs;
@@ -905,16 +905,16 @@ typedef struct Sort
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *sortColIdx;
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
 	/* OIDs of operators to sort them by */
-	Oid		   *sortOperators;
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));
 
 	/* OIDs of collations */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numCols));
 
 	/* NULLS FIRST/LAST directions */
-	bool	   *nullsFirst;
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));
 } Sort;
 
 /* ----------------
@@ -941,11 +941,11 @@ typedef struct Group
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *grpColIdx;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *grpOperators;
-	Oid		   *grpCollations;
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 } Group;
 
 /* ---------------
@@ -976,11 +976,11 @@ typedef struct Agg
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *grpColIdx;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *grpOperators;
-	Oid		   *grpCollations;
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 
 	/* estimated number of groups in input */
 	long		numGroups;
@@ -1015,25 +1015,25 @@ typedef struct WindowAgg
 	int			partNumCols;
 
 	/* their indexes in the target list */
-	AttrNumber *partColIdx;
+	AttrNumber *partColIdx pg_node_attr(array_size(partNumCols));
 
 	/* equality operators for partition columns */
-	Oid		   *partOperators;
+	Oid		   *partOperators pg_node_attr(array_size(partNumCols));
 
 	/* collations for partition columns */
-	Oid		   *partCollations;
+	Oid		   *partCollations pg_node_attr(array_size(partNumCols));
 
 	/* number of columns in ordering clause */
 	int			ordNumCols;
 
 	/* their indexes in the target list */
-	AttrNumber *ordColIdx;
+	AttrNumber *ordColIdx pg_node_attr(array_size(ordNumCols));
 
 	/* equality operators for ordering columns */
-	Oid		   *ordOperators;
+	Oid		   *ordOperators pg_node_attr(array_size(ordNumCols));
 
 	/* collations for ordering columns */
-	Oid		   *ordCollations;
+	Oid		   *ordCollations pg_node_attr(array_size(ordNumCols));
 
 	/* frame_clause options, see WindowDef */
 	int			frameOptions;
@@ -1086,13 +1086,13 @@ typedef struct Unique
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *uniqColIdx;
+	AttrNumber *uniqColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *uniqOperators;
+	Oid		   *uniqOperators pg_node_attr(array_size(numCols));
 
 	/* collations for equality comparisons */
-	Oid		   *uniqCollations;
+	Oid		   *uniqCollations pg_node_attr(array_size(numCols));
 } Unique;
 
 /* ------------
@@ -1137,16 +1137,16 @@ typedef struct GatherMerge
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *sortColIdx;
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
 	/* OIDs of operators to sort them by */
-	Oid		   *sortOperators;
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));
 
 	/* OIDs of collations */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numCols));
 
 	/* NULLS FIRST/LAST directions */
-	bool	   *nullsFirst;
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));
 
 	/*
 	 * param id's of initplans which are referred at gather merge or one of
@@ -1197,11 +1197,11 @@ typedef struct SetOp
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *dupColIdx;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *dupOperators;
-	Oid		   *dupCollations;
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 
 	/* where is the flag column, if any */
 	AttrNumber	flagColIdx;
@@ -1253,13 +1253,13 @@ typedef struct Limit
 	int			uniqNumCols;
 
 	/* their indexes in the target list */
-	AttrNumber *uniqColIdx;
+	AttrNumber *uniqColIdx pg_node_attr(array_size(uniqNumCols));
 
 	/* equality operators to compare with */
-	Oid		   *uniqOperators;
+	Oid		   *uniqOperators pg_node_attr(array_size(uniqNumCols));
 
 	/* collations for equality comparisons */
-	Oid		   *uniqCollations;
+	Oid		   *uniqCollations pg_node_attr(array_size(uniqNumCols));
 } Limit;
 
 
@@ -1425,13 +1425,13 @@ typedef struct PartitionedRelPruneInfo
 	int			nparts;
 
 	/* subplan index by partition index, or -1 */
-	int		   *subplan_map;
+	int		   *subplan_map pg_node_attr(array_size(nparts));
 
 	/* subpart index by partition index, or -1 */
-	int		   *subpart_map;
+	int		   *subpart_map pg_node_attr(array_size(nparts));
 
 	/* relation OID by partition index, or 0 */
-	Oid		   *relid_map;
+	Oid		   *relid_map pg_node_attr(array_size(nparts));
 
 	/*
 	 * initial_pruning_steps shows how to prune during executor startup (i.e.,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 732c00c098..f5d756d49f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -64,8 +64,9 @@ 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(readwrite_ignore);
 
 	/* the schema name, or NULL */
 	char	   *schemaname;
@@ -233,10 +234,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;
@@ -374,8 +380,8 @@ 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 +461,10 @@ typedef struct GroupingFunc
 	List	   *args;
 
 	/* ressortgrouprefs of arguments */
-	List	   *refs;
+	List	   *refs pg_node_attr(equal_ignore);
 
 	/* actual column positions set by planner */
-	List	   *cols;
+	List	   *cols pg_node_attr(equal_ignore);
 
 	/* same as Aggref.agglevelsup */
 	Index		agglevelsup;
@@ -634,7 +640,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 +704,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 +717,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;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 1896a9a06d..ece4ace51d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -273,9 +273,9 @@ typedef struct ForeignKeyCacheInfo
 	Oid			confrelid;		/* relation referenced by the foreign key */
 	int			nkeys;			/* number of columns in the foreign key */
 	/* these arrays each have nkeys valid entries: */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys)); /* cols in referencing table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* cols in referenced table */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));	/* PK = FK operator OIDs */
 } ForeignKeyCacheInfo;
 
 
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index d30e8fcb11..286b5810c9 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -841,6 +841,52 @@ EOF
 		close($chs);
 	}
 
+	if (IsNewer('src/backend/nodes/node-support-stamp',
+		'src/backend/nodes/gen_node_support.pl'))
+	{
+		# XXX duplicates src/backend/nodes/Makefile
+
+		my @node_headers = qw(
+			nodes/nodes.h
+			nodes/execnodes.h
+			nodes/plannodes.h
+			nodes/primnodes.h
+			nodes/pathnodes.h
+			nodes/extensible.h
+			nodes/parsenodes.h
+			nodes/replnodes.h
+			nodes/value.h
+			commands/trigger.h
+			commands/event_trigger.h
+			foreign/fdwapi.h
+			access/amapi.h
+			access/tableam.h
+			access/tsmapi.h
+			utils/rel.h
+			nodes/supportnodes.h
+			executor/tuptable.h
+			nodes/lockoptions.h
+			access/sdir.h
+		);
+
+		chdir('src/backend/nodes');
+
+		my @node_files = map { "../../../src/include/$_" } @node_headers;
+
+		system("perl gen_node_support.pl @node_files");
+		open(my $f, '>', 'node-support-stamp') || confess "Could not touch node-support-stamp";
+		close($f);
+		chdir('../../..');
+	}
+
+	if (IsNewer(
+			'src/include/nodes/nodetags.h',
+			'src/backend/nodes/nodetags.h'))
+	{
+		copyFile('src/backend/nodes/nodetags.h',
+			'src/include/nodes/nodetags.h');
+	}
+
 	open(my $o, '>', "doc/src/sgml/version.sgml")
 	  || croak "Could not write to version.sgml\n";
 	print $o <<EOF;

base-commit: 55f4802785f66a584c05dca40e5d9b25491674b2
-- 
2.36.1

#40Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#39)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

[ v6-0001-Automatically-generate-node-support-functions.patch ]

I've now spent some time looking at this fairly carefully, and I think
this is a direction we can pursue, but I'm not yet happy about the
amount of magic knowledge that's embedded in the gen_node_support.pl
script rather than being encoded in pg_node_attr markers. Once this
is in place, people will stop thinking about the nodes/*funcs.c
infrastructure altogether when they write patches, at least until
they get badly burned by it; so I don't want there to be big gotchas.
As an example, heaven help the future hacker who decides to change
the contents of A_Const and doesn't realize that that still has a
manually-implemented copyfuncs.c routine. So rather than embedding
knowledge in gen_node_support.pl like this:

my @custom_copy = qw(A_Const Const ExtensibleNode);

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

typedef struct A_Const pg_node_attr(custom_copy)
{ ...

I will grant that there are some things that are okay to embed
in gen_node_support.pl, such as the list of @scalar_types,
because if you need to add an entry there you will find it out
when the script complains it doesn't know how to process a field.
So there is some judgment involved here, but on the whole I want
to err on the side of exposing decisions in the headers.

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

abstract_types
no_copy
no_read_write
no_read
custom_copy
custom_readwrite

(The markings that "we are not publishing right now to stay level with the
manual system" are fine to apply in the script, since that's probably a
temporary thing anyway. Also, I don't have a problem with applying
no_copy etc to the contents of whole files in the script, rather than
tediously labeling each struct in such files.)

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

I wonder whether this:

# We do not support copying Path trees, mainly
# because the circular linkages between RelOptInfo
# and Path nodes can't be handled easily in a
# simple depth-first traversal.

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

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

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

In the department of nitpicks:

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

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

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

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

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

I don't know if you have time right now to push forward with these
points, but if you don't I can take a stab at it. I would like to
see this done and committed PDQ, because 835d476fd already broke
many patches that touch *nodes.h and I'd like to get the rest of
the fallout in place before rebasing affected patches.

regards, tom lane

#41Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#40)
Re: automatically generating node support functions

... BTW, I thought of a consideration that we probably need some
answer for. As far as I can see, the patch assigns NodeTag values
sequentially in the order it sees the struct declarations in the
input files; an order that doesn't have a lot to do with our past
practice. The problem with that is that it's next door to impossible
to control the tag value assigned to any one struct. During normal
development that's not a big deal, but what if we need to add a
node struct in a released branch? As nodes.h observes already,

* Note that inserting or deleting node types changes the numbers of other
* node types later in the list. This is no problem during development, since
* the node numbers are never stored on disk. But don't do it in a released
* branch, because that would represent an ABI break for extensions.

We used to have the option of sticking new nodetags at the end of
the list in this situation, but we won't anymore.

It might be enough to invent a struct-level attribute allowing
manual assignment of node tags, ie

typedef struct MyNewNode pg_node_attr(nodetag=466)

where it'd be the programmer's responsibility to pick a nonconflicting
tag number. We'd only ever use that in ABI-frozen branches, so
manual assignment of the tag value should be workable.

Anyway, this isn't something we have to have before committing,
but I think we're going to need it at some point.

regards, tom lane

#42Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#40)
1 attachment(s)
Re: automatically generating node support functions

The new patch addresses almost all of these issues.

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

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

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

fixed

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

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

typedef struct A_Const pg_node_attr(custom_copy)
{ ...

done

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

abstract_types
no_copy
no_read_write
no_read
custom_copy
custom_readwrite

done (no_copy is actually no_copy_equal, hence renamed)

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

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

I wonder whether this:

# We do not support copying Path trees, mainly
# because the circular linkages between RelOptInfo
# and Path nodes can't be handled easily in a
# simple depth-first traversal.

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

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

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

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

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

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

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

fixed

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

fixed

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

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

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

fixed -- I think, could use more testing

Attachments:

v7-0001-Automatically-generate-node-support-functions.patchtext/plain; charset=UTF-8; name=v7-0001-Automatically-generate-node-support-functions.patchDownload
From c82ee081a7a8cdc77b44f325d1df695b55a60b06 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 6 Jul 2022 12:13:32 +0200
Subject: [PATCH v7] Automatically generate node support functions

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

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

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

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

Subtyping (TidScan -> Scan) is supported.

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

Discussion: https://www.postgresql.org/message-id/flat/c1097590-a6a4-486a-64b1-e1f9cc0533ce%40enterprisedb.com
---
 src/backend/Makefile                  |  10 +-
 src/backend/nodes/.gitignore          |   4 +
 src/backend/nodes/Makefile            |  59 ++
 src/backend/nodes/copyfuncs.c         |  19 +-
 src/backend/nodes/equalfuncs.c        |  22 +-
 src/backend/nodes/gen_node_support.pl | 770 ++++++++++++++++++++++++++
 src/backend/nodes/outfuncs.c          |  34 +-
 src/backend/nodes/readfuncs.c         |  23 +-
 src/include/Makefile                  |   1 +
 src/include/executor/tuptable.h       |   8 +-
 src/include/nodes/.gitignore          |   2 +
 src/include/nodes/extensible.h        |   2 +-
 src/include/nodes/nodes.h             |  52 ++
 src/include/nodes/parsenodes.h        |  19 +-
 src/include/nodes/pathnodes.h         | 315 +++++++----
 src/include/nodes/plannodes.h         |  92 +--
 src/include/nodes/primnodes.h         |  45 +-
 src/include/nodes/value.h             |  10 +-
 src/include/utils/rel.h               |  11 +-
 src/tools/msvc/Solution.pm            |  46 ++
 20 files changed, 1320 insertions(+), 224 deletions(-)
 create mode 100644 src/backend/nodes/.gitignore
 create mode 100644 src/backend/nodes/gen_node_support.pl
 create mode 100644 src/include/nodes/.gitignore

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a02006788..953c80db5a 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -143,11 +143,15 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
 submake-catalog-headers:
 	$(MAKE) -C catalog distprep generated-header-symlinks
 
+# run this unconditionally to avoid needing to know its dependencies here:
+submake-nodes-headers:
+	$(MAKE) -C nodes distprep generated-header-symlinks
+
 # run this unconditionally to avoid needing to know its dependencies here:
 submake-utils-headers:
 	$(MAKE) -C utils distprep generated-header-symlinks
 
-.PHONY: submake-catalog-headers submake-utils-headers
+.PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 # Make symlinks for these headers in the include directory. That way
 # we can cut down on the -I options. Also, a symlink is automatically
@@ -162,7 +166,7 @@ submake-utils-headers:
 
 .PHONY: generated-headers
 
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
@@ -185,6 +189,7 @@ distprep:
 	$(MAKE) -C parser	gram.c gram.h scan.c
 	$(MAKE) -C bootstrap	bootparse.c bootscanner.c
 	$(MAKE) -C catalog	distprep
+	$(MAKE) -C nodes	distprep
 	$(MAKE) -C replication	repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
 	$(MAKE) -C storage/lmgr	lwlocknames.h lwlocknames.c
 	$(MAKE) -C utils	distprep
@@ -297,6 +302,7 @@ distclean: clean
 
 maintainer-clean: distclean
 	$(MAKE) -C catalog $@
+	$(MAKE) -C nodes $@
 	$(MAKE) -C utils $@
 	rm -f bootstrap/bootparse.c \
 	      bootstrap/bootscanner.c \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..0c14b5697b
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,4 @@
+/node-support-stamp
+/nodetags.h
+/*funcs.funcs.c
+/*funcs.switch.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..1a0d5b9314 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,62 @@ OBJS = \
 	value.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+node_headers = \
+	nodes/nodes.h \
+	nodes/execnodes.h \
+	nodes/plannodes.h \
+	nodes/primnodes.h \
+	nodes/pathnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.h \
+	nodes/value.h \
+	commands/trigger.h \
+	commands/event_trigger.h \
+	foreign/fdwapi.h \
+	access/amapi.h \
+	access/tableam.h \
+	access/tsmapi.h \
+	utils/rel.h \
+	nodes/supportnodes.h \
+	executor/tuptable.h \
+	nodes/lockoptions.h \
+	access/sdir.h
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-support-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+# node-support-stamp records the last time we ran gen_node_support.pl.
+# We don't rely on the timestamps of the individual output files,
+# because the Perl script won't update them if they didn't change (to
+# avoid unnecessary recompiles).
+node-support-stamp: gen_node_support.pl $(addprefix $(top_srcdir)/src/include/,$(node_headers))
+	$(PERL) $^
+	touch $@
+
+# These generated headers must be symlinked into builddir/src/include/,
+# using absolute links for the reasons explained in src/backend/Makefile.
+# We use header-stamp to record that we've done this because the symlinks
+# themselves may appear older than node-support-stamp.
+$(top_builddir)/src/include/nodes/header-stamp: node-support-stamp
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	cd '$(dir $@)' && for file in nodetags.h; do \
+	  rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \
+	done
+	touch $@
+
+copyfuncs.o: copyfuncs.c copyfuncs.funcs.c copyfuncs.switch.c | node-support-stamp
+equalfuncs.o: equalfuncs.c equalfuncs.funcs.c equalfuncs.switch.c | node-support-stamp
+outfuncs.o: outfuncs.c outfuncs.funcs.c outfuncs.switch.c | node-support-stamp
+readfuncs.o:  readfuncs.c readfuncs.funcs.c readfuncs.switch.c | node-support-stamp
+
+maintainer-clean: clean
+	rm -f node-support-stamp $(addsuffix funcs.funcs.c,copy equal out read) $(addsuffix funcs.switch.c,copy equal out read) nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 706d283a92..48778aa4ef 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -23,11 +23,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
-#include "nodes/plannodes.h"
 #include "utils/datum.h"
-#include "utils/rel.h"
 
 
 /*
@@ -73,6 +69,9 @@
 	(newnode->fldname = from->fldname)
 
 
+#include "copyfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /* ****************************************************************
  *					 plannodes.h copy functions
  * ****************************************************************
@@ -1465,6 +1464,7 @@ _copyVar(const Var *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * _copyConst
@@ -1504,6 +1504,7 @@ _copyConst(const Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -3248,6 +3249,7 @@ _copyParamRef(const ParamRef *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -3288,6 +3290,7 @@ _copyA_Const(const A_Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -5453,6 +5456,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *					extensible.h copy functions
@@ -5475,6 +5479,7 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /* ****************************************************************
  *					value.h copy functions
  * ****************************************************************
@@ -5545,6 +5550,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5565,6 +5571,8 @@ copyObjectImpl(const void *from)
 
 	switch (nodeTag(from))
 	{
+#include "copyfuncs.switch.c"
+#ifdef OBSOLETE
 			/*
 			 * PLAN NODES
 			 */
@@ -6009,6 +6017,7 @@ copyObjectImpl(const void *from)
 		case T_BitString:
 			retval = _copyBitString(from);
 			break;
+#endif /*OBSOLETE*/
 
 			/*
 			 * LIST NODES
@@ -6026,6 +6035,7 @@ copyObjectImpl(const void *from)
 			retval = list_copy(from);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -6577,6 +6587,7 @@ copyObjectImpl(const void *from)
 		case T_ForeignKeyCacheInfo:
 			retval = _copyForeignKeyCacheInfo(from);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index fccc0b4a18..7f09ccd978 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -10,9 +10,6 @@
  * because the circular linkages between RelOptInfo and Path nodes can't
  * be handled easily in a simple depth-first traversal.
  *
- * Currently, in fact, equal() doesn't know how to compare Plan trees
- * either.  This might need to be fixed someday.
- *
  * NOTE: it is intentional that parse location fields (in nodes that have
  * one) are not compared.  This is because we want, for example, a variable
  * "x" to be considered equal() to another reference to "x" in the query.
@@ -30,8 +27,6 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
 #include "utils/datum.h"
 
 
@@ -97,6 +92,9 @@
 	((void) 0)
 
 
+#include "equalfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from primnodes.h
  */
@@ -258,6 +256,7 @@ _equalVar(const Var *a, const Var *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -280,6 +279,7 @@ _equalConst(const Const *a, const Const *b)
 						a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -1304,6 +1304,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -1325,6 +1326,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const ExtensibleNode *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2815,6 +2817,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2831,6 +2834,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3468,6 +3472,7 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3528,6 +3533,7 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from value.h
  */
@@ -3571,6 +3577,7 @@ _equalBitString(const BitString *a, const BitString *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * equal
@@ -3601,6 +3608,8 @@ equal(const void *a, const void *b)
 
 	switch (nodeTag(a))
 	{
+#include "equalfuncs.switch.c"
+#ifdef OBSOLETE
 			/*
 			 * PRIMITIVE NODES
 			 */
@@ -3821,6 +3830,7 @@ equal(const void *a, const void *b)
 		case T_PlaceHolderInfo:
 			retval = _equalPlaceHolderInfo(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		case T_List:
 		case T_IntList:
@@ -3828,6 +3838,7 @@ equal(const void *a, const void *b)
 			retval = _equalList(a, b);
 			break;
 
+#ifdef OBSOLETE
 		case T_Integer:
 			retval = _equalInteger(a, b);
 			break;
@@ -4430,6 +4441,7 @@ equal(const void *a, const void *b)
 		case T_JsonTableColumn:
 			retval = _equalJsonTableColumn(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
new file mode 100644
index 0000000000..6aaf401a72
--- /dev/null
+++ b/src/backend/nodes/gen_node_support.pl
@@ -0,0 +1,770 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/backend/nodes/gen_node_support.pl
+#
+#----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use File::Basename;
+
+use FindBin;
+use lib "$FindBin::RealBin/../catalog";
+
+use Catalog;  # for RenameTempFile
+
+
+# Test whether first argument is element of the list in the second
+# argument
+sub elem
+{
+	my $x = shift;
+	return grep { $_ eq $x } @_;
+}
+
+
+# collect node names
+my @node_types = qw(Node);
+# collect info for each node type
+my %node_type_info;
+
+# node types we don't want copy/equal support for
+my @no_copy_equal;
+# node types we don't want read support for
+my @no_read;
+# node types we don't want read/write support for
+my @no_read_write;
+
+# types that are copied by straight assignment
+my @scalar_types = qw(
+	bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+	AclMode AttrNumber Cardinality Cost Index Oid Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr
+);
+
+# collect enum types
+my @enum_types;
+
+my @abstract_types = qw(Node);
+
+# Special cases that either don't have their own struct or the struct
+# is not in a header file.  We just generate node tags for them, but
+# they otherwise don't participate in node support.
+my @extra_tags = qw(
+	IntList OidList XidList
+	AllocSetContext GenerationContext SlabContext
+	TIDBitmap
+	WindowObjectData
+);
+
+# This is a regular node, but we skip parsing it from its header file
+# since we won't use its internal structure here anyway.
+push @node_types, qw(List);
+# See special treatment in outNode() and nodeRead().
+push @no_read_write, qw(List);
+
+# Nodes with custom copy/equal implementations are skipped from
+# .funcs.c but need case statements in .switch.c.
+my @custom_copy_equal;
+
+# Similarly for custom read/write implementations.
+my @custom_read_write;
+
+# EquivalenceClasses are never moved, so just shallow-copy the pointer
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember*);
+
+# This is a struct, so we can copy it by assignment.  Equal support is
+# currently not required.
+push @scalar_types, qw(QualCost);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy_equal, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation);
+
+
+## read input
+
+foreach my $infile (@ARGV)
+{
+	my $in_struct;
+	my $subline;
+	my $is_node_struct;
+	my $supertype;
+	my $supertype_field;
+
+	my @my_fields;
+	my %my_field_types;
+	my %my_field_attrs;
+
+	open my $ifh, '<', $infile or die "could not open \"$infile\": $!";
+
+	my $file_content = do { local $/; <$ifh> };
+
+	# strip C comments
+	$file_content =~ s{/\*.*?\*/}{}gs;
+
+	foreach my $line (split /\n/, $file_content)
+	{
+		chomp $line;
+		$line =~ s/\s*$//;
+		next if $line eq '';
+		next if $line =~ /^#(define|ifdef|endif)/;
+
+		# we are analyzing a struct definition
+		if ($in_struct)
+		{
+			$subline++;
+
+			# first line should have opening brace
+			if ($subline == 1)
+			{
+				$is_node_struct = 0;
+				$supertype = undef;
+				next if $line eq '{';
+				die;
+			}
+			# second line should have node tag or supertype
+			elsif ($subline == 2)
+			{
+				if ($line =~ /^\s*NodeTag\s+type;/)
+				{
+					$is_node_struct = 1;
+					next;
+				}
+				elsif ($line =~ /\s*(\w+)\s+(\w+);/ and elem $1, @node_types)
+				{
+					$is_node_struct = 1;
+					$supertype = $1;
+					$supertype_field = $2;
+					next;
+				}
+			}
+
+			# end of struct
+			if ($line =~ /^\}\s*$in_struct;$/ || $line =~ /^\};$/)
+			{
+				if ($is_node_struct)
+				{
+					# This is the end of a node struct definition.
+					# Save everything we have collected.
+
+					# node name
+					push @node_types, $in_struct;
+
+					# field names, types, attributes
+					my @f = @my_fields;
+					my %ft = %my_field_types;
+					my %fa = %my_field_attrs;
+
+					# If there is a supertype, add those fields, too.
+					if ($supertype)
+					{
+						my @superfields;
+						foreach my $sf (@{$node_type_info{$supertype}->{fields}})
+						{
+							my $fn = "${supertype_field}.$sf";
+							push @superfields, $fn;
+							$ft{$fn} = $node_type_info{$supertype}->{field_types}{$sf};
+							if ($node_type_info{$supertype}->{field_attrs}{$sf})
+							{
+								# Copy any attributes, adjusting array_size field references
+								my @newa = @{$node_type_info{$supertype}->{field_attrs}{$sf}};
+								foreach my $a (@newa)
+								{
+									$a =~ s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/;
+								}
+								$fa{$fn} = \@newa;
+							}
+						}
+						unshift @f, @superfields;
+					}
+					# save in global info structure
+					$node_type_info{$in_struct}->{fields} = \@f;
+					$node_type_info{$in_struct}->{field_types} = \%ft;
+					$node_type_info{$in_struct}->{field_attrs} = \%fa;
+
+					# Nodes from these files don't need to be
+					# supported, except the node tags.
+					if (elem basename($infile),
+						qw(execnodes.h trigger.h event_trigger.h amapi.h tableam.h
+							tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h))
+					{
+						push @no_copy_equal, $in_struct;
+						push @no_read_write, $in_struct;
+					}
+
+					# Propagate some node attributes from supertypes
+					if ($supertype)
+					{
+						push @no_copy_equal, $in_struct if elem $supertype, @no_copy_equal;
+						push @no_read, $in_struct if elem $supertype, @no_read;
+					}
+				}
+
+				# start new cycle
+				$in_struct = undef;
+				@my_fields = ();
+				%my_field_types = ();
+				%my_field_attrs = ();
+			}
+			# normal struct field
+			elsif ($line =~ /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w(), ]*)\))?;/)
+			{
+				if ($is_node_struct)
+				{
+					my $type = $1;
+					my $name = $2;
+					my $array_size = $3;
+					my $attrs = $4;
+
+					# strip "const"
+					$type =~ s/^const\s*//;
+					# strip trailing space
+					$type =~ s/\s*$//;
+					# strip space between type and "*" (pointer) */
+					$type =~ s/\s+\*$/*/;
+
+					die if $type eq '';
+
+					my @attrs;
+					if ($attrs)
+					{
+						@attrs = split /,\s*/, $attrs;
+						foreach my $attr (@attrs)
+						{
+							if ($attr !~ /^array_size\(\w+\)$/ &&
+								!elem $attr, qw(copy_ignore equal_ignore equal_ignore_if_zero read_write_ignore
+									write_only_relids write_only_nondefault_pathtarget write_only_req_outer))
+							{
+								die "$infile:$.: unrecognized attribute \"$attr\"\n";
+							}
+						}
+					}
+
+					$type = $type . $array_size if $array_size;
+					push @my_fields, $name;
+					$my_field_types{$name} = $type;
+					$my_field_attrs{$name} = \@attrs;
+				}
+			}
+			else
+			{
+				if ($is_node_struct)
+				{
+					#warn "$infile:$.: could not parse \"$line\"\n";
+				}
+			}
+		}
+		# not in a struct
+		else
+		{
+			# start of a struct?
+			if ($line =~ /^(?:typedef )?struct (\w+)\s*(?:pg_node_attr\(([\w(), ]*)\))?$/ && $1 ne 'Node')
+			{
+				$in_struct = $1;
+				my $node_attrs = $2 || '';
+				$subline = 0;
+
+				foreach my $attr (split /,\s*/, $node_attrs)
+				{
+					if ($attr eq 'abstract')
+					{
+						push @abstract_types, $in_struct;
+					}
+					elsif ($attr eq 'custom_copy_equal')
+					{
+						push @custom_copy_equal, $in_struct;
+					}
+					elsif ($attr eq 'custom_read_write')
+					{
+						push @custom_read_write, $in_struct;
+					}
+					elsif ($attr eq 'no_copy_equal')
+					{
+						push @no_copy_equal, $in_struct;
+					}
+					elsif ($attr eq 'no_read')
+					{
+						push @no_read, $in_struct;
+					}
+					elsif ($attr eq 'special_read_write')
+					{
+						# This attribute is called
+						# "special_read_write" because there is
+						# special treatment in outNode() and
+						# nodeRead() for these nodes.  For this
+						# script, it's the same as "no_read_write",
+						# but calling the attribute that externally
+						# would probably be confusing, since
+						# read/write support does in fact exist.
+						push @no_read_write, $in_struct;
+					}
+					else
+					{
+						die "$infile:$.: unrecognized attribute \"$attr\"\n";
+					}
+				}
+			}
+			# one node type typedef'ed directly from another
+			elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types)
+			{
+				my $alias_of = $1;
+				my $n = $2;
+
+				# copy everything over
+				push @node_types, $n;
+				my @f = @{$node_type_info{$alias_of}->{fields}};
+				my %ft = %{$node_type_info{$alias_of}->{field_types}};
+				my %fa = %{$node_type_info{$alias_of}->{field_attrs}};
+				$node_type_info{$n}->{fields} = \@f;
+				$node_type_info{$n}->{field_types} = \%ft;
+				$node_type_info{$n}->{field_attrs} = \%fa;
+			}
+			# collect enum names
+			elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/)
+			{
+				push @enum_types, $1;
+			}
+		}
+	}
+
+	if ($in_struct)
+	{
+		die "runaway \"$in_struct\" in file \"$infile\"\n";
+	}
+
+	close $ifh;
+} # for each file
+
+
+## write output
+
+my $tmpext  = ".tmp$$";
+
+# nodetags.h
+
+open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
+
+my $i = 1;
+foreach my $n (@node_types,@extra_tags)
+{
+	next if elem $n, @abstract_types;
+	print $nt "\tT_${n} = $i,\n";
+	$i++;
+}
+
+close $nt;
+
+
+# make #include lines necessary to pull in all the struct definitions
+my $node_includes = '';
+foreach my $infile (sort @ARGV)
+{
+	$infile =~ s!.*src/include/!!;
+	$node_includes .= qq{#include "$infile"\n};
+}
+
+
+# copyfuncs.c, equalfuncs.c
+
+open my $cff, '>', 'copyfuncs.funcs.c' . $tmpext or die $!;
+open my $eff, '>', 'equalfuncs.funcs.c' . $tmpext or die $!;
+open my $cfs, '>', 'copyfuncs.switch.c' . $tmpext or die $!;
+open my $efs, '>', 'equalfuncs.switch.c' . $tmpext or die $!;
+
+# add required #include lines to each file set
+print $cff $node_includes;
+print $eff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_copy_equal;
+	next if $n eq 'List';
+
+	print $cfs "\t\tcase T_${n}:\n".
+	  "\t\t\tretval = _copy${n}(from);\n".
+	  "\t\t\tbreak;\n";
+
+	print $efs "\t\tcase T_${n}:\n".
+	  "\t\t\tretval = _equal${n}(a, b);\n".
+	  "\t\t\tbreak;\n";
+
+	next if elem $n, @custom_copy_equal;
+
+	print $cff "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+";
+
+	print $eff "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+";
+
+	# print instructions for each field
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
+		my $copy_ignore = (elem 'copy_ignore', @a);
+		my $equal_ignore = (elem 'equal_ignore', @a);
+
+		# select instructions by field type
+		if ($t eq 'char*')
+		{
+			print $cff "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_STRING_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $cff "\tCOPY_BITMAPSET_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_BITMAPSET_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $cff "\tCOPY_LOCATION_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_LOCATION_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif (elem $t, @scalar_types or elem $t, @enum_types)
+		{
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			if (elem 'equal_ignore_if_zero', @a)
+			{
+				print $eff "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
+			}
+			else
+			{
+				print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+			}
+		}
+		# scalar type pointer
+		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
+		{
+			my $tt = $1;
+			my $array_size_field;
+			foreach my $a (@a)
+			{
+				if ($a =~ /^array_size.([\w.]+)/)
+				{
+					$array_size_field = $1;
+					last;
+				}
+			}
+			if (!$array_size_field)
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $cff "\tCOPY_POINTER_FIELD($f, list_length(from->$array_size_field) * sizeof($tt));\n" unless $copy_ignore;
+				print $eff "\tCOMPARE_POINTER_FIELD($f, list_length(a->$array_size_field) * sizeof($tt));\n" unless $equal_ignore;
+			}
+			else
+			{
+				print $cff "\tCOPY_POINTER_FIELD($f, from->$array_size_field * sizeof($tt));\n" unless $copy_ignore;
+				print $eff "\tCOMPARE_POINTER_FIELD($f, a->$array_size_field * sizeof($tt));\n" unless $equal_ignore;
+			}
+		}
+		# node type
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $cff "\tCOPY_NODE_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
+		}
+		# array (inline)
+		elsif ($t =~ /\w+\[/)
+		{
+			print $cff "\tCOPY_ARRAY_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_ARRAY_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			# Fields of these types are required to be a pointer to a
+			# static table of callback functions.  So we don't copy
+			# the table itself, just reference the original one.
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $cff "
+\treturn newnode;
+}
+";
+	print $eff "
+\treturn true;
+}
+";
+}
+
+close $cff;
+close $eff;
+close $cfs;
+close $efs;
+
+
+# outfuncs.c, readfuncs.c
+
+open my $off, '>', 'outfuncs.funcs.c' . $tmpext or die $!;
+open my $rff, '>', 'readfuncs.funcs.c' . $tmpext or die $!;
+open my $ofs, '>', 'outfuncs.switch.c' . $tmpext or die $!;
+open my $rfs, '>', 'readfuncs.switch.c' . $tmpext or die $!;
+
+print $off $node_includes;
+print $rff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_read_write;
+
+	# XXX For now, skip all "Stmt"s except that ones that were there before.
+	if ($n =~ /Stmt$/)
+	{
+		my @keep = qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt ImportForeignSchemaStmt IndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt);
+		next unless elem $n, @keep;
+	}
+
+	my $no_read = (elem $n, @no_read);
+
+	# output format starts with upper case node type, underscores stripped
+	my $N = uc $n;
+	$N =~ s/_//g;
+
+	print $ofs "\t\t\tcase T_${n}:\n".
+	  "\t\t\t\t_out${n}(str, obj);\n".
+	  "\t\t\t\tbreak;\n";
+
+	print $rfs "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+
+	next if elem $n, @custom_read_write;
+
+	print $off "
+static void
+_out${n}(StringInfo str, const $n *node)
+{
+\tWRITE_NODE_TYPE(\"$N\");
+
+";
+
+	print $rff "
+static $n *
+_read${n}(void)
+{
+\tREAD_LOCALS($n);
+
+" unless $no_read;
+
+	# print instructions for each field
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
+		next if (elem 'read_write_ignore', @a);
+
+		# XXX Previously, for subtyping, only the leaf field name is
+		# used. Ponder whether we want to keep it that way.
+
+		# select instructions by field type
+		if ($t eq 'bool')
+		{
+			print $off "\tWRITE_BOOL_FIELD($f);\n";
+			print $rff "\tREAD_BOOL_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $off "\tWRITE_LOCATION_FIELD($f);\n";
+			print $rff "\tREAD_LOCATION_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' || $t eq 'int32' || $t eq 'AttrNumber' || $t eq 'StrategyNumber')
+		{
+			print $off "\tWRITE_INT_FIELD($f);\n";
+			print $rff "\tREAD_INT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint32' || $t eq 'bits32' || $t eq 'AclMode' || $t eq 'BlockNumber' || $t eq 'Index' || $t eq 'SubTransactionId')
+		{
+			print $off "\tWRITE_UINT_FIELD($f);\n";
+			print $rff "\tREAD_UINT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint64')
+		{
+			print $off "\tWRITE_UINT64_FIELD($f);\n";
+			print $rff "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Oid')
+		{
+			print $off "\tWRITE_OID_FIELD($f);\n";
+			print $rff "\tREAD_OID_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'long')
+		{
+			print $off "\tWRITE_LONG_FIELD($f);\n";
+			print $rff "\tREAD_LONG_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char')
+		{
+			print $off "\tWRITE_CHAR_FIELD($f);\n";
+			print $rff "\tREAD_CHAR_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'double')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.6f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cardinality')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.0f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cost')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.2f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'QualCost')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f.startup, \"%.2f\");\n";
+			print $off "\tWRITE_FLOAT_FIELD($f.per_tuple, \"%.2f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read;
+			print $rff "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
+		}
+		elsif ($t eq 'Selectivity')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.4f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $off "\tWRITE_STRING_FIELD($f);\n";
+			print $rff "\tREAD_STRING_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $off "\tWRITE_BITMAPSET_FIELD($f);\n";
+			print $rff "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
+		}
+		elsif (elem $t, @enum_types)
+		{
+			print $off "\tWRITE_ENUM_FIELD($f, $t);\n";
+			print $rff "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read;
+		}
+		# arrays
+		elsif ($t =~ /(\w+)(\*|\[)/ and elem $1, @scalar_types)
+		{
+			my $tt = uc $1;
+			my $array_size_field;
+			foreach my $a (@a)
+			{
+				if ($a =~ /^array_size.([\w.]+)/)
+				{
+					$array_size_field = $1;
+					last;
+				}
+			}
+			if (!$array_size_field)
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $off "\tWRITE_${tt}_ARRAY($f, list_length(node->$array_size_field));\n";
+				print $rff "\tREAD_${tt}_ARRAY($f, list_length(local_node->$array_size_field));\n" unless $no_read;
+			}
+			else
+			{
+				print $off "\tWRITE_${tt}_ARRAY($f, node->$array_size_field);\n";
+				print $rff "\tREAD_${tt}_ARRAY($f, local_node->$array_size_field);\n" unless $no_read;
+			}
+		}
+		# Special treatments of several Path node fields
+		elsif ($t eq 'RelOptInfo*' && elem 'write_only_relids', @a)
+		{
+			print $off "\tappendStringInfoString(str, \" :parent_relids \");\n".
+			  "\toutBitmapset(str, node->$f->relids);\n";
+		}
+		elsif ($t eq 'PathTarget*' && elem 'write_only_nondefault_pathtarget', @a)
+		{
+			(my $f2 = $f) =~ s/pathtarget/parent/;
+			print $off "\tif (node->$f != node->$f2->reltarget)\n".
+			  "\t\tWRITE_NODE_FIELD($f);\n";
+		}
+		elsif ($t eq 'ParamPathInfo*' && elem 'write_only_req_outer', @a)
+		{
+			print $off "\tif (node->$f)\n".
+			  "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
+			  "\telse\n".
+			  "\t\toutBitmapset(str, NULL);\n";
+		}
+		# node type
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $off "\tWRITE_NODE_FIELD($f);\n";
+			print $rff "\tREAD_NODE_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $off q{
+	/* CustomName is a key to lookup CustomScanMethods */
+	appendStringInfoString(str, " :methods ");
+	outToken(str, node->methods->CustomName);
+};
+			print $rff q!
+	{
+		/* Lookup CustomScanMethods by CustomName */
+		char	   *custom_name;
+		const CustomScanMethods *methods;
+		token = pg_strtok(&length); /* skip methods: */
+		token = pg_strtok(&length); /* CustomName */
+		custom_name = nullable_string(token, length);
+		methods = GetCustomScanMethods(custom_name, false);
+		local_node->methods = methods;
+	}
+! unless $no_read;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $off "}
+";
+	print $rff "
+\tREAD_DONE();
+}
+" unless $no_read;
+}
+
+close $off;
+close $rff;
+close $ofs;
+close $rfs;
+
+
+# now rename the temporary files to their final name
+foreach my $file (qw(nodetags.h copyfuncs.funcs.c copyfuncs.switch.c equalfuncs.funcs.c equalfuncs.switch.c outfuncs.funcs.c outfuncs.switch.c readfuncs.funcs.c readfuncs.switch.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4315c53080..37508af94d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -31,11 +31,10 @@
 
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
-#include "nodes/plannodes.h"
+#include "nodes/bitmapset.h"
+#include "nodes/nodes.h"
+#include "nodes/pg_list.h"
 #include "utils/datum.h"
-#include "utils/rel.h"
 
 static void outChar(StringInfo str, char c);
 
@@ -306,6 +305,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
 }
 
 
+#include "outfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from plannodes.h
  */
@@ -1155,6 +1157,7 @@ _outVar(StringInfo str, const Var *node)
 	WRITE_INT_FIELD(varattnosyn);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outConst(StringInfo str, const Const *node)
@@ -1176,6 +1179,7 @@ _outConst(StringInfo str, const Const *node)
 		outDatum(str, node->constvalue, node->constlen, node->constbyval);
 }
 
+#ifdef OBSOLETE
 static void
 _outParam(StringInfo str, const Param *node)
 {
@@ -1346,6 +1350,7 @@ _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 	WRITE_NODE_FIELD(args);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outBoolExpr(StringInfo str, const BoolExpr *node)
@@ -1374,6 +1379,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2586,6 +2592,7 @@ _outIndexOptInfo(StringInfo str, const IndexOptInfo *node)
 	WRITE_BOOL_FIELD(hypothetical);
 	/* we don't bother with fields copied from the index AM's API struct */
 }
+#endif /* OBSOLETE */
 
 static void
 _outForeignKeyOptInfo(StringInfo str, const ForeignKeyOptInfo *node)
@@ -2613,6 +2620,7 @@ _outForeignKeyOptInfo(StringInfo str, const ForeignKeyOptInfo *node)
 		appendStringInfo(str, " %d", list_length(node->rinfos[i]));
 }
 
+#ifdef OBSOLETE
 static void
 _outStatisticExtInfo(StringInfo str, const StatisticExtInfo *node)
 {
@@ -2624,6 +2632,7 @@ _outStatisticExtInfo(StringInfo str, const StatisticExtInfo *node)
 	WRITE_CHAR_FIELD(kind);
 	WRITE_BITMAPSET_FIELD(keys);
 }
+#endif /* OBSOLETE */
 
 static void
 _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
@@ -2652,6 +2661,7 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
 	WRITE_UINT_FIELD(ec_max_security);
 }
 
+#ifdef OBSOLETE
 static void
 _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 {
@@ -2836,6 +2846,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
 	WRITE_NODE_FIELD(item);
 	WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2858,6 +2869,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -3191,6 +3203,7 @@ _outStatsElem(StringInfo str, const StatsElem *node)
 	WRITE_STRING_FIELD(name);
 	WRITE_NODE_FIELD(expr);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outQuery(StringInfo str, const Query *node)
@@ -3265,6 +3278,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3430,6 +3444,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_NODE_FIELD(colCollations);
 	WRITE_NODE_FIELD(groupClauses);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
@@ -3510,6 +3525,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3533,6 +3549,7 @@ _outTableSampleClause(StringInfo str, const TableSampleClause *node)
 	WRITE_NODE_FIELD(args);
 	WRITE_NODE_FIELD(repeatable);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outA_Expr(StringInfo str, const A_Expr *node)
@@ -3651,6 +3668,7 @@ _outBitString(StringInfo str, const BitString *node)
 	appendStringInfoString(str, node->bsval);
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3682,6 +3700,7 @@ _outRawStmt(StringInfo str, const RawStmt *node)
 	WRITE_LOCATION_FIELD(stmt_location);
 	WRITE_INT_FIELD(stmt_len);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outA_Const(StringInfo str, const A_Const *node)
@@ -3698,6 +3717,7 @@ _outA_Const(StringInfo str, const A_Const *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outA_Star(StringInfo str, const A_Star *node)
 {
@@ -3842,6 +3862,7 @@ _outRangeTableFuncCol(StringInfo str, const RangeTableFuncCol *node)
 	WRITE_NODE_FIELD(coldefexpr);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outConstraint(StringInfo str, const Constraint *node)
@@ -3964,6 +3985,7 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -4024,6 +4046,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -4055,6 +4078,8 @@ outNode(StringInfo str, const void *obj)
 		appendStringInfoChar(str, '{');
 		switch (nodeTag(obj))
 		{
+#include "outfuncs.switch.c"
+#ifdef OBSOLETE
 			case T_PlannedStmt:
 				_outPlannedStmt(str, obj);
 				break;
@@ -4766,6 +4791,7 @@ outNode(StringInfo str, const void *obj)
 			case T_JsonTableSibling:
 				_outJsonTableSibling(str, obj);
 				break;
+#endif /*OBSOLETE*/
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6a05b69415..f427aa05ec 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -33,9 +33,7 @@
 #include <math.h>
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/parsenodes.h"
-#include "nodes/plannodes.h"
+#include "nodes/bitmapset.h"
 #include "nodes/readfuncs.h"
 
 
@@ -238,6 +236,8 @@ readBitmapset(void)
 	return _readBitmapset();
 }
 
+#include "readfuncs.funcs.c"
+
 /*
  * _readQuery
  */
@@ -291,6 +291,7 @@ _readQuery(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readNotifyStmt
  */
@@ -629,6 +630,7 @@ _readVar(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readConst
@@ -655,6 +657,7 @@ _readConst(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readParam
  */
@@ -880,6 +883,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readBoolExpr
@@ -907,6 +911,7 @@ _readBoolExpr(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readSubLink
  */
@@ -1649,6 +1654,7 @@ _readAppendRelInfo(void)
 /*
  *	Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1744,6 +1750,7 @@ _readRangeTblEntry(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2872,6 +2879,7 @@ _readAlternativeSubPlan(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2903,6 +2911,7 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2937,6 +2946,7 @@ _readPartitionRangeDatum(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2961,7 +2971,11 @@ parseNodeString(void)
 #define MATCH(tokname, namelen) \
 	(length == namelen && memcmp(token, tokname, namelen) == 0)
 
-	if (MATCH("QUERY", 5))
+	if (false)
+		;
+#include "readfuncs.switch.c"
+#ifdef OBSOLETE
+	else if (MATCH("QUERY", 5))
 		return_value = _readQuery();
 	else if (MATCH("WITHCHECKOPTION", 15))
 		return_value = _readWithCheckOption();
@@ -3235,6 +3249,7 @@ parseNodeString(void)
 		return_value = _readJsonTableParent();
 	else if (MATCH("JSONTABLESIBLING", 16))
 		return_value = _readJsonTableSibling();
+#endif /*OBSOLETE*/
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/include/Makefile b/src/include/Makefile
index 5f257a958c..17cfd268b8 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -81,6 +81,7 @@ clean:
 	rm -f parser/gram.h storage/lwlocknames.h utils/probes.h
 	rm -f catalog/schemapg.h catalog/system_fk_info.h
 	rm -f catalog/pg_*_d.h catalog/header-stamp
+	rm -f nodes/nodetags.h nodes/header-stamp
 
 distclean maintainer-clean: clean
 	rm -f pg_config.h pg_config_ext.h pg_config_os.h stamp-h stamp-ext-h
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 6306bb6fc6..37c11522ee 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -235,14 +235,14 @@ extern PGDLLIMPORT const TupleTableSlotOps TTSOpsBufferHeapTuple;
  * Tuple table slot implementations.
  */
 
-typedef struct VirtualTupleTableSlot
+typedef struct VirtualTupleTableSlot pg_node_attr(abstract)
 {
 	TupleTableSlot base;
 
 	char	   *data;			/* data for materialized slots */
 } VirtualTupleTableSlot;
 
-typedef struct HeapTupleTableSlot
+typedef struct HeapTupleTableSlot pg_node_attr(abstract)
 {
 	TupleTableSlot base;
 
@@ -254,7 +254,7 @@ typedef struct HeapTupleTableSlot
 } HeapTupleTableSlot;
 
 /* heap tuple residing in a buffer */
-typedef struct BufferHeapTupleTableSlot
+typedef struct BufferHeapTupleTableSlot pg_node_attr(abstract)
 {
 	HeapTupleTableSlot base;
 
@@ -267,7 +267,7 @@ typedef struct BufferHeapTupleTableSlot
 	Buffer		buffer;			/* tuple's buffer, or InvalidBuffer */
 } BufferHeapTupleTableSlot;
 
-typedef struct MinimalTupleTableSlot
+typedef struct MinimalTupleTableSlot pg_node_attr(abstract)
 {
 	TupleTableSlot base;
 
diff --git a/src/include/nodes/.gitignore b/src/include/nodes/.gitignore
new file mode 100644
index 0000000000..99fb1d3787
--- /dev/null
+++ b/src/include/nodes/.gitignore
@@ -0,0 +1,2 @@
+/nodetags.h
+/header-stamp
diff --git a/src/include/nodes/extensible.h b/src/include/nodes/extensible.h
index 6244c8d961..fab5bf690b 100644
--- a/src/include/nodes/extensible.h
+++ b/src/include/nodes/extensible.h
@@ -29,7 +29,7 @@
  * specific type of node.  extnodename can be looked up to find the
  * ExtensibleNodeMethods for this node type.
  */
-typedef struct ExtensibleNode
+typedef struct ExtensibleNode pg_node_attr(custom_copy_equal, custom_read_write)
 {
 	NodeTag		type;
 	const char *extnodename;	/* identifier of ExtensibleNodeMethods */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ce1fc4deb..d77aad6473 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -27,6 +27,8 @@ typedef enum NodeTag
 {
 	T_Invalid = 0,
 
+#include "nodes/nodetags.h"
+#ifdef OBSOLETE
 	/*
 	 * TAGS FOR EXECUTOR NODES (execnodes.h)
 	 */
@@ -563,8 +565,58 @@ typedef enum NodeTag
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition, /* in nodes/supportnodes.h */
 	T_SupportRequestWFuncMonotonic	/* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * pg_node_attr() - Used in node definitions to set extra information for
+ * gen_node_support.pl
+ *
+ * Attributes can be attached to a node as a whole (the attribute
+ * specification must be on the same line as "struct") or to a specific field
+ * (must be at the end of the line).  The argument is a comma-separated list
+ * of attributes.  Unrecognized attributes cause an error.
+ *
+ * Valid node attributes:
+ *
+ * - abstract: Abstract types are types that cannot be instantiated but that
+ *   can be supertypes of other types.  We track their fields, so that
+ *   subtypes can use them, but we don't emit a node tag, so you can't
+ *   instantiate them.
+ *
+ * - custom_copy_equal: Has custom implementations in copyfuncs.c and
+ *   equalfuncs.c.
+ *
+ * - custom_read_write: Has custom implementations in outfuncs.c and
+ *   readfuncs.c.
+ *
+ * - no_copy_equal: Does not support copyObject() and equal() at all.
+ *
+ * - no_read: Does not support nodeRead() at all.
+ *
+ * - special_read_write: Has special treatment in outNode() and nodeRead().
+ *
+ * Valid node field attributes:
+ *
+ * - array_size(OTHERFIELD): This field is a dynamically allocated array with
+ *   size indicated by the mentioned other field.  The other field is either a
+ *   scalar or a list, in which case the length of the list is used.
+ *
+ * - copy_ignore: Ignore the field for copy.
+ *
+ * - equal_ignore: Ignore the field for equality.
+ *
+ * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
+ *   (Otherwise, compare normally.)
+ *
+ * - read_write_ignore: Ignore the field for read/write.
+ *
+ * - write_only_relids, write_only_nondefault_pathtarget, write_only_req_outer:
+ *   Special handling for Path struct; see there.
+ *
+ */
+#define pg_node_attr(...)
+
 /*
  * The first field of a node of any type is guaranteed to be the NodeTag.
  * Hence the type of any node can be gotten by casting it to Node. Declaring
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f93d866548..fb026c6b1f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -115,7 +115,7 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
  *	  Planning converts a Query tree into a Plan tree headed by a PlannedStmt
  *	  node --- the Query structure is not used by the executor.
  */
-typedef struct Query
+typedef struct Query pg_node_attr(custom_read_write)
 {
 	NodeTag		type;
 
@@ -123,8 +123,11 @@ typedef struct Query
 
 	QuerySource querySource;	/* where did I come from? */
 
-	/* query identifier (can be set by plugins) */
-	uint64		queryId;
+	/*
+	 * query identifier (can be set by plugins); ignored for equal, might not
+	 * be set
+	 */
+	uint64		queryId pg_node_attr(equal_ignore);
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
@@ -286,7 +289,7 @@ typedef enum A_Expr_Kind
 	AEXPR_NOT_BETWEEN_SYM		/* name must be "NOT BETWEEN SYMMETRIC" */
 } A_Expr_Kind;
 
-typedef struct A_Expr
+typedef struct A_Expr pg_node_attr(custom_read_write, no_read)
 {
 	NodeTag		type;
 	A_Expr_Kind kind;			/* see above */
@@ -299,7 +302,7 @@ typedef struct A_Expr
 /*
  * A_Const - a literal constant
  */
-typedef struct A_Const
+typedef struct A_Const pg_node_attr(custom_copy_equal, custom_read_write, no_read)
 {
 	NodeTag		type;
 
@@ -398,7 +401,7 @@ typedef struct FuncCall
  * This can appear within ColumnRef.fields, A_Indirection.indirection, and
  * ResTarget.indirection lists.
  */
-typedef struct A_Star
+typedef struct A_Star pg_node_attr(no_read)
 {
 	NodeTag		type;
 } A_Star;
@@ -1010,7 +1013,7 @@ typedef enum RTEKind
 								 * present during parsing or rewriting */
 } RTEKind;
 
-typedef struct RangeTblEntry
+typedef struct RangeTblEntry pg_node_attr(custom_read_write)
 {
 	NodeTag		type;
 
@@ -2606,7 +2609,7 @@ typedef enum ConstrType			/* types of constraints */
 #define FKCONSTR_MATCH_PARTIAL		'p'
 #define FKCONSTR_MATCH_SIMPLE		's'
 
-typedef struct Constraint
+typedef struct Constraint pg_node_attr(custom_read_write, no_read)
 {
 	NodeTag		type;
 	ConstrType	contype;		/* see above */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b88cfb8dc0..4212610d5e 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -3,6 +3,8 @@
  * pathnodes.h
  *	  Definitions for planner's internal data structures, especially Paths.
  *
+ * We don't support copying RelOptInfo, IndexOptInfo, or Path nodes.
+ * There are some subsidiary structs that are useful to copy, though.
  *
  * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -85,17 +87,20 @@ typedef enum UpperRelationKind
  * PlannerGlobal holds state for an entire planner invocation; this state
  * is shared across all levels of sub-Queries that exist in the command being
  * planned.
+ *
+ * Not all fields are printed.  (In some cases, there is no print support for
+ * the field type.)
  *----------
  */
-typedef struct PlannerGlobal
+typedef struct PlannerGlobal pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
-	ParamListInfo boundParams;	/* Param values provided to planner() */
+	ParamListInfo boundParams pg_node_attr(read_write_ignore);	/* Param values provided to planner() */
 
 	List	   *subplans;		/* Plans for SubPlan nodes */
 
-	List	   *subroots;		/* PlannerInfos for SubPlan nodes */
+	List	   *subroots pg_node_attr(read_write_ignore);		/* PlannerInfos for SubPlan nodes */
 
 	Bitmapset  *rewindPlanIDs;	/* indices of subplans that require REWIND */
 
@@ -129,7 +134,7 @@ typedef struct PlannerGlobal
 
 	char		maxParallelHazard;	/* worst PROPARALLEL hazard level */
 
-	PartitionDirectory partition_directory; /* partition descriptors */
+	PartitionDirectory partition_directory pg_node_attr(read_write_ignore); /* partition descriptors */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -148,6 +153,9 @@ typedef struct PlannerGlobal
  *
  * For reasons explained in optimizer/optimizer.h, we define the typedef
  * either here or in that header, whichever is read first.
+ *
+ * Not all fields are printed.  (In some cases, there is no print support for
+ * the field type.)
  *----------
  */
 #ifndef HAVE_PLANNERINFO_TYPEDEF
@@ -155,7 +163,7 @@ typedef struct PlannerInfo PlannerInfo;
 #define HAVE_PLANNERINFO_TYPEDEF 1
 #endif
 
-struct PlannerInfo
+struct PlannerInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -165,7 +173,7 @@ struct PlannerInfo
 
 	Index		query_level;	/* 1 at the outermost Query */
 
-	PlannerInfo *parent_root;	/* NULL at outermost Query */
+	PlannerInfo *parent_root pg_node_attr(read_write_ignore);	/* NULL at outermost Query */
 
 	/*
 	 * plan_params contains the expressions that this query level needs to
@@ -183,15 +191,15 @@ struct PlannerInfo
 	 * does not correspond to a base relation, such as a join RTE or an
 	 * unreferenced view RTE; or if the RelOptInfo hasn't been made yet.
 	 */
-	struct RelOptInfo **simple_rel_array;	/* All 1-rel RelOptInfos */
-	int			simple_rel_array_size;	/* allocated size of array */
+	struct RelOptInfo **simple_rel_array pg_node_attr(read_write_ignore);	/* All 1-rel RelOptInfos */
+	int			simple_rel_array_size pg_node_attr(read_write_ignore);	/* allocated size of array */
 
 	/*
 	 * simple_rte_array is the same length as simple_rel_array and holds
 	 * pointers to the associated rangetable entries.  Using this is a shade
 	 * faster than using rt_fetch(), mostly due to fewer indirections.
 	 */
-	RangeTblEntry **simple_rte_array;	/* rangetable as an array */
+	RangeTblEntry **simple_rte_array pg_node_attr(read_write_ignore);	/* rangetable as an array */
 
 	/*
 	 * append_rel_array is the same length as the above arrays, and holds
@@ -199,7 +207,7 @@ struct PlannerInfo
 	 * child_relid, or NULL if the rel is not an appendrel child.  The array
 	 * itself is not allocated if append_rel_list is empty.
 	 */
-	struct AppendRelInfo **append_rel_array;
+	struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore);
 
 	/*
 	 * all_baserels is a Relids set of all base relids (but not "other"
@@ -227,7 +235,7 @@ struct PlannerInfo
 	 * GEQO.
 	 */
 	List	   *join_rel_list;
-	struct HTAB *join_rel_hash;
+	struct HTAB *join_rel_hash pg_node_attr(read_write_ignore);
 
 	/*
 	 * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -236,7 +244,7 @@ struct PlannerInfo
 	 * automatically added to the join_rel_level[join_cur_level] list.
 	 * join_rel_level is NULL if not in use.
 	 */
-	List	  **join_rel_level; /* lists of join-relation RelOptInfos */
+	List	  **join_rel_level pg_node_attr(read_write_ignore); /* lists of join-relation RelOptInfos */
 	int			join_cur_level; /* index of list being extended */
 
 	List	   *init_plans;		/* init SubPlans for query */
@@ -299,16 +307,16 @@ struct PlannerInfo
 	List	   *distinct_pathkeys;	/* distinctClause pathkeys, if any */
 	List	   *sort_pathkeys;	/* sortClause pathkeys, if any */
 
-	List	   *part_schemes;	/* Canonicalised partition schemes used in the
+	List	   *part_schemes pg_node_attr(read_write_ignore);	/* Canonicalised partition schemes used in the
 								 * query. */
 
-	List	   *initial_rels;	/* RelOptInfos we are now trying to join */
+	List	   *initial_rels pg_node_attr(read_write_ignore);	/* RelOptInfos we are now trying to join */
 
 	/* Use fetch_upper_rel() to get any particular upper rel */
-	List	   *upper_rels[UPPERREL_FINAL + 1]; /* upper-rel RelOptInfos */
+	List	   *upper_rels[UPPERREL_FINAL + 1] pg_node_attr(read_write_ignore); /* upper-rel RelOptInfos */
 
 	/* Result tlists chosen by grouping_planner for upper-stage processing */
-	struct PathTarget *upper_targets[UPPERREL_FINAL + 1];
+	struct PathTarget *upper_targets[UPPERREL_FINAL + 1] pg_node_attr(read_write_ignore);
 
 	/*
 	 * The fully-processed targetlist is kept here.  It differs from
@@ -333,12 +341,12 @@ struct PlannerInfo
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
 	/* for GroupingFunc fixup */
-	AttrNumber *grouping_map;
+	AttrNumber *grouping_map pg_node_attr(array_size(update_colnos), read_write_ignore);
 	/* List of MinMaxAggInfos */
 	List	   *minmax_aggs;
 
 	/* context holding PlannerInfo */
-	MemoryContext planner_cxt;
+	MemoryContext planner_cxt pg_node_attr(read_write_ignore);
 
 	Cardinality total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -360,11 +368,11 @@ struct PlannerInfo
 	/*
 	 * Information about aggregates. Filled by preprocess_aggrefs().
 	 */
-	List	   *agginfos;		/* AggInfo structs */
-	List	   *aggtransinfos;	/* AggTransInfo structs */
-	int			numOrderedAggs; /* number w/ DISTINCT/ORDER BY/WITHIN GROUP */
-	bool		hasNonPartialAggs;	/* does any agg not support partial mode? */
-	bool		hasNonSerialAggs;	/* is any partial agg non-serializable? */
+	List	   *agginfos pg_node_attr(read_write_ignore);		/* AggInfo structs */
+	List	   *aggtransinfos pg_node_attr(read_write_ignore);	/* AggTransInfo structs */
+	int			numOrderedAggs pg_node_attr(read_write_ignore); /* number w/ DISTINCT/ORDER BY/WITHIN GROUP */
+	bool		hasNonPartialAggs pg_node_attr(read_write_ignore);	/* does any agg not support partial mode? */
+	bool		hasNonSerialAggs pg_node_attr(read_write_ignore);	/* is any partial agg non-serializable? */
 
 	/* These fields are used only when hasRecursion is true: */
 	int			wt_param_id;	/* PARAM_EXEC ID for the work table */
@@ -378,11 +386,11 @@ struct PlannerInfo
 	 * These fields are workspace for setrefs.c.  Each is an array
 	 * corresponding to glob->subplans.
 	 */
-	bool	   *isAltSubplan;
-	bool	   *isUsedSubplan;
+	bool	   *isAltSubplan pg_node_attr(read_write_ignore);
+	bool	   *isUsedSubplan pg_node_attr(read_write_ignore);
 
 	/* optional private data for join_search_hook, e.g., GEQO */
-	void	   *join_search_private;
+	void	   *join_search_private pg_node_attr(read_write_ignore);
 
 	/* Does this query modify any partition key columns? */
 	bool		partColsUpdated;
@@ -639,6 +647,9 @@ typedef struct PartitionSchemeData *PartitionScheme;
  * Furthermore, FULL JOINs add extra nullable_partexprs expressions
  * corresponding to COALESCE expressions of the left and right join columns,
  * to simplify matching join clauses to those lists.
+ *
+ * Not all fields are printed.  (In some cases, there is no print support for
+ * the field type.)
  *----------
  */
 
@@ -680,7 +691,7 @@ typedef enum RelOptKind
 	 (rel)->reloptkind == RELOPT_OTHER_JOINREL || \
 	 (rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
 
-typedef struct RelOptInfo
+typedef struct RelOptInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -747,9 +758,9 @@ typedef struct RelOptInfo
 	/* largest attrno of rel */
 	AttrNumber	max_attr;
 	/* array indexed [min_attr .. max_attr] */
-	Relids	   *attr_needed;
+	Relids	   *attr_needed pg_node_attr(read_write_ignore);
 	/* array indexed [min_attr .. max_attr] */
-	int32	   *attr_widths;
+	int32	   *attr_widths pg_node_attr(read_write_ignore);
 	/* LATERAL Vars and PHVs referenced by rel */
 	List	   *lateral_vars;
 	/* rels that reference me laterally */
@@ -784,16 +795,18 @@ typedef struct RelOptInfo
 	/* join is only valid for current user */
 	bool		useridiscurrent;
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
-	struct FdwRoutine *fdwroutine;
-	void	   *fdw_private;
+	struct FdwRoutine *fdwroutine pg_node_attr(read_write_ignore);
+	void	   *fdw_private pg_node_attr(read_write_ignore);
 
 	/*
 	 * cache space for remembering if we have proven this relation unique
+	 *
+	 * can't print unique_for_rels/non_unique_for_rels; BMSes aren't Nodes
 	 */
 	/* known unique for these other relid set(s) */
-	List	   *unique_for_rels;
+	List	   *unique_for_rels pg_node_attr(read_write_ignore);
 	/* known not unique for these set(s) */
-	List	   *non_unique_for_rels;
+	List	   *non_unique_for_rels pg_node_attr(read_write_ignore);
 
 	/*
 	 * used by various scans and joins:
@@ -821,24 +834,24 @@ typedef struct RelOptInfo
 	 * used for partitioned relations:
 	 */
 	/* Partitioning scheme */
-	PartitionScheme part_scheme;
+	PartitionScheme part_scheme pg_node_attr(read_write_ignore);
 
 	/*
 	 * Number of partitions; -1 if not yet set; in case of a join relation 0
 	 * means it's considered unpartitioned
 	 */
-	int			nparts;
+	int			nparts pg_node_attr(read_write_ignore);
 	/* Partition bounds */
-	struct PartitionBoundInfoData *boundinfo;
+	struct PartitionBoundInfoData *boundinfo pg_node_attr(read_write_ignore);
 	/* True if partition bounds were created by partition_bounds_merge() */
 	bool		partbounds_merged;
 	/* Partition constraint, if not the root */
-	List	   *partition_qual;
+	List	   *partition_qual pg_node_attr(read_write_ignore);
 
 	/*
 	 * Array of RelOptInfos of partitions, stored in the same order as bounds
 	 */
-	struct RelOptInfo **part_rels;
+	struct RelOptInfo **part_rels pg_node_attr(read_write_ignore);
 
 	/*
 	 * Bitmap with members acting as indexes into the part_rels[] array to
@@ -848,9 +861,9 @@ typedef struct RelOptInfo
 	/* Relids set of all partition relids */
 	Relids		all_partrels;
 	/* Non-nullable partition key expressions */
-	List	  **partexprs;
+	List	  **partexprs pg_node_attr(read_write_ignore);
 	/* Nullable partition key expressions */
-	List	  **nullable_partexprs;
+	List	  **nullable_partexprs pg_node_attr(read_write_ignore);
 } RelOptInfo;
 
 /*
@@ -909,7 +922,7 @@ typedef struct IndexOptInfo IndexOptInfo;
 #define HAVE_INDEXOPTINFO_TYPEDEF 1
 #endif
 
-struct IndexOptInfo
+struct IndexOptInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -917,8 +930,8 @@ struct IndexOptInfo
 	Oid			indexoid;
 	/* tablespace of index (not table) */
 	Oid			reltablespace;
-	/* back-link to index's table */
-	RelOptInfo *rel;
+	/* back-link to index's table; don't print, else infinite recursion */
+	RelOptInfo *rel pg_node_attr(read_write_ignore);
 
 	/*
 	 * index-size statistics (from pg_class and elsewhere)
@@ -938,31 +951,39 @@ struct IndexOptInfo
 	/* number of key columns in index */
 	int			nkeycolumns;
 
+	/*
+	 * array fields aren't really worth the trouble to print
+	 */
+
 	/*
 	 * column numbers of index's attributes both key and included columns, or
 	 * 0
 	 */
-	int		   *indexkeys;
+	int		   *indexkeys pg_node_attr(read_write_ignore);
 	/* OIDs of collations of index columns */
-	Oid		   *indexcollations;
+	Oid		   *indexcollations pg_node_attr(read_write_ignore);
 	/* OIDs of operator families for columns */
-	Oid		   *opfamily;
+	Oid		   *opfamily pg_node_attr(read_write_ignore);
 	/* OIDs of opclass declared input data types */
-	Oid		   *opcintype;
+	Oid		   *opcintype pg_node_attr(read_write_ignore);
 	/* OIDs of btree opfamilies, if orderable */
-	Oid		   *sortopfamily;
+	Oid		   *sortopfamily pg_node_attr(read_write_ignore);
 	/* is sort order descending? */
-	bool	   *reverse_sort;
+	bool	   *reverse_sort pg_node_attr(read_write_ignore);
 	/* do NULLs come first in the sort order? */
-	bool	   *nulls_first;
+	bool	   *nulls_first pg_node_attr(read_write_ignore);
 	/* opclass-specific options for columns */
-	bytea	  **opclassoptions;
+	bytea	  **opclassoptions pg_node_attr(read_write_ignore);
 	/* which index cols can be returned in an index-only scan? */
-	bool	   *canreturn;
+	bool	   *canreturn pg_node_attr(read_write_ignore);
 	/* OID of the access method (in pg_am) */
 	Oid			relam;
-	/* expressions for non-simple index columns */
-	List	   *indexprs;
+
+	/*
+	 * expressions for non-simple index columns; redundant to print since we
+	 * print indextlist
+	 */
+	List	   *indexprs pg_node_attr(read_write_ignore);
 	/* predicate if a partial index, else NIL */
 	List	   *indpred;
 
@@ -989,17 +1010,17 @@ struct IndexOptInfo
 	 * Remaining fields are copied from the index AM's API struct
 	 * (IndexAmRoutine)
 	 */
-	bool		amcanorderbyop;
-	bool		amoptionalkey;
-	bool		amsearcharray;
-	bool		amsearchnulls;
+	bool		amcanorderbyop pg_node_attr(read_write_ignore);
+	bool		amoptionalkey pg_node_attr(read_write_ignore);
+	bool		amsearcharray pg_node_attr(read_write_ignore);
+	bool		amsearchnulls pg_node_attr(read_write_ignore);
 	/* does AM have amgettuple interface? */
-	bool		amhasgettuple;
+	bool		amhasgettuple pg_node_attr(read_write_ignore);
 	/* does AM have amgetbitmap interface? */
-	bool		amhasgetbitmap;
-	bool		amcanparallel;
+	bool		amhasgetbitmap pg_node_attr(read_write_ignore);
+	bool		amcanparallel pg_node_attr(read_write_ignore);
 	/* does AM have ammarkpos interface? */
-	bool		amcanmarkpos;
+	bool		amcanmarkpos pg_node_attr(read_write_ignore);
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
 };
@@ -1012,7 +1033,7 @@ struct IndexOptInfo
  * INDEX_MAX_KEYS columns in a foreign key constraint.  Each array has
  * nkeys valid entries.
  */
-typedef struct ForeignKeyOptInfo
+typedef struct ForeignKeyOptInfo pg_node_attr(custom_read_write, no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -1027,11 +1048,11 @@ typedef struct ForeignKeyOptInfo
 	/* number of columns in the foreign key */
 	int			nkeys;
 	/* cols in referencing table */
-	AttrNumber	conkey[INDEX_MAX_KEYS];
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
 	/* cols in referenced table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
 	/* PK = FK operator OIDs */
-	Oid			conpfeqop[INDEX_MAX_KEYS];
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
 
 	/*
 	 * Derived info about whether FK's equality conditions match the query:
@@ -1060,7 +1081,7 @@ typedef struct ForeignKeyOptInfo
  * Each pg_statistic_ext row is represented by one or more nodes of this
  * type, or even zero if ANALYZE has not computed them.
  */
-typedef struct StatisticExtInfo
+typedef struct StatisticExtInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -1068,10 +1089,13 @@ typedef struct StatisticExtInfo
 	Oid			statOid;
 
 	/* includes child relations */
-	bool		inherit;
+	bool		inherit pg_node_attr(read_write_ignore);
 
-	/* back-link to statistic's table */
-	RelOptInfo *rel;
+	/*
+	 * back-link to statistic's table; don't print, infinite recursion on plan
+	 * tree dump
+	 */
+	RelOptInfo *rel pg_node_attr(read_write_ignore);
 
 	/* statistics kind of this entry */
 	char		kind;
@@ -1123,7 +1147,7 @@ typedef struct StatisticExtInfo
  * NB: if ec_merged isn't NULL, this class has been merged into another, and
  * should be ignored in favor of using the pointed-to class.
  */
-typedef struct EquivalenceClass
+typedef struct EquivalenceClass pg_node_attr(custom_read_write, no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -1173,7 +1197,7 @@ typedef struct EquivalenceClass
  * anyarray_ops would never work without this.  Use em_datatype when
  * looking up a specific btree operator to work with this expression.
  */
-typedef struct EquivalenceMember
+typedef struct EquivalenceMember pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -1257,7 +1281,7 @@ typedef enum VolatileFunctionStatus
  * deal with sort/group refnos when needed with less expense than including
  * TargetEntry nodes in the exprs list.
  */
-typedef struct PathTarget
+typedef struct PathTarget pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -1265,7 +1289,7 @@ typedef struct PathTarget
 	List	   *exprs;
 
 	/* corresponding sort/group refnos, or 0 */
-	Index	   *sortgrouprefs;
+	Index	   *sortgrouprefs pg_node_attr(array_size(exprs));
 
 	/* cost of evaluating the expressions */
 	QualCost	cost;
@@ -1296,7 +1320,7 @@ typedef struct PathTarget
  * on how the join is formed.  The relevant clauses will appear in each
  * parameterized join path's joinrestrictinfo list, instead.
  */
-typedef struct ParamPathInfo
+typedef struct ParamPathInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -1334,22 +1358,41 @@ typedef struct ParamPathInfo
  *
  * "pathkeys" is a List of PathKey nodes (see above), describing the sort
  * ordering of the path's output rows.
+ *
+ * We do not support copying Path trees, mainly because the circular linkages
+ * between RelOptInfo and Path nodes can't be handled easily in a simple
+ * depth-first traversal.  We also don't have read support at the moment.
  */
-typedef struct Path
+typedef struct Path pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
 	/* tag identifying scan/join method */
 	NodeTag		pathtype;
 
-	/* the relation this path can build */
-	RelOptInfo *parent;
+	/*
+	 * the relation this path can build
+	 *
+	 * We do NOT print the parent, else we'd be in infinite recursion.  We can
+	 * print the parent's relids for identification purposes, though.
+	 */
+	RelOptInfo *parent pg_node_attr(write_only_relids);
 
-	/* list of Vars/Exprs, cost, width */
-	PathTarget *pathtarget;
+	/*
+	 * list of Vars/Exprs, cost, width
+	 *
+	 * We print the pathtarget only if it's not the default one for the rel.
+	 */
+	PathTarget *pathtarget pg_node_attr(write_only_nondefault_pathtarget);
 
-	/* parameterization info, or NULL if none */
-	ParamPathInfo *param_info;
+	/*
+	 * parameterization info, or NULL if none
+	 *
+	 * We do not print the whole of param_info, since it's printed via
+	 * RelOptInfo; it's sufficient and less cluttering to print just the
+	 * required outer relids.
+	 */
+	ParamPathInfo *param_info pg_node_attr(write_only_req_outer);
 
 	/* engage parallel-aware logic? */
 	bool		parallel_aware;
@@ -1455,7 +1498,7 @@ typedef struct IndexPath
  * column, i.e. the indexcol values must form a nondecreasing sequence.
  * (The order of multiple clauses for the same index column is unspecified.)
  */
-typedef struct IndexClause
+typedef struct IndexClause pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 	struct RestrictInfo *rinfo; /* original restriction or join clause */
@@ -1750,7 +1793,7 @@ typedef struct GatherMergePath
  * All join-type paths share these fields.
  */
 
-typedef struct JoinPath
+typedef struct JoinPath pg_node_attr(abstract)
 {
 	Path		path;
 
@@ -1952,14 +1995,14 @@ typedef struct AggPath
  * Various annotations used for grouping sets in the planner.
  */
 
-typedef struct GroupingSetData
+typedef struct GroupingSetData pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 	List	   *set;			/* grouping set as list of sortgrouprefs */
 	Cardinality numGroups;		/* est. number of result groups */
 } GroupingSetData;
 
-typedef struct RollupData
+typedef struct RollupData pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 	List	   *groupClause;	/* applicable subset of parse->groupClause */
@@ -2224,6 +2267,12 @@ typedef struct LimitPath
  * apply only one.  We mark clauses of this kind by setting parent_ec to
  * point to the generating EquivalenceClass.  Multiple clauses with the same
  * parent_ec in the same join are redundant.
+ *
+ * Most fields are ignored for equality, since they may not be set yet, and
+ * should be derivable from the clause anyway.
+ *
+ * parent_ec, left_ec, right_ec are not printed, lest it lead to infinite
+ * recursion in plan tree dump.
  */
 
 typedef struct RestrictInfo
@@ -2240,22 +2289,22 @@ typedef struct RestrictInfo
 	bool		outerjoin_delayed;
 
 	/* see comment above */
-	bool		can_join;
+	bool		can_join pg_node_attr(equal_ignore);
 
 	/* see comment above */
-	bool		pseudoconstant;
+	bool		pseudoconstant pg_node_attr(equal_ignore);
 
 	/* true if known to contain no leaked Vars */
-	bool		leakproof;
+	bool		leakproof pg_node_attr(equal_ignore);
 
 	/* to indicate if clause contains any volatile functions. */
-	VolatileFunctionStatus has_volatile;
+	VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);
 
 	/* see comment above */
 	Index		security_level;
 
 	/* The set of relids (varnos) actually referenced in the clause: */
-	Relids		clause_relids;
+	Relids		clause_relids pg_node_attr(equal_ignore);
 
 	/* The set of relids required to evaluate the clause: */
 	Relids		required_relids;
@@ -2270,84 +2319,89 @@ typedef struct RestrictInfo
 	 * Relids in the left/right side of the clause.  These fields are set for
 	 * any binary opclause.
 	 */
-	Relids		left_relids;
-	Relids		right_relids;
+	Relids		left_relids pg_node_attr(equal_ignore);
+	Relids		right_relids pg_node_attr(equal_ignore);
 
 	/*
 	 * Modified clause with RestrictInfos.  This field is NULL unless clause
 	 * is an OR clause.
 	 */
-	Expr	   *orclause;
+	Expr	   *orclause pg_node_attr(equal_ignore);
 
 	/*
 	 * Generating EquivalenceClass.  This field is NULL unless clause is
 	 * potentially redundant.
 	 */
-	EquivalenceClass *parent_ec;
+	EquivalenceClass *parent_ec pg_node_attr(equal_ignore, read_write_ignore);
 
 	/*
 	 * cache space for cost and selectivity
 	 */
 
 	/* eval cost of clause; -1 if not yet set */
-	QualCost	eval_cost;
+	QualCost	eval_cost pg_node_attr(equal_ignore);
 
 	/*
 	 * selectivity for "normal" (JOIN_INNER) semantics; -1 if not yet set; >1
 	 * means a redundant clause
 	 */
-	Selectivity norm_selec;
+	Selectivity norm_selec pg_node_attr(equal_ignore);
 	/* selectivity for outer join semantics; -1 if not yet set */
-	Selectivity outer_selec;
+	Selectivity outer_selec pg_node_attr(equal_ignore);
 
 	/*
 	 * opfamilies containing clause operator; valid if clause is
 	 * mergejoinable, else NIL
 	 */
-	List	   *mergeopfamilies;
+	List	   *mergeopfamilies pg_node_attr(equal_ignore);
 
 	/*
 	 * cache space for mergeclause processing; NULL if not yet set
 	 */
 
 	/* EquivalenceClass containing lefthand */
-	EquivalenceClass *left_ec;
+	EquivalenceClass *left_ec pg_node_attr(equal_ignore, read_write_ignore);
 	/* EquivalenceClass containing righthand */
-	EquivalenceClass *right_ec;
+	EquivalenceClass *right_ec pg_node_attr(equal_ignore, read_write_ignore);
 	/* EquivalenceMember for lefthand */
-	EquivalenceMember *left_em;
+	EquivalenceMember *left_em pg_node_attr(equal_ignore);
 	/* EquivalenceMember for righthand */
-	EquivalenceMember *right_em;
-	/* list of MergeScanSelCache structs */
-	List	   *scansel_cache;
+	EquivalenceMember *right_em pg_node_attr(equal_ignore);
+
+	/*
+	 * List of MergeScanSelCache structs.  Those aren't Nodes, so hard to
+	 * copy.  Ignoring it will have the effect that copying will just reset
+	 * the cache.
+	 */
+	List	   *scansel_cache pg_node_attr(copy_ignore, equal_ignore);
 
 	/*
 	 * transient workspace for use while considering a specific join path; T =
 	 * outer var on left, F = on right
 	 */
-	bool		outer_is_left;
+	bool		outer_is_left pg_node_attr(equal_ignore);
 
 	/*
 	 * copy of clause operator; valid if clause is hashjoinable, else
 	 * InvalidOid
 	 */
-	Oid			hashjoinoperator;
+	Oid			hashjoinoperator pg_node_attr(equal_ignore);
 
 	/*
 	 * cache space for hashclause processing; -1 if not yet set
 	 */
 	/* avg bucketsize of left side */
-	Selectivity left_bucketsize;
+	Selectivity left_bucketsize pg_node_attr(equal_ignore);
 	/* avg bucketsize of right side */
-	Selectivity right_bucketsize;
+	Selectivity right_bucketsize pg_node_attr(equal_ignore);
 	/* left side's most common val's freq */
-	Selectivity left_mcvfreq;
+	Selectivity left_mcvfreq pg_node_attr(equal_ignore);
 	/* right side's most common val's freq */
-	Selectivity right_mcvfreq;
+	Selectivity right_mcvfreq pg_node_attr(equal_ignore);
 
 	/* hash equality operators used for memoize nodes, else InvalidOid */
-	Oid			left_hasheqoperator;
-	Oid			right_hasheqoperator;
+	Oid			left_hasheqoperator pg_node_attr(equal_ignore);
+	Oid			right_hasheqoperator pg_node_attr(equal_ignore);
 } RestrictInfo;
 
 /*
@@ -2397,6 +2451,17 @@ typedef struct MergeScanSelCache
  * Although the planner treats this as an expression node type, it is not
  * recognized by the parser or executor, so we declare it here rather than
  * in primnodes.h.
+ *
+ * We intentionally do not compare phexpr.  Two PlaceHolderVars with the
+ * same ID and levelsup should be considered equal even if the contained
+ * expressions have managed to mutate to different states.  This will
+ * happen during final plan construction when there are nested PHVs, since
+ * the inner PHV will get replaced by a Param in some copies of the outer
+ * PHV.  Another way in which it can happen is that initplan sublinks
+ * could get replaced by differently-numbered Params when sublink folding
+ * is done.  (The end result of such a situation would be some
+ * unreferenced initplans, which is annoying but not really a problem.) On
+ * the same reasoning, there is no need to examine phrels.
  */
 
 typedef struct PlaceHolderVar
@@ -2404,10 +2469,10 @@ typedef struct PlaceHolderVar
 	Expr		xpr;
 
 	/* the represented expression */
-	Expr	   *phexpr;
+	Expr	   *phexpr pg_node_attr(equal_ignore);
 
 	/* base relids syntactically within expr src */
-	Relids		phrels;
+	Relids		phrels pg_node_attr(equal_ignore);
 
 	/* ID for PHV (unique within planner run) */
 	Index		phid;
@@ -2572,7 +2637,7 @@ typedef struct AppendRelInfo
 	 * child column is dropped or doesn't exist in the parent.
 	 */
 	int			num_child_cols; /* length of array */
-	AttrNumber *parent_colnos;
+	AttrNumber *parent_colnos pg_node_attr(array_size(num_child_cols));
 
 	/*
 	 * We store the parent table's OID here for inheritance, or InvalidOid for
@@ -2600,7 +2665,7 @@ typedef struct AppendRelInfo
  * We add such a reference to root->processed_tlist when creating the entry,
  * and it propagates into the plan tree from there.
  */
-typedef struct RowIdentityVarInfo
+typedef struct RowIdentityVarInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -2643,7 +2708,10 @@ typedef struct PlaceHolderInfo
 	/* ID for PH (unique within planner run) */
 	Index		phid;
 
-	/* copy of PlaceHolderVar tree */
+	/*
+	 * copy of PlaceHolderVar tree (should be redundant for comparison, could
+	 * be ignored)
+	 */
 	PlaceHolderVar *ph_var;
 
 	/* lowest level we can evaluate value at */
@@ -2664,7 +2732,7 @@ typedef struct PlaceHolderInfo
  * function.  MinMaxAggPath contains a list of these, and if we accept that
  * path, the list is stored into root->minmax_aggs for use during setrefs.c.
  */
-typedef struct MinMaxAggInfo
+typedef struct MinMaxAggInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -2677,8 +2745,11 @@ typedef struct MinMaxAggInfo
 	/* expression we are aggregating on */
 	Expr	   *target;
 
-	/* modified "root" for planning the subquery */
-	PlannerInfo *subroot;
+	/*
+	 * modified "root" for planning the subquery; not printed, too large, not
+	 * interesting enough
+	 */
+	PlannerInfo *subroot pg_node_attr(read_write_ignore);
 
 	/* access path for subquery */
 	Path	   *path;
@@ -2737,7 +2808,7 @@ typedef struct MinMaxAggInfo
  * Instead, we just record the assignment of the slot number by appending to
  * root->glob->paramExecTypes.
  */
-typedef struct PlannerParamItem
+typedef struct PlannerParamItem pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d5c0ebe859..19b5ce2ec6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -286,16 +286,16 @@ typedef struct MergeAppend
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *sortColIdx;
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
 	/* OIDs of operators to sort them by */
-	Oid		   *sortOperators;
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));
 
 	/* OIDs of collations */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numCols));
 
 	/* NULLS FIRST/LAST directions */
-	bool	   *nullsFirst;
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));
 
 	/* Info for run-time subplan pruning; NULL if we're not doing that */
 	struct PartitionPruneInfo *part_prune_info;
@@ -322,11 +322,11 @@ typedef struct RecursiveUnion
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *dupColIdx;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *dupOperators;
-	Oid		   *dupCollations;
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 
 	/* estimated number of groups in input */
 	long		numGroups;
@@ -812,16 +812,16 @@ typedef struct MergeJoin
 	/* these are arrays, but have the same length as the mergeclauses list: */
 
 	/* per-clause OIDs of btree opfamilies */
-	Oid		   *mergeFamilies;
+	Oid		   *mergeFamilies pg_node_attr(array_size(mergeclauses));
 
 	/* per-clause OIDs of collations */
-	Oid		   *mergeCollations;
+	Oid		   *mergeCollations pg_node_attr(array_size(mergeclauses));
 
 	/* per-clause ordering (ASC or DESC) */
-	int		   *mergeStrategies;
+	int		   *mergeStrategies pg_node_attr(array_size(mergeclauses));
 
 	/* per-clause nulls ordering */
-	bool	   *mergeNullsFirst;
+	bool	   *mergeNullsFirst pg_node_attr(array_size(mergeclauses));
 } MergeJoin;
 
 /* ----------------
@@ -863,10 +863,10 @@ typedef struct Memoize
 	int			numKeys;
 
 	/* hash operators for each key */
-	Oid		   *hashOperators;
+	Oid		   *hashOperators pg_node_attr(array_size(numKeys));
 
 	/* collations for each key */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numKeys));
 
 	/* cache keys in the form of exprs containing parameters */
 	List	   *param_exprs;
@@ -905,16 +905,16 @@ typedef struct Sort
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *sortColIdx;
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
 	/* OIDs of operators to sort them by */
-	Oid		   *sortOperators;
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));
 
 	/* OIDs of collations */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numCols));
 
 	/* NULLS FIRST/LAST directions */
-	bool	   *nullsFirst;
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));
 } Sort;
 
 /* ----------------
@@ -941,11 +941,11 @@ typedef struct Group
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *grpColIdx;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *grpOperators;
-	Oid		   *grpCollations;
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 } Group;
 
 /* ---------------
@@ -976,11 +976,11 @@ typedef struct Agg
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *grpColIdx;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *grpOperators;
-	Oid		   *grpCollations;
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 
 	/* estimated number of groups in input */
 	long		numGroups;
@@ -1015,25 +1015,25 @@ typedef struct WindowAgg
 	int			partNumCols;
 
 	/* their indexes in the target list */
-	AttrNumber *partColIdx;
+	AttrNumber *partColIdx pg_node_attr(array_size(partNumCols));
 
 	/* equality operators for partition columns */
-	Oid		   *partOperators;
+	Oid		   *partOperators pg_node_attr(array_size(partNumCols));
 
 	/* collations for partition columns */
-	Oid		   *partCollations;
+	Oid		   *partCollations pg_node_attr(array_size(partNumCols));
 
 	/* number of columns in ordering clause */
 	int			ordNumCols;
 
 	/* their indexes in the target list */
-	AttrNumber *ordColIdx;
+	AttrNumber *ordColIdx pg_node_attr(array_size(ordNumCols));
 
 	/* equality operators for ordering columns */
-	Oid		   *ordOperators;
+	Oid		   *ordOperators pg_node_attr(array_size(ordNumCols));
 
 	/* collations for ordering columns */
-	Oid		   *ordCollations;
+	Oid		   *ordCollations pg_node_attr(array_size(ordNumCols));
 
 	/* frame_clause options, see WindowDef */
 	int			frameOptions;
@@ -1086,13 +1086,13 @@ typedef struct Unique
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *uniqColIdx;
+	AttrNumber *uniqColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *uniqOperators;
+	Oid		   *uniqOperators pg_node_attr(array_size(numCols));
 
 	/* collations for equality comparisons */
-	Oid		   *uniqCollations;
+	Oid		   *uniqCollations pg_node_attr(array_size(numCols));
 } Unique;
 
 /* ------------
@@ -1137,16 +1137,16 @@ typedef struct GatherMerge
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *sortColIdx;
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
 	/* OIDs of operators to sort them by */
-	Oid		   *sortOperators;
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));
 
 	/* OIDs of collations */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numCols));
 
 	/* NULLS FIRST/LAST directions */
-	bool	   *nullsFirst;
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));
 
 	/*
 	 * param id's of initplans which are referred at gather merge or one of
@@ -1197,11 +1197,11 @@ typedef struct SetOp
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *dupColIdx;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *dupOperators;
-	Oid		   *dupCollations;
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 
 	/* where is the flag column, if any */
 	AttrNumber	flagColIdx;
@@ -1253,13 +1253,13 @@ typedef struct Limit
 	int			uniqNumCols;
 
 	/* their indexes in the target list */
-	AttrNumber *uniqColIdx;
+	AttrNumber *uniqColIdx pg_node_attr(array_size(uniqNumCols));
 
 	/* equality operators to compare with */
-	Oid		   *uniqOperators;
+	Oid		   *uniqOperators pg_node_attr(array_size(uniqNumCols));
 
 	/* collations for equality comparisons */
-	Oid		   *uniqCollations;
+	Oid		   *uniqCollations pg_node_attr(array_size(uniqNumCols));
 } Limit;
 
 
@@ -1425,13 +1425,13 @@ typedef struct PartitionedRelPruneInfo
 	int			nparts;
 
 	/* subplan index by partition index, or -1 */
-	int		   *subplan_map;
+	int		   *subplan_map pg_node_attr(array_size(nparts));
 
 	/* subpart index by partition index, or -1 */
-	int		   *subpart_map;
+	int		   *subpart_map pg_node_attr(array_size(nparts));
 
 	/* relation OID by partition index, or 0 */
-	Oid		   *relid_map;
+	Oid		   *relid_map pg_node_attr(array_size(nparts));
 
 	/*
 	 * initial_pruning_steps shows how to prune during executor startup (i.e.,
@@ -1452,7 +1452,7 @@ typedef struct PartitionedRelPruneInfo
  *
  * step_id is the global identifier of the step within its pruning context.
  */
-typedef struct PartitionPruneStep
+typedef struct PartitionPruneStep pg_node_attr(abstract)
 {
 	NodeTag		type;
 	int			step_id;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 732c00c098..9e6b4bdb1d 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -64,8 +64,11 @@ typedef struct RangeVar
 {
 	NodeTag		type;
 
-	/* the catalog (database) name, or NULL */
-	char	   *catalogname;
+	/*
+	 * the catalog (database) name, or NULL; ignored for read/write, since it
+	 * is presently not semantically meaningful
+	 */
+	char	   *catalogname pg_node_attr(read_write_ignore);
 
 	/* the schema name, or NULL */
 	char	   *schemaname;
@@ -155,7 +158,7 @@ typedef struct IntoClause
  * contains NodeTag, this is a formality, but it is an easy form of
  * documentation.  See also the ExprState node types in execnodes.h.
  */
-typedef struct Expr
+typedef struct Expr pg_node_attr(abstract)
 {
 	NodeTag		type;
 } Expr;
@@ -233,10 +236,15 @@ typedef struct Var
 	 */
 	Index		varlevelsup;
 
+	/*
+	 * varnosyn/varattnosyn are ignored for equality, because Vars with
+	 * different syntactic identifiers are semantically the same as long as
+	 * their varno/varattno match.
+	 */
 	/* syntactic relation index (0 if unknown) */
-	Index		varnosyn;
+	Index		varnosyn pg_node_attr(equal_ignore);
 	/* syntactic attribute number */
-	AttrNumber	varattnosyn;
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -250,7 +258,7 @@ typedef struct Var
  * references).  This ensures that the Const node is self-contained and makes
  * it more likely that equal() will see logically identical values as equal.
  */
-typedef struct Const
+typedef struct Const pg_node_attr(custom_copy_equal, custom_read_write)
 {
 	Expr		xpr;
 	Oid			consttype;		/* pg_type OID of the constant's datatype */
@@ -374,8 +382,11 @@ typedef struct Aggref
 	/* OID of collation that function should use */
 	Oid			inputcollid;
 
-	/* type Oid of aggregate's transition value */
-	Oid			aggtranstype;
+	/*
+	 * type Oid of aggregate's transition value; ignored for equal since it
+	 * might not be set yet
+	 */
+	Oid			aggtranstype pg_node_attr(equal_ignore);
 
 	/* type Oids of direct and aggregated args */
 	List	   *aggargtypes;
@@ -455,10 +466,10 @@ typedef struct GroupingFunc
 	List	   *args;
 
 	/* ressortgrouprefs of arguments */
-	List	   *refs;
+	List	   *refs pg_node_attr(equal_ignore);
 
 	/* actual column positions set by planner */
-	List	   *cols;
+	List	   *cols pg_node_attr(equal_ignore);
 
 	/* same as Aggref.agglevelsup */
 	Index		agglevelsup;
@@ -634,7 +645,7 @@ typedef struct OpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of underlying function */
-	Oid			opfuncid;
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
 
 	/* PG_TYPE OID of result value */
 	Oid			opresulttype;
@@ -698,6 +709,10 @@ typedef OpExpr NullIfExpr;
  * corresponding function and won't be used during execution.  For
  * non-hashtable based NOT INs, negfuncid will be set to InvalidOid.  See
  * convert_saop_to_hashed_saop().
+ *
+ * Similar to OpExpr, opfuncid, hashfuncid, and negfuncid are not necessarily
+ * filled in right away, so will be ignored for equality if they are not set
+ * yet.
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -707,13 +722,13 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of comparison function */
-	Oid			opfuncid;
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
 
 	/* PG_PROC OID of hash func or InvalidOid */
-	Oid			hashfuncid;
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);
 
 	/* PG_PROC OID of negator of opfuncid function or InvalidOid.  See above */
-	Oid			negfuncid;
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);
 
 	/* true for ANY, false for ALL */
 	bool		useOr;
@@ -740,7 +755,7 @@ typedef enum BoolExprType
 	AND_EXPR, OR_EXPR, NOT_EXPR
 } BoolExprType;
 
-typedef struct BoolExpr
+typedef struct BoolExpr pg_node_attr(custom_read_write)
 {
 	Expr		xpr;
 	BoolExprType boolop;
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index eaf937051c..6193f51536 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -25,7 +25,7 @@
  * (There used to be a Value node, which encompassed all these different node types.  Hence the name of this file.)
  */
 
-typedef struct Integer
+typedef struct Integer pg_node_attr(special_read_write)
 {
 	NodeTag		type;
 	int			ival;
@@ -42,25 +42,25 @@ typedef struct Integer
  * Note that an integer-looking string will get lexed as T_Float if the value
  * is too large to fit in an 'int'.
  */
-typedef struct Float
+typedef struct Float pg_node_attr(special_read_write)
 {
 	NodeTag		type;
 	char	   *fval;
 } Float;
 
-typedef struct Boolean
+typedef struct Boolean pg_node_attr(special_read_write)
 {
 	NodeTag		type;
 	bool		boolval;
 } Boolean;
 
-typedef struct String
+typedef struct String pg_node_attr(special_read_write)
 {
 	NodeTag		type;
 	char	   *sval;
 } String;
 
-typedef struct BitString
+typedef struct BitString pg_node_attr(special_read_write)
 {
 	NodeTag		type;
 	char	   *bsval;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 1896a9a06d..3e6da86688 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -265,7 +265,7 @@ typedef struct RelationData
  * Currently, we mostly cache fields of interest to the planner, but the set
  * of fields has already grown the constraint OID for other uses.
  */
-typedef struct ForeignKeyCacheInfo
+typedef struct ForeignKeyCacheInfo pg_node_attr(no_read)
 {
 	NodeTag		type;
 	Oid			conoid;			/* oid of the constraint itself */
@@ -273,9 +273,12 @@ typedef struct ForeignKeyCacheInfo
 	Oid			confrelid;		/* relation referenced by the foreign key */
 	int			nkeys;			/* number of columns in the foreign key */
 	/* these arrays each have nkeys valid entries: */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	/* cols in referencing table */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
+	/* cols in referenced table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
+	/* PK = FK operator OIDs */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
 } ForeignKeyCacheInfo;
 
 
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index d30e8fcb11..286b5810c9 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -841,6 +841,52 @@ EOF
 		close($chs);
 	}
 
+	if (IsNewer('src/backend/nodes/node-support-stamp',
+		'src/backend/nodes/gen_node_support.pl'))
+	{
+		# XXX duplicates src/backend/nodes/Makefile
+
+		my @node_headers = qw(
+			nodes/nodes.h
+			nodes/execnodes.h
+			nodes/plannodes.h
+			nodes/primnodes.h
+			nodes/pathnodes.h
+			nodes/extensible.h
+			nodes/parsenodes.h
+			nodes/replnodes.h
+			nodes/value.h
+			commands/trigger.h
+			commands/event_trigger.h
+			foreign/fdwapi.h
+			access/amapi.h
+			access/tableam.h
+			access/tsmapi.h
+			utils/rel.h
+			nodes/supportnodes.h
+			executor/tuptable.h
+			nodes/lockoptions.h
+			access/sdir.h
+		);
+
+		chdir('src/backend/nodes');
+
+		my @node_files = map { "../../../src/include/$_" } @node_headers;
+
+		system("perl gen_node_support.pl @node_files");
+		open(my $f, '>', 'node-support-stamp') || confess "Could not touch node-support-stamp";
+		close($f);
+		chdir('../../..');
+	}
+
+	if (IsNewer(
+			'src/include/nodes/nodetags.h',
+			'src/backend/nodes/nodetags.h'))
+	{
+		copyFile('src/backend/nodes/nodetags.h',
+			'src/include/nodes/nodetags.h');
+	}
+
 	open(my $o, '>', "doc/src/sgml/version.sgml")
 	  || croak "Could not write to version.sgml\n";
 	print $o <<EOF;

base-commit: b55f62abb2c2e07dfae99e19a2b3d7ca9e58dc1a
-- 
2.36.1

#43Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#41)
Re: automatically generating node support functions

On 06.07.22 02:54, Tom Lane wrote:

It might be enough to invent a struct-level attribute allowing
manual assignment of node tags, ie

typedef struct MyNewNode pg_node_attr(nodetag=466)

where it'd be the programmer's responsibility to pick a nonconflicting
tag number. We'd only ever use that in ABI-frozen branches, so
manual assignment of the tag value should be workable.

Yes, I'm aware of this issue, and that was also more or less my idea.

(Well, before the introduction of per-struct attributes, I was thinking
about parsing nodes.h to see if the tag is listed explicitly. But this
is probably better.)

#44Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#42)
2 attachment(s)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

[ v7-0001-Automatically-generate-node-support-functions.patch ]

I have gone through this and made some proposed changes (attached),
and I think it is almost committable. There is one nasty problem
we need a solution to, which is that pgindent is not at all on board
with this idea of attaching node attrs to typedefs. It pushes them
to the next line, like this:

@@ -691,7 +709,8 @@
(rel)->reloptkind == RELOPT_OTHER_JOINREL || \
(rel)->reloptkind == RELOPT_OTHER_UPPER_REL)

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

which is already enough to break the simplistic parsing in
gen_node_support.pl. Now, we could fix that parsing logic to deal
with this layout, but this also seems to change pgindent's opinion
of whether the subsequent braced material is part of a typedef or a
function. That results in it injecting a lot of vertical space
that wasn't there before, which is annoying.

I experimented a bit and found that we could do it this way:

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

without (AFAICT) confusing pgindent, but I've not tried to adapt
the perl script or the code to that style.

Anyway, besides that, I have some comments that I've implemented
in the attached delta patch.

* After further thought I'm okay with your theory that attaching
special copy/equal rules to specific field types is appropriate.
We might at some point want the pg_node_attr(copy_as_scalar)
approach too, but we can always add that later. However, I thought
some more comments about it were needed in the *nodes.h files,
so I added those. (My general feeling about this is that if
anyone needs to look into gen_node_support.pl to understand how
the backend works, we've failed at documentation.)

* As written, the patch created equal() support for all Plan structs,
which is quite a bit of useless code bloat. I solved this by
separating no_copy and no_equal properties, so that we could mark
Plan as no_equal while still having copy support.

* I did not like the semantics of copy_ignore one bit: it was
relying on the pre-zeroing behavior of makeNode() to be sane at
all, and I don't want that to be a requirement. (I foresee
wanting to flat-copy node contents and turn COPY_SCALAR_FIELD
into a no-op.) I replaced it with copy_as(VALUE) to provide
better-defined semantics.

* Likewise, read_write_ignore left the contents of the field after
reading too squishy for me. I invented read_as(VALUE) parallel
to copy_as() to fix the semantics, and added a check that you
can only use read_write_ignore if the struct is no_read or
you provide read_as(). (This could be factored differently
of course.)

* I threw in a bunch more no_read markers to bring the readfuncs.c
contents into closer alignment with what we have today. Maybe
there is an argument for accepting that code bloat, but it's a
discussion to have later. In any case, most of the pathnodes.h
structs HAVE to be marked no_read because there's no sane way
to reconstruct them from outfuncs output.

* I got rid of the code that stripped underscores from outfuncs
struct labels. That seemed like an entirely unnecessary
behavioral change.

* FWIW, I'm okay with the question about

# XXX Previously, for subtyping, only the leaf field name is
# used. Ponder whether we want to keep it that way.

I thought that it might make the output too cluttered, but after
some study of the results from printing plans and planner data
structures, it's not a big addition, and indeed I kind of like it.

* Fixed a bug in write_only_req_outer code.

* Made Plan and Join into abstract nodes.

Anyway, if we can fix the impedance mismatch with pgindent,
I think this is committable. There is a lot of follow-on
work that could be considered, but I'd like to get the present
changes in place ASAP so that other patches can be rebased
onto something stable.

I've attached a delta patch, and also repeated v7 so as not
to confuse the cfbot.

regards, tom lane

Attachments:

v7-0001-Automatically-generate-node-support-functions.patchtext/x-diff; charset=us-ascii; name=v7-0001-Automatically-generate-node-support-functions.patchDownload
From c82ee081a7a8cdc77b44f325d1df695b55a60b06 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 6 Jul 2022 12:13:32 +0200
Subject: [PATCH v7] Automatically generate node support functions

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

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

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

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

Subtyping (TidScan -> Scan) is supported.

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

Discussion: https://www.postgresql.org/message-id/flat/c1097590-a6a4-486a-64b1-e1f9cc0533ce%40enterprisedb.com
---
 src/backend/Makefile                  |  10 +-
 src/backend/nodes/.gitignore          |   4 +
 src/backend/nodes/Makefile            |  59 ++
 src/backend/nodes/copyfuncs.c         |  19 +-
 src/backend/nodes/equalfuncs.c        |  22 +-
 src/backend/nodes/gen_node_support.pl | 770 ++++++++++++++++++++++++++
 src/backend/nodes/outfuncs.c          |  34 +-
 src/backend/nodes/readfuncs.c         |  23 +-
 src/include/Makefile                  |   1 +
 src/include/executor/tuptable.h       |   8 +-
 src/include/nodes/.gitignore          |   2 +
 src/include/nodes/extensible.h        |   2 +-
 src/include/nodes/nodes.h             |  52 ++
 src/include/nodes/parsenodes.h        |  19 +-
 src/include/nodes/pathnodes.h         | 315 +++++++----
 src/include/nodes/plannodes.h         |  92 +--
 src/include/nodes/primnodes.h         |  45 +-
 src/include/nodes/value.h             |  10 +-
 src/include/utils/rel.h               |  11 +-
 src/tools/msvc/Solution.pm            |  46 ++
 20 files changed, 1320 insertions(+), 224 deletions(-)
 create mode 100644 src/backend/nodes/.gitignore
 create mode 100644 src/backend/nodes/gen_node_support.pl
 create mode 100644 src/include/nodes/.gitignore

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a02006788..953c80db5a 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -143,11 +143,15 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
 submake-catalog-headers:
 	$(MAKE) -C catalog distprep generated-header-symlinks
 
+# run this unconditionally to avoid needing to know its dependencies here:
+submake-nodes-headers:
+	$(MAKE) -C nodes distprep generated-header-symlinks
+
 # run this unconditionally to avoid needing to know its dependencies here:
 submake-utils-headers:
 	$(MAKE) -C utils distprep generated-header-symlinks
 
-.PHONY: submake-catalog-headers submake-utils-headers
+.PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 # Make symlinks for these headers in the include directory. That way
 # we can cut down on the -I options. Also, a symlink is automatically
@@ -162,7 +166,7 @@ submake-utils-headers:
 
 .PHONY: generated-headers
 
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
@@ -185,6 +189,7 @@ distprep:
 	$(MAKE) -C parser	gram.c gram.h scan.c
 	$(MAKE) -C bootstrap	bootparse.c bootscanner.c
 	$(MAKE) -C catalog	distprep
+	$(MAKE) -C nodes	distprep
 	$(MAKE) -C replication	repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
 	$(MAKE) -C storage/lmgr	lwlocknames.h lwlocknames.c
 	$(MAKE) -C utils	distprep
@@ -297,6 +302,7 @@ distclean: clean
 
 maintainer-clean: distclean
 	$(MAKE) -C catalog $@
+	$(MAKE) -C nodes $@
 	$(MAKE) -C utils $@
 	rm -f bootstrap/bootparse.c \
 	      bootstrap/bootscanner.c \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..0c14b5697b
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,4 @@
+/node-support-stamp
+/nodetags.h
+/*funcs.funcs.c
+/*funcs.switch.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..1a0d5b9314 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,62 @@ OBJS = \
 	value.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+node_headers = \
+	nodes/nodes.h \
+	nodes/execnodes.h \
+	nodes/plannodes.h \
+	nodes/primnodes.h \
+	nodes/pathnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.h \
+	nodes/value.h \
+	commands/trigger.h \
+	commands/event_trigger.h \
+	foreign/fdwapi.h \
+	access/amapi.h \
+	access/tableam.h \
+	access/tsmapi.h \
+	utils/rel.h \
+	nodes/supportnodes.h \
+	executor/tuptable.h \
+	nodes/lockoptions.h \
+	access/sdir.h
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-support-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+# node-support-stamp records the last time we ran gen_node_support.pl.
+# We don't rely on the timestamps of the individual output files,
+# because the Perl script won't update them if they didn't change (to
+# avoid unnecessary recompiles).
+node-support-stamp: gen_node_support.pl $(addprefix $(top_srcdir)/src/include/,$(node_headers))
+	$(PERL) $^
+	touch $@
+
+# These generated headers must be symlinked into builddir/src/include/,
+# using absolute links for the reasons explained in src/backend/Makefile.
+# We use header-stamp to record that we've done this because the symlinks
+# themselves may appear older than node-support-stamp.
+$(top_builddir)/src/include/nodes/header-stamp: node-support-stamp
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	cd '$(dir $@)' && for file in nodetags.h; do \
+	  rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \
+	done
+	touch $@
+
+copyfuncs.o: copyfuncs.c copyfuncs.funcs.c copyfuncs.switch.c | node-support-stamp
+equalfuncs.o: equalfuncs.c equalfuncs.funcs.c equalfuncs.switch.c | node-support-stamp
+outfuncs.o: outfuncs.c outfuncs.funcs.c outfuncs.switch.c | node-support-stamp
+readfuncs.o:  readfuncs.c readfuncs.funcs.c readfuncs.switch.c | node-support-stamp
+
+maintainer-clean: clean
+	rm -f node-support-stamp $(addsuffix funcs.funcs.c,copy equal out read) $(addsuffix funcs.switch.c,copy equal out read) nodetags.h
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 706d283a92..48778aa4ef 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -23,11 +23,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
-#include "nodes/plannodes.h"
 #include "utils/datum.h"
-#include "utils/rel.h"
 
 
 /*
@@ -73,6 +69,9 @@
 	(newnode->fldname = from->fldname)
 
 
+#include "copyfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /* ****************************************************************
  *					 plannodes.h copy functions
  * ****************************************************************
@@ -1465,6 +1464,7 @@ _copyVar(const Var *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * _copyConst
@@ -1504,6 +1504,7 @@ _copyConst(const Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /*
  * _copyParam
  */
@@ -3248,6 +3249,7 @@ _copyParamRef(const ParamRef *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 static A_Const *
 _copyA_Const(const A_Const *from)
@@ -3288,6 +3290,7 @@ _copyA_Const(const A_Const *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 static FuncCall *
 _copyFuncCall(const FuncCall *from)
 {
@@ -5453,6 +5456,7 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /* ****************************************************************
  *					extensible.h copy functions
@@ -5475,6 +5479,7 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+#ifdef OBSOLETE
 /* ****************************************************************
  *					value.h copy functions
  * ****************************************************************
@@ -5545,6 +5550,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 
 	return newnode;
 }
+#endif /*OBSOLETE*/
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5565,6 +5571,8 @@ copyObjectImpl(const void *from)
 
 	switch (nodeTag(from))
 	{
+#include "copyfuncs.switch.c"
+#ifdef OBSOLETE
 			/*
 			 * PLAN NODES
 			 */
@@ -6009,6 +6017,7 @@ copyObjectImpl(const void *from)
 		case T_BitString:
 			retval = _copyBitString(from);
 			break;
+#endif /*OBSOLETE*/
 
 			/*
 			 * LIST NODES
@@ -6026,6 +6035,7 @@ copyObjectImpl(const void *from)
 			retval = list_copy(from);
 			break;
 
+#ifdef OBSOLETE
 			/*
 			 * EXTENSIBLE NODES
 			 */
@@ -6577,6 +6587,7 @@ copyObjectImpl(const void *from)
 		case T_ForeignKeyCacheInfo:
 			retval = _copyForeignKeyCacheInfo(from);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index fccc0b4a18..7f09ccd978 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -10,9 +10,6 @@
  * because the circular linkages between RelOptInfo and Path nodes can't
  * be handled easily in a simple depth-first traversal.
  *
- * Currently, in fact, equal() doesn't know how to compare Plan trees
- * either.  This might need to be fixed someday.
- *
  * NOTE: it is intentional that parse location fields (in nodes that have
  * one) are not compared.  This is because we want, for example, a variable
  * "x" to be considered equal() to another reference to "x" in the query.
@@ -30,8 +27,6 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
 #include "utils/datum.h"
 
 
@@ -97,6 +92,9 @@
 	((void) 0)
 
 
+#include "equalfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from primnodes.h
  */
@@ -258,6 +256,7 @@ _equalVar(const Var *a, const Var *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalConst(const Const *a, const Const *b)
@@ -280,6 +279,7 @@ _equalConst(const Const *a, const Const *b)
 						a->constbyval, a->constlen);
 }
 
+#ifdef OBSOLETE
 static bool
 _equalParam(const Param *a, const Param *b)
 {
@@ -1304,6 +1304,7 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from extensible.h
@@ -1325,6 +1326,7 @@ _equalExtensibleNode(const ExtensibleNode *a, const ExtensibleNode *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from parsenodes.h
  */
@@ -2815,6 +2817,7 @@ _equalParamRef(const ParamRef *a, const ParamRef *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 static bool
 _equalA_Const(const A_Const *a, const A_Const *b)
@@ -2831,6 +2834,7 @@ _equalA_Const(const A_Const *a, const A_Const *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 static bool
 _equalFuncCall(const FuncCall *a, const FuncCall *b)
 {
@@ -3468,6 +3472,7 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * Stuff from pg_list.h
@@ -3528,6 +3533,7 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+#ifdef OBSOLETE
 /*
  * Stuff from value.h
  */
@@ -3571,6 +3577,7 @@ _equalBitString(const BitString *a, const BitString *b)
 
 	return true;
 }
+#endif /*OBSOLETE*/
 
 /*
  * equal
@@ -3601,6 +3608,8 @@ equal(const void *a, const void *b)
 
 	switch (nodeTag(a))
 	{
+#include "equalfuncs.switch.c"
+#ifdef OBSOLETE
 			/*
 			 * PRIMITIVE NODES
 			 */
@@ -3821,6 +3830,7 @@ equal(const void *a, const void *b)
 		case T_PlaceHolderInfo:
 			retval = _equalPlaceHolderInfo(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		case T_List:
 		case T_IntList:
@@ -3828,6 +3838,7 @@ equal(const void *a, const void *b)
 			retval = _equalList(a, b);
 			break;
 
+#ifdef OBSOLETE
 		case T_Integer:
 			retval = _equalInteger(a, b);
 			break;
@@ -4430,6 +4441,7 @@ equal(const void *a, const void *b)
 		case T_JsonTableColumn:
 			retval = _equalJsonTableColumn(a, b);
 			break;
+#endif /*OBSOLETE*/
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
new file mode 100644
index 0000000000..6aaf401a72
--- /dev/null
+++ b/src/backend/nodes/gen_node_support.pl
@@ -0,0 +1,770 @@
+#!/usr/bin/perl
+#----------------------------------------------------------------------
+#
+# Generate node support files:
+# - nodetags.h
+# - copyfuncs
+# - equalfuncs
+# - readfuncs
+# - outfuncs
+#
+# Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/backend/nodes/gen_node_support.pl
+#
+#----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use File::Basename;
+
+use FindBin;
+use lib "$FindBin::RealBin/../catalog";
+
+use Catalog;  # for RenameTempFile
+
+
+# Test whether first argument is element of the list in the second
+# argument
+sub elem
+{
+	my $x = shift;
+	return grep { $_ eq $x } @_;
+}
+
+
+# collect node names
+my @node_types = qw(Node);
+# collect info for each node type
+my %node_type_info;
+
+# node types we don't want copy/equal support for
+my @no_copy_equal;
+# node types we don't want read support for
+my @no_read;
+# node types we don't want read/write support for
+my @no_read_write;
+
+# types that are copied by straight assignment
+my @scalar_types = qw(
+	bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64
+	AclMode AttrNumber Cardinality Cost Index Oid Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr
+);
+
+# collect enum types
+my @enum_types;
+
+my @abstract_types = qw(Node);
+
+# Special cases that either don't have their own struct or the struct
+# is not in a header file.  We just generate node tags for them, but
+# they otherwise don't participate in node support.
+my @extra_tags = qw(
+	IntList OidList XidList
+	AllocSetContext GenerationContext SlabContext
+	TIDBitmap
+	WindowObjectData
+);
+
+# This is a regular node, but we skip parsing it from its header file
+# since we won't use its internal structure here anyway.
+push @node_types, qw(List);
+# See special treatment in outNode() and nodeRead().
+push @no_read_write, qw(List);
+
+# Nodes with custom copy/equal implementations are skipped from
+# .funcs.c but need case statements in .switch.c.
+my @custom_copy_equal;
+
+# Similarly for custom read/write implementations.
+my @custom_read_write;
+
+# EquivalenceClasses are never moved, so just shallow-copy the pointer
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember*);
+
+# This is a struct, so we can copy it by assignment.  Equal support is
+# currently not required.
+push @scalar_types, qw(QualCost);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy_equal, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation);
+
+
+## read input
+
+foreach my $infile (@ARGV)
+{
+	my $in_struct;
+	my $subline;
+	my $is_node_struct;
+	my $supertype;
+	my $supertype_field;
+
+	my @my_fields;
+	my %my_field_types;
+	my %my_field_attrs;
+
+	open my $ifh, '<', $infile or die "could not open \"$infile\": $!";
+
+	my $file_content = do { local $/; <$ifh> };
+
+	# strip C comments
+	$file_content =~ s{/\*.*?\*/}{}gs;
+
+	foreach my $line (split /\n/, $file_content)
+	{
+		chomp $line;
+		$line =~ s/\s*$//;
+		next if $line eq '';
+		next if $line =~ /^#(define|ifdef|endif)/;
+
+		# we are analyzing a struct definition
+		if ($in_struct)
+		{
+			$subline++;
+
+			# first line should have opening brace
+			if ($subline == 1)
+			{
+				$is_node_struct = 0;
+				$supertype = undef;
+				next if $line eq '{';
+				die;
+			}
+			# second line should have node tag or supertype
+			elsif ($subline == 2)
+			{
+				if ($line =~ /^\s*NodeTag\s+type;/)
+				{
+					$is_node_struct = 1;
+					next;
+				}
+				elsif ($line =~ /\s*(\w+)\s+(\w+);/ and elem $1, @node_types)
+				{
+					$is_node_struct = 1;
+					$supertype = $1;
+					$supertype_field = $2;
+					next;
+				}
+			}
+
+			# end of struct
+			if ($line =~ /^\}\s*$in_struct;$/ || $line =~ /^\};$/)
+			{
+				if ($is_node_struct)
+				{
+					# This is the end of a node struct definition.
+					# Save everything we have collected.
+
+					# node name
+					push @node_types, $in_struct;
+
+					# field names, types, attributes
+					my @f = @my_fields;
+					my %ft = %my_field_types;
+					my %fa = %my_field_attrs;
+
+					# If there is a supertype, add those fields, too.
+					if ($supertype)
+					{
+						my @superfields;
+						foreach my $sf (@{$node_type_info{$supertype}->{fields}})
+						{
+							my $fn = "${supertype_field}.$sf";
+							push @superfields, $fn;
+							$ft{$fn} = $node_type_info{$supertype}->{field_types}{$sf};
+							if ($node_type_info{$supertype}->{field_attrs}{$sf})
+							{
+								# Copy any attributes, adjusting array_size field references
+								my @newa = @{$node_type_info{$supertype}->{field_attrs}{$sf}};
+								foreach my $a (@newa)
+								{
+									$a =~ s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/;
+								}
+								$fa{$fn} = \@newa;
+							}
+						}
+						unshift @f, @superfields;
+					}
+					# save in global info structure
+					$node_type_info{$in_struct}->{fields} = \@f;
+					$node_type_info{$in_struct}->{field_types} = \%ft;
+					$node_type_info{$in_struct}->{field_attrs} = \%fa;
+
+					# Nodes from these files don't need to be
+					# supported, except the node tags.
+					if (elem basename($infile),
+						qw(execnodes.h trigger.h event_trigger.h amapi.h tableam.h
+							tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h))
+					{
+						push @no_copy_equal, $in_struct;
+						push @no_read_write, $in_struct;
+					}
+
+					# Propagate some node attributes from supertypes
+					if ($supertype)
+					{
+						push @no_copy_equal, $in_struct if elem $supertype, @no_copy_equal;
+						push @no_read, $in_struct if elem $supertype, @no_read;
+					}
+				}
+
+				# start new cycle
+				$in_struct = undef;
+				@my_fields = ();
+				%my_field_types = ();
+				%my_field_attrs = ();
+			}
+			# normal struct field
+			elsif ($line =~ /^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w(), ]*)\))?;/)
+			{
+				if ($is_node_struct)
+				{
+					my $type = $1;
+					my $name = $2;
+					my $array_size = $3;
+					my $attrs = $4;
+
+					# strip "const"
+					$type =~ s/^const\s*//;
+					# strip trailing space
+					$type =~ s/\s*$//;
+					# strip space between type and "*" (pointer) */
+					$type =~ s/\s+\*$/*/;
+
+					die if $type eq '';
+
+					my @attrs;
+					if ($attrs)
+					{
+						@attrs = split /,\s*/, $attrs;
+						foreach my $attr (@attrs)
+						{
+							if ($attr !~ /^array_size\(\w+\)$/ &&
+								!elem $attr, qw(copy_ignore equal_ignore equal_ignore_if_zero read_write_ignore
+									write_only_relids write_only_nondefault_pathtarget write_only_req_outer))
+							{
+								die "$infile:$.: unrecognized attribute \"$attr\"\n";
+							}
+						}
+					}
+
+					$type = $type . $array_size if $array_size;
+					push @my_fields, $name;
+					$my_field_types{$name} = $type;
+					$my_field_attrs{$name} = \@attrs;
+				}
+			}
+			else
+			{
+				if ($is_node_struct)
+				{
+					#warn "$infile:$.: could not parse \"$line\"\n";
+				}
+			}
+		}
+		# not in a struct
+		else
+		{
+			# start of a struct?
+			if ($line =~ /^(?:typedef )?struct (\w+)\s*(?:pg_node_attr\(([\w(), ]*)\))?$/ && $1 ne 'Node')
+			{
+				$in_struct = $1;
+				my $node_attrs = $2 || '';
+				$subline = 0;
+
+				foreach my $attr (split /,\s*/, $node_attrs)
+				{
+					if ($attr eq 'abstract')
+					{
+						push @abstract_types, $in_struct;
+					}
+					elsif ($attr eq 'custom_copy_equal')
+					{
+						push @custom_copy_equal, $in_struct;
+					}
+					elsif ($attr eq 'custom_read_write')
+					{
+						push @custom_read_write, $in_struct;
+					}
+					elsif ($attr eq 'no_copy_equal')
+					{
+						push @no_copy_equal, $in_struct;
+					}
+					elsif ($attr eq 'no_read')
+					{
+						push @no_read, $in_struct;
+					}
+					elsif ($attr eq 'special_read_write')
+					{
+						# This attribute is called
+						# "special_read_write" because there is
+						# special treatment in outNode() and
+						# nodeRead() for these nodes.  For this
+						# script, it's the same as "no_read_write",
+						# but calling the attribute that externally
+						# would probably be confusing, since
+						# read/write support does in fact exist.
+						push @no_read_write, $in_struct;
+					}
+					else
+					{
+						die "$infile:$.: unrecognized attribute \"$attr\"\n";
+					}
+				}
+			}
+			# one node type typedef'ed directly from another
+			elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types)
+			{
+				my $alias_of = $1;
+				my $n = $2;
+
+				# copy everything over
+				push @node_types, $n;
+				my @f = @{$node_type_info{$alias_of}->{fields}};
+				my %ft = %{$node_type_info{$alias_of}->{field_types}};
+				my %fa = %{$node_type_info{$alias_of}->{field_attrs}};
+				$node_type_info{$n}->{fields} = \@f;
+				$node_type_info{$n}->{field_types} = \%ft;
+				$node_type_info{$n}->{field_attrs} = \%fa;
+			}
+			# collect enum names
+			elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/)
+			{
+				push @enum_types, $1;
+			}
+		}
+	}
+
+	if ($in_struct)
+	{
+		die "runaway \"$in_struct\" in file \"$infile\"\n";
+	}
+
+	close $ifh;
+} # for each file
+
+
+## write output
+
+my $tmpext  = ".tmp$$";
+
+# nodetags.h
+
+open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
+
+my $i = 1;
+foreach my $n (@node_types,@extra_tags)
+{
+	next if elem $n, @abstract_types;
+	print $nt "\tT_${n} = $i,\n";
+	$i++;
+}
+
+close $nt;
+
+
+# make #include lines necessary to pull in all the struct definitions
+my $node_includes = '';
+foreach my $infile (sort @ARGV)
+{
+	$infile =~ s!.*src/include/!!;
+	$node_includes .= qq{#include "$infile"\n};
+}
+
+
+# copyfuncs.c, equalfuncs.c
+
+open my $cff, '>', 'copyfuncs.funcs.c' . $tmpext or die $!;
+open my $eff, '>', 'equalfuncs.funcs.c' . $tmpext or die $!;
+open my $cfs, '>', 'copyfuncs.switch.c' . $tmpext or die $!;
+open my $efs, '>', 'equalfuncs.switch.c' . $tmpext or die $!;
+
+# add required #include lines to each file set
+print $cff $node_includes;
+print $eff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_copy_equal;
+	next if $n eq 'List';
+
+	print $cfs "\t\tcase T_${n}:\n".
+	  "\t\t\tretval = _copy${n}(from);\n".
+	  "\t\t\tbreak;\n";
+
+	print $efs "\t\tcase T_${n}:\n".
+	  "\t\t\tretval = _equal${n}(a, b);\n".
+	  "\t\t\tbreak;\n";
+
+	next if elem $n, @custom_copy_equal;
+
+	print $cff "
+static $n *
+_copy${n}(const $n *from)
+{
+\t${n} *newnode = makeNode($n);
+
+";
+
+	print $eff "
+static bool
+_equal${n}(const $n *a, const $n *b)
+{
+";
+
+	# print instructions for each field
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
+		my $copy_ignore = (elem 'copy_ignore', @a);
+		my $equal_ignore = (elem 'equal_ignore', @a);
+
+		# select instructions by field type
+		if ($t eq 'char*')
+		{
+			print $cff "\tCOPY_STRING_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_STRING_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $cff "\tCOPY_BITMAPSET_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_BITMAPSET_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $cff "\tCOPY_LOCATION_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_LOCATION_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif (elem $t, @scalar_types or elem $t, @enum_types)
+		{
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			if (elem 'equal_ignore_if_zero', @a)
+			{
+				print $eff "\tif (a->$f != b->$f && a->$f != 0 && b->$f != 0)\n\t\treturn false;\n";
+			}
+			else
+			{
+				print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
+			}
+		}
+		# scalar type pointer
+		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
+		{
+			my $tt = $1;
+			my $array_size_field;
+			foreach my $a (@a)
+			{
+				if ($a =~ /^array_size.([\w.]+)/)
+				{
+					$array_size_field = $1;
+					last;
+				}
+			}
+			if (!$array_size_field)
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $cff "\tCOPY_POINTER_FIELD($f, list_length(from->$array_size_field) * sizeof($tt));\n" unless $copy_ignore;
+				print $eff "\tCOMPARE_POINTER_FIELD($f, list_length(a->$array_size_field) * sizeof($tt));\n" unless $equal_ignore;
+			}
+			else
+			{
+				print $cff "\tCOPY_POINTER_FIELD($f, from->$array_size_field * sizeof($tt));\n" unless $copy_ignore;
+				print $eff "\tCOMPARE_POINTER_FIELD($f, a->$array_size_field * sizeof($tt));\n" unless $equal_ignore;
+			}
+		}
+		# node type
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $cff "\tCOPY_NODE_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
+		}
+		# array (inline)
+		elsif ($t =~ /\w+\[/)
+		{
+			print $cff "\tCOPY_ARRAY_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_ARRAY_FIELD($f);\n" unless $equal_ignore;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			# Fields of these types are required to be a pointer to a
+			# static table of callback functions.  So we don't copy
+			# the table itself, just reference the original one.
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n" unless $copy_ignore;
+			print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $cff "
+\treturn newnode;
+}
+";
+	print $eff "
+\treturn true;
+}
+";
+}
+
+close $cff;
+close $eff;
+close $cfs;
+close $efs;
+
+
+# outfuncs.c, readfuncs.c
+
+open my $off, '>', 'outfuncs.funcs.c' . $tmpext or die $!;
+open my $rff, '>', 'readfuncs.funcs.c' . $tmpext or die $!;
+open my $ofs, '>', 'outfuncs.switch.c' . $tmpext or die $!;
+open my $rfs, '>', 'readfuncs.switch.c' . $tmpext or die $!;
+
+print $off $node_includes;
+print $rff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @no_read_write;
+
+	# XXX For now, skip all "Stmt"s except that ones that were there before.
+	if ($n =~ /Stmt$/)
+	{
+		my @keep = qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt ImportForeignSchemaStmt IndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt);
+		next unless elem $n, @keep;
+	}
+
+	my $no_read = (elem $n, @no_read);
+
+	# output format starts with upper case node type, underscores stripped
+	my $N = uc $n;
+	$N =~ s/_//g;
+
+	print $ofs "\t\t\tcase T_${n}:\n".
+	  "\t\t\t\t_out${n}(str, obj);\n".
+	  "\t\t\t\tbreak;\n";
+
+	print $rfs "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+
+	next if elem $n, @custom_read_write;
+
+	print $off "
+static void
+_out${n}(StringInfo str, const $n *node)
+{
+\tWRITE_NODE_TYPE(\"$N\");
+
+";
+
+	print $rff "
+static $n *
+_read${n}(void)
+{
+\tREAD_LOCALS($n);
+
+" unless $no_read;
+
+	# print instructions for each field
+	foreach my $f (@{$node_type_info{$n}->{fields}})
+	{
+		my $t = $node_type_info{$n}->{field_types}{$f};
+		my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
+		next if (elem 'read_write_ignore', @a);
+
+		# XXX Previously, for subtyping, only the leaf field name is
+		# used. Ponder whether we want to keep it that way.
+
+		# select instructions by field type
+		if ($t eq 'bool')
+		{
+			print $off "\tWRITE_BOOL_FIELD($f);\n";
+			print $rff "\tREAD_BOOL_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $off "\tWRITE_LOCATION_FIELD($f);\n";
+			print $rff "\tREAD_LOCATION_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'int' || $t eq 'int32' || $t eq 'AttrNumber' || $t eq 'StrategyNumber')
+		{
+			print $off "\tWRITE_INT_FIELD($f);\n";
+			print $rff "\tREAD_INT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint32' || $t eq 'bits32' || $t eq 'AclMode' || $t eq 'BlockNumber' || $t eq 'Index' || $t eq 'SubTransactionId')
+		{
+			print $off "\tWRITE_UINT_FIELD($f);\n";
+			print $rff "\tREAD_UINT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'uint64')
+		{
+			print $off "\tWRITE_UINT64_FIELD($f);\n";
+			print $rff "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Oid')
+		{
+			print $off "\tWRITE_OID_FIELD($f);\n";
+			print $rff "\tREAD_OID_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'long')
+		{
+			print $off "\tWRITE_LONG_FIELD($f);\n";
+			print $rff "\tREAD_LONG_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char')
+		{
+			print $off "\tWRITE_CHAR_FIELD($f);\n";
+			print $rff "\tREAD_CHAR_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'double')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.6f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cardinality')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.0f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Cost')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.2f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'QualCost')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f.startup, \"%.2f\");\n";
+			print $off "\tWRITE_FLOAT_FIELD($f.per_tuple, \"%.2f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read;
+			print $rff "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read;
+		}
+		elsif ($t eq 'Selectivity')
+		{
+			print $off "\tWRITE_FLOAT_FIELD($f, \"%.4f\");\n";
+			print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $off "\tWRITE_STRING_FIELD($f);\n";
+			print $rff "\tREAD_STRING_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'Bitmapset*' || $t eq 'Relids')
+		{
+			print $off "\tWRITE_BITMAPSET_FIELD($f);\n";
+			print $rff "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read;
+		}
+		elsif (elem $t, @enum_types)
+		{
+			print $off "\tWRITE_ENUM_FIELD($f, $t);\n";
+			print $rff "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read;
+		}
+		# arrays
+		elsif ($t =~ /(\w+)(\*|\[)/ and elem $1, @scalar_types)
+		{
+			my $tt = uc $1;
+			my $array_size_field;
+			foreach my $a (@a)
+			{
+				if ($a =~ /^array_size.([\w.]+)/)
+				{
+					$array_size_field = $1;
+					last;
+				}
+			}
+			if (!$array_size_field)
+			{
+				die "no array size defined for $n.$f of type $t";
+			}
+			if ($node_type_info{$n}->{field_types}{$array_size_field} eq 'List*')
+			{
+				print $off "\tWRITE_${tt}_ARRAY($f, list_length(node->$array_size_field));\n";
+				print $rff "\tREAD_${tt}_ARRAY($f, list_length(local_node->$array_size_field));\n" unless $no_read;
+			}
+			else
+			{
+				print $off "\tWRITE_${tt}_ARRAY($f, node->$array_size_field);\n";
+				print $rff "\tREAD_${tt}_ARRAY($f, local_node->$array_size_field);\n" unless $no_read;
+			}
+		}
+		# Special treatments of several Path node fields
+		elsif ($t eq 'RelOptInfo*' && elem 'write_only_relids', @a)
+		{
+			print $off "\tappendStringInfoString(str, \" :parent_relids \");\n".
+			  "\toutBitmapset(str, node->$f->relids);\n";
+		}
+		elsif ($t eq 'PathTarget*' && elem 'write_only_nondefault_pathtarget', @a)
+		{
+			(my $f2 = $f) =~ s/pathtarget/parent/;
+			print $off "\tif (node->$f != node->$f2->reltarget)\n".
+			  "\t\tWRITE_NODE_FIELD($f);\n";
+		}
+		elsif ($t eq 'ParamPathInfo*' && elem 'write_only_req_outer', @a)
+		{
+			print $off "\tif (node->$f)\n".
+			  "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n".
+			  "\telse\n".
+			  "\t\toutBitmapset(str, NULL);\n";
+		}
+		# node type
+		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
+		{
+			print $off "\tWRITE_NODE_FIELD($f);\n";
+			print $rff "\tREAD_NODE_FIELD($f);\n" unless $no_read;
+		}
+		elsif ($t eq 'struct CustomPathMethods*' ||	$t eq 'struct CustomScanMethods*')
+		{
+			print $off q{
+	/* CustomName is a key to lookup CustomScanMethods */
+	appendStringInfoString(str, " :methods ");
+	outToken(str, node->methods->CustomName);
+};
+			print $rff q!
+	{
+		/* Lookup CustomScanMethods by CustomName */
+		char	   *custom_name;
+		const CustomScanMethods *methods;
+		token = pg_strtok(&length); /* skip methods: */
+		token = pg_strtok(&length); /* CustomName */
+		custom_name = nullable_string(token, length);
+		methods = GetCustomScanMethods(custom_name, false);
+		local_node->methods = methods;
+	}
+! unless $no_read;
+		}
+		else
+		{
+			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+		}
+	}
+
+	print $off "}
+";
+	print $rff "
+\tREAD_DONE();
+}
+" unless $no_read;
+}
+
+close $off;
+close $rff;
+close $ofs;
+close $rfs;
+
+
+# now rename the temporary files to their final name
+foreach my $file (qw(nodetags.h copyfuncs.funcs.c copyfuncs.switch.c equalfuncs.funcs.c equalfuncs.switch.c outfuncs.funcs.c outfuncs.switch.c readfuncs.funcs.c readfuncs.switch.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4315c53080..37508af94d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -31,11 +31,10 @@
 
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/pathnodes.h"
-#include "nodes/plannodes.h"
+#include "nodes/bitmapset.h"
+#include "nodes/nodes.h"
+#include "nodes/pg_list.h"
 #include "utils/datum.h"
-#include "utils/rel.h"
 
 static void outChar(StringInfo str, char c);
 
@@ -306,6 +305,9 @@ outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
 }
 
 
+#include "outfuncs.funcs.c"
+
+#ifdef OBSOLETE
 /*
  *	Stuff from plannodes.h
  */
@@ -1155,6 +1157,7 @@ _outVar(StringInfo str, const Var *node)
 	WRITE_INT_FIELD(varattnosyn);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outConst(StringInfo str, const Const *node)
@@ -1176,6 +1179,7 @@ _outConst(StringInfo str, const Const *node)
 		outDatum(str, node->constvalue, node->constlen, node->constbyval);
 }
 
+#ifdef OBSOLETE
 static void
 _outParam(StringInfo str, const Param *node)
 {
@@ -1346,6 +1350,7 @@ _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 	WRITE_NODE_FIELD(args);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outBoolExpr(StringInfo str, const BoolExpr *node)
@@ -1374,6 +1379,7 @@ _outBoolExpr(StringInfo str, const BoolExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outSubLink(StringInfo str, const SubLink *node)
 {
@@ -2586,6 +2592,7 @@ _outIndexOptInfo(StringInfo str, const IndexOptInfo *node)
 	WRITE_BOOL_FIELD(hypothetical);
 	/* we don't bother with fields copied from the index AM's API struct */
 }
+#endif /* OBSOLETE */
 
 static void
 _outForeignKeyOptInfo(StringInfo str, const ForeignKeyOptInfo *node)
@@ -2613,6 +2620,7 @@ _outForeignKeyOptInfo(StringInfo str, const ForeignKeyOptInfo *node)
 		appendStringInfo(str, " %d", list_length(node->rinfos[i]));
 }
 
+#ifdef OBSOLETE
 static void
 _outStatisticExtInfo(StringInfo str, const StatisticExtInfo *node)
 {
@@ -2624,6 +2632,7 @@ _outStatisticExtInfo(StringInfo str, const StatisticExtInfo *node)
 	WRITE_CHAR_FIELD(kind);
 	WRITE_BITMAPSET_FIELD(keys);
 }
+#endif /* OBSOLETE */
 
 static void
 _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
@@ -2652,6 +2661,7 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
 	WRITE_UINT_FIELD(ec_max_security);
 }
 
+#ifdef OBSOLETE
 static void
 _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 {
@@ -2836,6 +2846,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node)
 	WRITE_NODE_FIELD(item);
 	WRITE_INT_FIELD(paramId);
 }
+#endif /*OBSOLETE*/
 
 /*****************************************************************************
  *
@@ -2858,6 +2869,7 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+#ifdef OBSOLETE
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -3191,6 +3203,7 @@ _outStatsElem(StringInfo str, const StatsElem *node)
 	WRITE_STRING_FIELD(name);
 	WRITE_NODE_FIELD(expr);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outQuery(StringInfo str, const Query *node)
@@ -3265,6 +3278,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_INT_FIELD(stmt_len);
 }
 
+#ifdef OBSOLETE
 static void
 _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
@@ -3430,6 +3444,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_NODE_FIELD(colCollations);
 	WRITE_NODE_FIELD(groupClauses);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
@@ -3510,6 +3525,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_NODE_FIELD(securityQuals);
 }
 
+#ifdef OBSOLETE
 static void
 _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
 {
@@ -3533,6 +3549,7 @@ _outTableSampleClause(StringInfo str, const TableSampleClause *node)
 	WRITE_NODE_FIELD(args);
 	WRITE_NODE_FIELD(repeatable);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outA_Expr(StringInfo str, const A_Expr *node)
@@ -3651,6 +3668,7 @@ _outBitString(StringInfo str, const BitString *node)
 	appendStringInfoString(str, node->bsval);
 }
 
+#ifdef OBSOLETE
 static void
 _outColumnRef(StringInfo str, const ColumnRef *node)
 {
@@ -3682,6 +3700,7 @@ _outRawStmt(StringInfo str, const RawStmt *node)
 	WRITE_LOCATION_FIELD(stmt_location);
 	WRITE_INT_FIELD(stmt_len);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outA_Const(StringInfo str, const A_Const *node)
@@ -3698,6 +3717,7 @@ _outA_Const(StringInfo str, const A_Const *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+#ifdef OBSOLETE
 static void
 _outA_Star(StringInfo str, const A_Star *node)
 {
@@ -3842,6 +3862,7 @@ _outRangeTableFuncCol(StringInfo str, const RangeTableFuncCol *node)
 	WRITE_NODE_FIELD(coldefexpr);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 static void
 _outConstraint(StringInfo str, const Constraint *node)
@@ -3964,6 +3985,7 @@ _outConstraint(StringInfo str, const Constraint *node)
 	}
 }
 
+#ifdef OBSOLETE
 static void
 _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 {
@@ -4024,6 +4046,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 	WRITE_LOCATION_FIELD(location);
 }
+#endif /*OBSOLETE*/
 
 /*
  * outNode -
@@ -4055,6 +4078,8 @@ outNode(StringInfo str, const void *obj)
 		appendStringInfoChar(str, '{');
 		switch (nodeTag(obj))
 		{
+#include "outfuncs.switch.c"
+#ifdef OBSOLETE
 			case T_PlannedStmt:
 				_outPlannedStmt(str, obj);
 				break;
@@ -4766,6 +4791,7 @@ outNode(StringInfo str, const void *obj)
 			case T_JsonTableSibling:
 				_outJsonTableSibling(str, obj);
 				break;
+#endif /*OBSOLETE*/
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6a05b69415..f427aa05ec 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -33,9 +33,7 @@
 #include <math.h>
 
 #include "miscadmin.h"
-#include "nodes/extensible.h"
-#include "nodes/parsenodes.h"
-#include "nodes/plannodes.h"
+#include "nodes/bitmapset.h"
 #include "nodes/readfuncs.h"
 
 
@@ -238,6 +236,8 @@ readBitmapset(void)
 	return _readBitmapset();
 }
 
+#include "readfuncs.funcs.c"
+
 /*
  * _readQuery
  */
@@ -291,6 +291,7 @@ _readQuery(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readNotifyStmt
  */
@@ -629,6 +630,7 @@ _readVar(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readConst
@@ -655,6 +657,7 @@ _readConst(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readParam
  */
@@ -880,6 +883,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readBoolExpr
@@ -907,6 +911,7 @@ _readBoolExpr(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readSubLink
  */
@@ -1649,6 +1654,7 @@ _readAppendRelInfo(void)
 /*
  *	Stuff from parsenodes.h.
  */
+#endif /*OBSOLETE*/
 
 /*
  * _readRangeTblEntry
@@ -1744,6 +1750,7 @@ _readRangeTblEntry(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readRangeTblFunction
  */
@@ -2872,6 +2879,7 @@ _readAlternativeSubPlan(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * _readExtensibleNode
@@ -2903,6 +2911,7 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+#ifdef OBSOLETE
 /*
  * _readPartitionBoundSpec
  */
@@ -2937,6 +2946,7 @@ _readPartitionRangeDatum(void)
 
 	READ_DONE();
 }
+#endif /*OBSOLETE*/
 
 /*
  * parseNodeString
@@ -2961,7 +2971,11 @@ parseNodeString(void)
 #define MATCH(tokname, namelen) \
 	(length == namelen && memcmp(token, tokname, namelen) == 0)
 
-	if (MATCH("QUERY", 5))
+	if (false)
+		;
+#include "readfuncs.switch.c"
+#ifdef OBSOLETE
+	else if (MATCH("QUERY", 5))
 		return_value = _readQuery();
 	else if (MATCH("WITHCHECKOPTION", 15))
 		return_value = _readWithCheckOption();
@@ -3235,6 +3249,7 @@ parseNodeString(void)
 		return_value = _readJsonTableParent();
 	else if (MATCH("JSONTABLESIBLING", 16))
 		return_value = _readJsonTableSibling();
+#endif /*OBSOLETE*/
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/include/Makefile b/src/include/Makefile
index 5f257a958c..17cfd268b8 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -81,6 +81,7 @@ clean:
 	rm -f parser/gram.h storage/lwlocknames.h utils/probes.h
 	rm -f catalog/schemapg.h catalog/system_fk_info.h
 	rm -f catalog/pg_*_d.h catalog/header-stamp
+	rm -f nodes/nodetags.h nodes/header-stamp
 
 distclean maintainer-clean: clean
 	rm -f pg_config.h pg_config_ext.h pg_config_os.h stamp-h stamp-ext-h
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 6306bb6fc6..37c11522ee 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -235,14 +235,14 @@ extern PGDLLIMPORT const TupleTableSlotOps TTSOpsBufferHeapTuple;
  * Tuple table slot implementations.
  */
 
-typedef struct VirtualTupleTableSlot
+typedef struct VirtualTupleTableSlot pg_node_attr(abstract)
 {
 	TupleTableSlot base;
 
 	char	   *data;			/* data for materialized slots */
 } VirtualTupleTableSlot;
 
-typedef struct HeapTupleTableSlot
+typedef struct HeapTupleTableSlot pg_node_attr(abstract)
 {
 	TupleTableSlot base;
 
@@ -254,7 +254,7 @@ typedef struct HeapTupleTableSlot
 } HeapTupleTableSlot;
 
 /* heap tuple residing in a buffer */
-typedef struct BufferHeapTupleTableSlot
+typedef struct BufferHeapTupleTableSlot pg_node_attr(abstract)
 {
 	HeapTupleTableSlot base;
 
@@ -267,7 +267,7 @@ typedef struct BufferHeapTupleTableSlot
 	Buffer		buffer;			/* tuple's buffer, or InvalidBuffer */
 } BufferHeapTupleTableSlot;
 
-typedef struct MinimalTupleTableSlot
+typedef struct MinimalTupleTableSlot pg_node_attr(abstract)
 {
 	TupleTableSlot base;
 
diff --git a/src/include/nodes/.gitignore b/src/include/nodes/.gitignore
new file mode 100644
index 0000000000..99fb1d3787
--- /dev/null
+++ b/src/include/nodes/.gitignore
@@ -0,0 +1,2 @@
+/nodetags.h
+/header-stamp
diff --git a/src/include/nodes/extensible.h b/src/include/nodes/extensible.h
index 6244c8d961..fab5bf690b 100644
--- a/src/include/nodes/extensible.h
+++ b/src/include/nodes/extensible.h
@@ -29,7 +29,7 @@
  * specific type of node.  extnodename can be looked up to find the
  * ExtensibleNodeMethods for this node type.
  */
-typedef struct ExtensibleNode
+typedef struct ExtensibleNode pg_node_attr(custom_copy_equal, custom_read_write)
 {
 	NodeTag		type;
 	const char *extnodename;	/* identifier of ExtensibleNodeMethods */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ce1fc4deb..d77aad6473 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -27,6 +27,8 @@ typedef enum NodeTag
 {
 	T_Invalid = 0,
 
+#include "nodes/nodetags.h"
+#ifdef OBSOLETE
 	/*
 	 * TAGS FOR EXECUTOR NODES (execnodes.h)
 	 */
@@ -563,8 +565,58 @@ typedef enum NodeTag
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
 	T_SupportRequestIndexCondition, /* in nodes/supportnodes.h */
 	T_SupportRequestWFuncMonotonic	/* in nodes/supportnodes.h */
+#endif /*OBSOLETE*/
 } NodeTag;
 
+/*
+ * pg_node_attr() - Used in node definitions to set extra information for
+ * gen_node_support.pl
+ *
+ * Attributes can be attached to a node as a whole (the attribute
+ * specification must be on the same line as "struct") or to a specific field
+ * (must be at the end of the line).  The argument is a comma-separated list
+ * of attributes.  Unrecognized attributes cause an error.
+ *
+ * Valid node attributes:
+ *
+ * - abstract: Abstract types are types that cannot be instantiated but that
+ *   can be supertypes of other types.  We track their fields, so that
+ *   subtypes can use them, but we don't emit a node tag, so you can't
+ *   instantiate them.
+ *
+ * - custom_copy_equal: Has custom implementations in copyfuncs.c and
+ *   equalfuncs.c.
+ *
+ * - custom_read_write: Has custom implementations in outfuncs.c and
+ *   readfuncs.c.
+ *
+ * - no_copy_equal: Does not support copyObject() and equal() at all.
+ *
+ * - no_read: Does not support nodeRead() at all.
+ *
+ * - special_read_write: Has special treatment in outNode() and nodeRead().
+ *
+ * Valid node field attributes:
+ *
+ * - array_size(OTHERFIELD): This field is a dynamically allocated array with
+ *   size indicated by the mentioned other field.  The other field is either a
+ *   scalar or a list, in which case the length of the list is used.
+ *
+ * - copy_ignore: Ignore the field for copy.
+ *
+ * - equal_ignore: Ignore the field for equality.
+ *
+ * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
+ *   (Otherwise, compare normally.)
+ *
+ * - read_write_ignore: Ignore the field for read/write.
+ *
+ * - write_only_relids, write_only_nondefault_pathtarget, write_only_req_outer:
+ *   Special handling for Path struct; see there.
+ *
+ */
+#define pg_node_attr(...)
+
 /*
  * The first field of a node of any type is guaranteed to be the NodeTag.
  * Hence the type of any node can be gotten by casting it to Node. Declaring
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f93d866548..fb026c6b1f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -115,7 +115,7 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
  *	  Planning converts a Query tree into a Plan tree headed by a PlannedStmt
  *	  node --- the Query structure is not used by the executor.
  */
-typedef struct Query
+typedef struct Query pg_node_attr(custom_read_write)
 {
 	NodeTag		type;
 
@@ -123,8 +123,11 @@ typedef struct Query
 
 	QuerySource querySource;	/* where did I come from? */
 
-	/* query identifier (can be set by plugins) */
-	uint64		queryId;
+	/*
+	 * query identifier (can be set by plugins); ignored for equal, might not
+	 * be set
+	 */
+	uint64		queryId pg_node_attr(equal_ignore);
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
@@ -286,7 +289,7 @@ typedef enum A_Expr_Kind
 	AEXPR_NOT_BETWEEN_SYM		/* name must be "NOT BETWEEN SYMMETRIC" */
 } A_Expr_Kind;
 
-typedef struct A_Expr
+typedef struct A_Expr pg_node_attr(custom_read_write, no_read)
 {
 	NodeTag		type;
 	A_Expr_Kind kind;			/* see above */
@@ -299,7 +302,7 @@ typedef struct A_Expr
 /*
  * A_Const - a literal constant
  */
-typedef struct A_Const
+typedef struct A_Const pg_node_attr(custom_copy_equal, custom_read_write, no_read)
 {
 	NodeTag		type;
 
@@ -398,7 +401,7 @@ typedef struct FuncCall
  * This can appear within ColumnRef.fields, A_Indirection.indirection, and
  * ResTarget.indirection lists.
  */
-typedef struct A_Star
+typedef struct A_Star pg_node_attr(no_read)
 {
 	NodeTag		type;
 } A_Star;
@@ -1010,7 +1013,7 @@ typedef enum RTEKind
 								 * present during parsing or rewriting */
 } RTEKind;
 
-typedef struct RangeTblEntry
+typedef struct RangeTblEntry pg_node_attr(custom_read_write)
 {
 	NodeTag		type;
 
@@ -2606,7 +2609,7 @@ typedef enum ConstrType			/* types of constraints */
 #define FKCONSTR_MATCH_PARTIAL		'p'
 #define FKCONSTR_MATCH_SIMPLE		's'
 
-typedef struct Constraint
+typedef struct Constraint pg_node_attr(custom_read_write, no_read)
 {
 	NodeTag		type;
 	ConstrType	contype;		/* see above */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b88cfb8dc0..4212610d5e 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -3,6 +3,8 @@
  * pathnodes.h
  *	  Definitions for planner's internal data structures, especially Paths.
  *
+ * We don't support copying RelOptInfo, IndexOptInfo, or Path nodes.
+ * There are some subsidiary structs that are useful to copy, though.
  *
  * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -85,17 +87,20 @@ typedef enum UpperRelationKind
  * PlannerGlobal holds state for an entire planner invocation; this state
  * is shared across all levels of sub-Queries that exist in the command being
  * planned.
+ *
+ * Not all fields are printed.  (In some cases, there is no print support for
+ * the field type.)
  *----------
  */
-typedef struct PlannerGlobal
+typedef struct PlannerGlobal pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
-	ParamListInfo boundParams;	/* Param values provided to planner() */
+	ParamListInfo boundParams pg_node_attr(read_write_ignore);	/* Param values provided to planner() */
 
 	List	   *subplans;		/* Plans for SubPlan nodes */
 
-	List	   *subroots;		/* PlannerInfos for SubPlan nodes */
+	List	   *subroots pg_node_attr(read_write_ignore);		/* PlannerInfos for SubPlan nodes */
 
 	Bitmapset  *rewindPlanIDs;	/* indices of subplans that require REWIND */
 
@@ -129,7 +134,7 @@ typedef struct PlannerGlobal
 
 	char		maxParallelHazard;	/* worst PROPARALLEL hazard level */
 
-	PartitionDirectory partition_directory; /* partition descriptors */
+	PartitionDirectory partition_directory pg_node_attr(read_write_ignore); /* partition descriptors */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -148,6 +153,9 @@ typedef struct PlannerGlobal
  *
  * For reasons explained in optimizer/optimizer.h, we define the typedef
  * either here or in that header, whichever is read first.
+ *
+ * Not all fields are printed.  (In some cases, there is no print support for
+ * the field type.)
  *----------
  */
 #ifndef HAVE_PLANNERINFO_TYPEDEF
@@ -155,7 +163,7 @@ typedef struct PlannerInfo PlannerInfo;
 #define HAVE_PLANNERINFO_TYPEDEF 1
 #endif
 
-struct PlannerInfo
+struct PlannerInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -165,7 +173,7 @@ struct PlannerInfo
 
 	Index		query_level;	/* 1 at the outermost Query */
 
-	PlannerInfo *parent_root;	/* NULL at outermost Query */
+	PlannerInfo *parent_root pg_node_attr(read_write_ignore);	/* NULL at outermost Query */
 
 	/*
 	 * plan_params contains the expressions that this query level needs to
@@ -183,15 +191,15 @@ struct PlannerInfo
 	 * does not correspond to a base relation, such as a join RTE or an
 	 * unreferenced view RTE; or if the RelOptInfo hasn't been made yet.
 	 */
-	struct RelOptInfo **simple_rel_array;	/* All 1-rel RelOptInfos */
-	int			simple_rel_array_size;	/* allocated size of array */
+	struct RelOptInfo **simple_rel_array pg_node_attr(read_write_ignore);	/* All 1-rel RelOptInfos */
+	int			simple_rel_array_size pg_node_attr(read_write_ignore);	/* allocated size of array */
 
 	/*
 	 * simple_rte_array is the same length as simple_rel_array and holds
 	 * pointers to the associated rangetable entries.  Using this is a shade
 	 * faster than using rt_fetch(), mostly due to fewer indirections.
 	 */
-	RangeTblEntry **simple_rte_array;	/* rangetable as an array */
+	RangeTblEntry **simple_rte_array pg_node_attr(read_write_ignore);	/* rangetable as an array */
 
 	/*
 	 * append_rel_array is the same length as the above arrays, and holds
@@ -199,7 +207,7 @@ struct PlannerInfo
 	 * child_relid, or NULL if the rel is not an appendrel child.  The array
 	 * itself is not allocated if append_rel_list is empty.
 	 */
-	struct AppendRelInfo **append_rel_array;
+	struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore);
 
 	/*
 	 * all_baserels is a Relids set of all base relids (but not "other"
@@ -227,7 +235,7 @@ struct PlannerInfo
 	 * GEQO.
 	 */
 	List	   *join_rel_list;
-	struct HTAB *join_rel_hash;
+	struct HTAB *join_rel_hash pg_node_attr(read_write_ignore);
 
 	/*
 	 * When doing a dynamic-programming-style join search, join_rel_level[k]
@@ -236,7 +244,7 @@ struct PlannerInfo
 	 * automatically added to the join_rel_level[join_cur_level] list.
 	 * join_rel_level is NULL if not in use.
 	 */
-	List	  **join_rel_level; /* lists of join-relation RelOptInfos */
+	List	  **join_rel_level pg_node_attr(read_write_ignore); /* lists of join-relation RelOptInfos */
 	int			join_cur_level; /* index of list being extended */
 
 	List	   *init_plans;		/* init SubPlans for query */
@@ -299,16 +307,16 @@ struct PlannerInfo
 	List	   *distinct_pathkeys;	/* distinctClause pathkeys, if any */
 	List	   *sort_pathkeys;	/* sortClause pathkeys, if any */
 
-	List	   *part_schemes;	/* Canonicalised partition schemes used in the
+	List	   *part_schemes pg_node_attr(read_write_ignore);	/* Canonicalised partition schemes used in the
 								 * query. */
 
-	List	   *initial_rels;	/* RelOptInfos we are now trying to join */
+	List	   *initial_rels pg_node_attr(read_write_ignore);	/* RelOptInfos we are now trying to join */
 
 	/* Use fetch_upper_rel() to get any particular upper rel */
-	List	   *upper_rels[UPPERREL_FINAL + 1]; /* upper-rel RelOptInfos */
+	List	   *upper_rels[UPPERREL_FINAL + 1] pg_node_attr(read_write_ignore); /* upper-rel RelOptInfos */
 
 	/* Result tlists chosen by grouping_planner for upper-stage processing */
-	struct PathTarget *upper_targets[UPPERREL_FINAL + 1];
+	struct PathTarget *upper_targets[UPPERREL_FINAL + 1] pg_node_attr(read_write_ignore);
 
 	/*
 	 * The fully-processed targetlist is kept here.  It differs from
@@ -333,12 +341,12 @@ struct PlannerInfo
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
 	/* for GroupingFunc fixup */
-	AttrNumber *grouping_map;
+	AttrNumber *grouping_map pg_node_attr(array_size(update_colnos), read_write_ignore);
 	/* List of MinMaxAggInfos */
 	List	   *minmax_aggs;
 
 	/* context holding PlannerInfo */
-	MemoryContext planner_cxt;
+	MemoryContext planner_cxt pg_node_attr(read_write_ignore);
 
 	Cardinality total_table_pages;	/* # of pages in all non-dummy tables of
 									 * query */
@@ -360,11 +368,11 @@ struct PlannerInfo
 	/*
 	 * Information about aggregates. Filled by preprocess_aggrefs().
 	 */
-	List	   *agginfos;		/* AggInfo structs */
-	List	   *aggtransinfos;	/* AggTransInfo structs */
-	int			numOrderedAggs; /* number w/ DISTINCT/ORDER BY/WITHIN GROUP */
-	bool		hasNonPartialAggs;	/* does any agg not support partial mode? */
-	bool		hasNonSerialAggs;	/* is any partial agg non-serializable? */
+	List	   *agginfos pg_node_attr(read_write_ignore);		/* AggInfo structs */
+	List	   *aggtransinfos pg_node_attr(read_write_ignore);	/* AggTransInfo structs */
+	int			numOrderedAggs pg_node_attr(read_write_ignore); /* number w/ DISTINCT/ORDER BY/WITHIN GROUP */
+	bool		hasNonPartialAggs pg_node_attr(read_write_ignore);	/* does any agg not support partial mode? */
+	bool		hasNonSerialAggs pg_node_attr(read_write_ignore);	/* is any partial agg non-serializable? */
 
 	/* These fields are used only when hasRecursion is true: */
 	int			wt_param_id;	/* PARAM_EXEC ID for the work table */
@@ -378,11 +386,11 @@ struct PlannerInfo
 	 * These fields are workspace for setrefs.c.  Each is an array
 	 * corresponding to glob->subplans.
 	 */
-	bool	   *isAltSubplan;
-	bool	   *isUsedSubplan;
+	bool	   *isAltSubplan pg_node_attr(read_write_ignore);
+	bool	   *isUsedSubplan pg_node_attr(read_write_ignore);
 
 	/* optional private data for join_search_hook, e.g., GEQO */
-	void	   *join_search_private;
+	void	   *join_search_private pg_node_attr(read_write_ignore);
 
 	/* Does this query modify any partition key columns? */
 	bool		partColsUpdated;
@@ -639,6 +647,9 @@ typedef struct PartitionSchemeData *PartitionScheme;
  * Furthermore, FULL JOINs add extra nullable_partexprs expressions
  * corresponding to COALESCE expressions of the left and right join columns,
  * to simplify matching join clauses to those lists.
+ *
+ * Not all fields are printed.  (In some cases, there is no print support for
+ * the field type.)
  *----------
  */
 
@@ -680,7 +691,7 @@ typedef enum RelOptKind
 	 (rel)->reloptkind == RELOPT_OTHER_JOINREL || \
 	 (rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
 
-typedef struct RelOptInfo
+typedef struct RelOptInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -747,9 +758,9 @@ typedef struct RelOptInfo
 	/* largest attrno of rel */
 	AttrNumber	max_attr;
 	/* array indexed [min_attr .. max_attr] */
-	Relids	   *attr_needed;
+	Relids	   *attr_needed pg_node_attr(read_write_ignore);
 	/* array indexed [min_attr .. max_attr] */
-	int32	   *attr_widths;
+	int32	   *attr_widths pg_node_attr(read_write_ignore);
 	/* LATERAL Vars and PHVs referenced by rel */
 	List	   *lateral_vars;
 	/* rels that reference me laterally */
@@ -784,16 +795,18 @@ typedef struct RelOptInfo
 	/* join is only valid for current user */
 	bool		useridiscurrent;
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
-	struct FdwRoutine *fdwroutine;
-	void	   *fdw_private;
+	struct FdwRoutine *fdwroutine pg_node_attr(read_write_ignore);
+	void	   *fdw_private pg_node_attr(read_write_ignore);
 
 	/*
 	 * cache space for remembering if we have proven this relation unique
+	 *
+	 * can't print unique_for_rels/non_unique_for_rels; BMSes aren't Nodes
 	 */
 	/* known unique for these other relid set(s) */
-	List	   *unique_for_rels;
+	List	   *unique_for_rels pg_node_attr(read_write_ignore);
 	/* known not unique for these set(s) */
-	List	   *non_unique_for_rels;
+	List	   *non_unique_for_rels pg_node_attr(read_write_ignore);
 
 	/*
 	 * used by various scans and joins:
@@ -821,24 +834,24 @@ typedef struct RelOptInfo
 	 * used for partitioned relations:
 	 */
 	/* Partitioning scheme */
-	PartitionScheme part_scheme;
+	PartitionScheme part_scheme pg_node_attr(read_write_ignore);
 
 	/*
 	 * Number of partitions; -1 if not yet set; in case of a join relation 0
 	 * means it's considered unpartitioned
 	 */
-	int			nparts;
+	int			nparts pg_node_attr(read_write_ignore);
 	/* Partition bounds */
-	struct PartitionBoundInfoData *boundinfo;
+	struct PartitionBoundInfoData *boundinfo pg_node_attr(read_write_ignore);
 	/* True if partition bounds were created by partition_bounds_merge() */
 	bool		partbounds_merged;
 	/* Partition constraint, if not the root */
-	List	   *partition_qual;
+	List	   *partition_qual pg_node_attr(read_write_ignore);
 
 	/*
 	 * Array of RelOptInfos of partitions, stored in the same order as bounds
 	 */
-	struct RelOptInfo **part_rels;
+	struct RelOptInfo **part_rels pg_node_attr(read_write_ignore);
 
 	/*
 	 * Bitmap with members acting as indexes into the part_rels[] array to
@@ -848,9 +861,9 @@ typedef struct RelOptInfo
 	/* Relids set of all partition relids */
 	Relids		all_partrels;
 	/* Non-nullable partition key expressions */
-	List	  **partexprs;
+	List	  **partexprs pg_node_attr(read_write_ignore);
 	/* Nullable partition key expressions */
-	List	  **nullable_partexprs;
+	List	  **nullable_partexprs pg_node_attr(read_write_ignore);
 } RelOptInfo;
 
 /*
@@ -909,7 +922,7 @@ typedef struct IndexOptInfo IndexOptInfo;
 #define HAVE_INDEXOPTINFO_TYPEDEF 1
 #endif
 
-struct IndexOptInfo
+struct IndexOptInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -917,8 +930,8 @@ struct IndexOptInfo
 	Oid			indexoid;
 	/* tablespace of index (not table) */
 	Oid			reltablespace;
-	/* back-link to index's table */
-	RelOptInfo *rel;
+	/* back-link to index's table; don't print, else infinite recursion */
+	RelOptInfo *rel pg_node_attr(read_write_ignore);
 
 	/*
 	 * index-size statistics (from pg_class and elsewhere)
@@ -938,31 +951,39 @@ struct IndexOptInfo
 	/* number of key columns in index */
 	int			nkeycolumns;
 
+	/*
+	 * array fields aren't really worth the trouble to print
+	 */
+
 	/*
 	 * column numbers of index's attributes both key and included columns, or
 	 * 0
 	 */
-	int		   *indexkeys;
+	int		   *indexkeys pg_node_attr(read_write_ignore);
 	/* OIDs of collations of index columns */
-	Oid		   *indexcollations;
+	Oid		   *indexcollations pg_node_attr(read_write_ignore);
 	/* OIDs of operator families for columns */
-	Oid		   *opfamily;
+	Oid		   *opfamily pg_node_attr(read_write_ignore);
 	/* OIDs of opclass declared input data types */
-	Oid		   *opcintype;
+	Oid		   *opcintype pg_node_attr(read_write_ignore);
 	/* OIDs of btree opfamilies, if orderable */
-	Oid		   *sortopfamily;
+	Oid		   *sortopfamily pg_node_attr(read_write_ignore);
 	/* is sort order descending? */
-	bool	   *reverse_sort;
+	bool	   *reverse_sort pg_node_attr(read_write_ignore);
 	/* do NULLs come first in the sort order? */
-	bool	   *nulls_first;
+	bool	   *nulls_first pg_node_attr(read_write_ignore);
 	/* opclass-specific options for columns */
-	bytea	  **opclassoptions;
+	bytea	  **opclassoptions pg_node_attr(read_write_ignore);
 	/* which index cols can be returned in an index-only scan? */
-	bool	   *canreturn;
+	bool	   *canreturn pg_node_attr(read_write_ignore);
 	/* OID of the access method (in pg_am) */
 	Oid			relam;
-	/* expressions for non-simple index columns */
-	List	   *indexprs;
+
+	/*
+	 * expressions for non-simple index columns; redundant to print since we
+	 * print indextlist
+	 */
+	List	   *indexprs pg_node_attr(read_write_ignore);
 	/* predicate if a partial index, else NIL */
 	List	   *indpred;
 
@@ -989,17 +1010,17 @@ struct IndexOptInfo
 	 * Remaining fields are copied from the index AM's API struct
 	 * (IndexAmRoutine)
 	 */
-	bool		amcanorderbyop;
-	bool		amoptionalkey;
-	bool		amsearcharray;
-	bool		amsearchnulls;
+	bool		amcanorderbyop pg_node_attr(read_write_ignore);
+	bool		amoptionalkey pg_node_attr(read_write_ignore);
+	bool		amsearcharray pg_node_attr(read_write_ignore);
+	bool		amsearchnulls pg_node_attr(read_write_ignore);
 	/* does AM have amgettuple interface? */
-	bool		amhasgettuple;
+	bool		amhasgettuple pg_node_attr(read_write_ignore);
 	/* does AM have amgetbitmap interface? */
-	bool		amhasgetbitmap;
-	bool		amcanparallel;
+	bool		amhasgetbitmap pg_node_attr(read_write_ignore);
+	bool		amcanparallel pg_node_attr(read_write_ignore);
 	/* does AM have ammarkpos interface? */
-	bool		amcanmarkpos;
+	bool		amcanmarkpos pg_node_attr(read_write_ignore);
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
 };
@@ -1012,7 +1033,7 @@ struct IndexOptInfo
  * INDEX_MAX_KEYS columns in a foreign key constraint.  Each array has
  * nkeys valid entries.
  */
-typedef struct ForeignKeyOptInfo
+typedef struct ForeignKeyOptInfo pg_node_attr(custom_read_write, no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -1027,11 +1048,11 @@ typedef struct ForeignKeyOptInfo
 	/* number of columns in the foreign key */
 	int			nkeys;
 	/* cols in referencing table */
-	AttrNumber	conkey[INDEX_MAX_KEYS];
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
 	/* cols in referenced table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
 	/* PK = FK operator OIDs */
-	Oid			conpfeqop[INDEX_MAX_KEYS];
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
 
 	/*
 	 * Derived info about whether FK's equality conditions match the query:
@@ -1060,7 +1081,7 @@ typedef struct ForeignKeyOptInfo
  * Each pg_statistic_ext row is represented by one or more nodes of this
  * type, or even zero if ANALYZE has not computed them.
  */
-typedef struct StatisticExtInfo
+typedef struct StatisticExtInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -1068,10 +1089,13 @@ typedef struct StatisticExtInfo
 	Oid			statOid;
 
 	/* includes child relations */
-	bool		inherit;
+	bool		inherit pg_node_attr(read_write_ignore);
 
-	/* back-link to statistic's table */
-	RelOptInfo *rel;
+	/*
+	 * back-link to statistic's table; don't print, infinite recursion on plan
+	 * tree dump
+	 */
+	RelOptInfo *rel pg_node_attr(read_write_ignore);
 
 	/* statistics kind of this entry */
 	char		kind;
@@ -1123,7 +1147,7 @@ typedef struct StatisticExtInfo
  * NB: if ec_merged isn't NULL, this class has been merged into another, and
  * should be ignored in favor of using the pointed-to class.
  */
-typedef struct EquivalenceClass
+typedef struct EquivalenceClass pg_node_attr(custom_read_write, no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -1173,7 +1197,7 @@ typedef struct EquivalenceClass
  * anyarray_ops would never work without this.  Use em_datatype when
  * looking up a specific btree operator to work with this expression.
  */
-typedef struct EquivalenceMember
+typedef struct EquivalenceMember pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -1257,7 +1281,7 @@ typedef enum VolatileFunctionStatus
  * deal with sort/group refnos when needed with less expense than including
  * TargetEntry nodes in the exprs list.
  */
-typedef struct PathTarget
+typedef struct PathTarget pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -1265,7 +1289,7 @@ typedef struct PathTarget
 	List	   *exprs;
 
 	/* corresponding sort/group refnos, or 0 */
-	Index	   *sortgrouprefs;
+	Index	   *sortgrouprefs pg_node_attr(array_size(exprs));
 
 	/* cost of evaluating the expressions */
 	QualCost	cost;
@@ -1296,7 +1320,7 @@ typedef struct PathTarget
  * on how the join is formed.  The relevant clauses will appear in each
  * parameterized join path's joinrestrictinfo list, instead.
  */
-typedef struct ParamPathInfo
+typedef struct ParamPathInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -1334,22 +1358,41 @@ typedef struct ParamPathInfo
  *
  * "pathkeys" is a List of PathKey nodes (see above), describing the sort
  * ordering of the path's output rows.
+ *
+ * We do not support copying Path trees, mainly because the circular linkages
+ * between RelOptInfo and Path nodes can't be handled easily in a simple
+ * depth-first traversal.  We also don't have read support at the moment.
  */
-typedef struct Path
+typedef struct Path pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
 	/* tag identifying scan/join method */
 	NodeTag		pathtype;
 
-	/* the relation this path can build */
-	RelOptInfo *parent;
+	/*
+	 * the relation this path can build
+	 *
+	 * We do NOT print the parent, else we'd be in infinite recursion.  We can
+	 * print the parent's relids for identification purposes, though.
+	 */
+	RelOptInfo *parent pg_node_attr(write_only_relids);
 
-	/* list of Vars/Exprs, cost, width */
-	PathTarget *pathtarget;
+	/*
+	 * list of Vars/Exprs, cost, width
+	 *
+	 * We print the pathtarget only if it's not the default one for the rel.
+	 */
+	PathTarget *pathtarget pg_node_attr(write_only_nondefault_pathtarget);
 
-	/* parameterization info, or NULL if none */
-	ParamPathInfo *param_info;
+	/*
+	 * parameterization info, or NULL if none
+	 *
+	 * We do not print the whole of param_info, since it's printed via
+	 * RelOptInfo; it's sufficient and less cluttering to print just the
+	 * required outer relids.
+	 */
+	ParamPathInfo *param_info pg_node_attr(write_only_req_outer);
 
 	/* engage parallel-aware logic? */
 	bool		parallel_aware;
@@ -1455,7 +1498,7 @@ typedef struct IndexPath
  * column, i.e. the indexcol values must form a nondecreasing sequence.
  * (The order of multiple clauses for the same index column is unspecified.)
  */
-typedef struct IndexClause
+typedef struct IndexClause pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 	struct RestrictInfo *rinfo; /* original restriction or join clause */
@@ -1750,7 +1793,7 @@ typedef struct GatherMergePath
  * All join-type paths share these fields.
  */
 
-typedef struct JoinPath
+typedef struct JoinPath pg_node_attr(abstract)
 {
 	Path		path;
 
@@ -1952,14 +1995,14 @@ typedef struct AggPath
  * Various annotations used for grouping sets in the planner.
  */
 
-typedef struct GroupingSetData
+typedef struct GroupingSetData pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 	List	   *set;			/* grouping set as list of sortgrouprefs */
 	Cardinality numGroups;		/* est. number of result groups */
 } GroupingSetData;
 
-typedef struct RollupData
+typedef struct RollupData pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 	List	   *groupClause;	/* applicable subset of parse->groupClause */
@@ -2224,6 +2267,12 @@ typedef struct LimitPath
  * apply only one.  We mark clauses of this kind by setting parent_ec to
  * point to the generating EquivalenceClass.  Multiple clauses with the same
  * parent_ec in the same join are redundant.
+ *
+ * Most fields are ignored for equality, since they may not be set yet, and
+ * should be derivable from the clause anyway.
+ *
+ * parent_ec, left_ec, right_ec are not printed, lest it lead to infinite
+ * recursion in plan tree dump.
  */
 
 typedef struct RestrictInfo
@@ -2240,22 +2289,22 @@ typedef struct RestrictInfo
 	bool		outerjoin_delayed;
 
 	/* see comment above */
-	bool		can_join;
+	bool		can_join pg_node_attr(equal_ignore);
 
 	/* see comment above */
-	bool		pseudoconstant;
+	bool		pseudoconstant pg_node_attr(equal_ignore);
 
 	/* true if known to contain no leaked Vars */
-	bool		leakproof;
+	bool		leakproof pg_node_attr(equal_ignore);
 
 	/* to indicate if clause contains any volatile functions. */
-	VolatileFunctionStatus has_volatile;
+	VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);
 
 	/* see comment above */
 	Index		security_level;
 
 	/* The set of relids (varnos) actually referenced in the clause: */
-	Relids		clause_relids;
+	Relids		clause_relids pg_node_attr(equal_ignore);
 
 	/* The set of relids required to evaluate the clause: */
 	Relids		required_relids;
@@ -2270,84 +2319,89 @@ typedef struct RestrictInfo
 	 * Relids in the left/right side of the clause.  These fields are set for
 	 * any binary opclause.
 	 */
-	Relids		left_relids;
-	Relids		right_relids;
+	Relids		left_relids pg_node_attr(equal_ignore);
+	Relids		right_relids pg_node_attr(equal_ignore);
 
 	/*
 	 * Modified clause with RestrictInfos.  This field is NULL unless clause
 	 * is an OR clause.
 	 */
-	Expr	   *orclause;
+	Expr	   *orclause pg_node_attr(equal_ignore);
 
 	/*
 	 * Generating EquivalenceClass.  This field is NULL unless clause is
 	 * potentially redundant.
 	 */
-	EquivalenceClass *parent_ec;
+	EquivalenceClass *parent_ec pg_node_attr(equal_ignore, read_write_ignore);
 
 	/*
 	 * cache space for cost and selectivity
 	 */
 
 	/* eval cost of clause; -1 if not yet set */
-	QualCost	eval_cost;
+	QualCost	eval_cost pg_node_attr(equal_ignore);
 
 	/*
 	 * selectivity for "normal" (JOIN_INNER) semantics; -1 if not yet set; >1
 	 * means a redundant clause
 	 */
-	Selectivity norm_selec;
+	Selectivity norm_selec pg_node_attr(equal_ignore);
 	/* selectivity for outer join semantics; -1 if not yet set */
-	Selectivity outer_selec;
+	Selectivity outer_selec pg_node_attr(equal_ignore);
 
 	/*
 	 * opfamilies containing clause operator; valid if clause is
 	 * mergejoinable, else NIL
 	 */
-	List	   *mergeopfamilies;
+	List	   *mergeopfamilies pg_node_attr(equal_ignore);
 
 	/*
 	 * cache space for mergeclause processing; NULL if not yet set
 	 */
 
 	/* EquivalenceClass containing lefthand */
-	EquivalenceClass *left_ec;
+	EquivalenceClass *left_ec pg_node_attr(equal_ignore, read_write_ignore);
 	/* EquivalenceClass containing righthand */
-	EquivalenceClass *right_ec;
+	EquivalenceClass *right_ec pg_node_attr(equal_ignore, read_write_ignore);
 	/* EquivalenceMember for lefthand */
-	EquivalenceMember *left_em;
+	EquivalenceMember *left_em pg_node_attr(equal_ignore);
 	/* EquivalenceMember for righthand */
-	EquivalenceMember *right_em;
-	/* list of MergeScanSelCache structs */
-	List	   *scansel_cache;
+	EquivalenceMember *right_em pg_node_attr(equal_ignore);
+
+	/*
+	 * List of MergeScanSelCache structs.  Those aren't Nodes, so hard to
+	 * copy.  Ignoring it will have the effect that copying will just reset
+	 * the cache.
+	 */
+	List	   *scansel_cache pg_node_attr(copy_ignore, equal_ignore);
 
 	/*
 	 * transient workspace for use while considering a specific join path; T =
 	 * outer var on left, F = on right
 	 */
-	bool		outer_is_left;
+	bool		outer_is_left pg_node_attr(equal_ignore);
 
 	/*
 	 * copy of clause operator; valid if clause is hashjoinable, else
 	 * InvalidOid
 	 */
-	Oid			hashjoinoperator;
+	Oid			hashjoinoperator pg_node_attr(equal_ignore);
 
 	/*
 	 * cache space for hashclause processing; -1 if not yet set
 	 */
 	/* avg bucketsize of left side */
-	Selectivity left_bucketsize;
+	Selectivity left_bucketsize pg_node_attr(equal_ignore);
 	/* avg bucketsize of right side */
-	Selectivity right_bucketsize;
+	Selectivity right_bucketsize pg_node_attr(equal_ignore);
 	/* left side's most common val's freq */
-	Selectivity left_mcvfreq;
+	Selectivity left_mcvfreq pg_node_attr(equal_ignore);
 	/* right side's most common val's freq */
-	Selectivity right_mcvfreq;
+	Selectivity right_mcvfreq pg_node_attr(equal_ignore);
 
 	/* hash equality operators used for memoize nodes, else InvalidOid */
-	Oid			left_hasheqoperator;
-	Oid			right_hasheqoperator;
+	Oid			left_hasheqoperator pg_node_attr(equal_ignore);
+	Oid			right_hasheqoperator pg_node_attr(equal_ignore);
 } RestrictInfo;
 
 /*
@@ -2397,6 +2451,17 @@ typedef struct MergeScanSelCache
  * Although the planner treats this as an expression node type, it is not
  * recognized by the parser or executor, so we declare it here rather than
  * in primnodes.h.
+ *
+ * We intentionally do not compare phexpr.  Two PlaceHolderVars with the
+ * same ID and levelsup should be considered equal even if the contained
+ * expressions have managed to mutate to different states.  This will
+ * happen during final plan construction when there are nested PHVs, since
+ * the inner PHV will get replaced by a Param in some copies of the outer
+ * PHV.  Another way in which it can happen is that initplan sublinks
+ * could get replaced by differently-numbered Params when sublink folding
+ * is done.  (The end result of such a situation would be some
+ * unreferenced initplans, which is annoying but not really a problem.) On
+ * the same reasoning, there is no need to examine phrels.
  */
 
 typedef struct PlaceHolderVar
@@ -2404,10 +2469,10 @@ typedef struct PlaceHolderVar
 	Expr		xpr;
 
 	/* the represented expression */
-	Expr	   *phexpr;
+	Expr	   *phexpr pg_node_attr(equal_ignore);
 
 	/* base relids syntactically within expr src */
-	Relids		phrels;
+	Relids		phrels pg_node_attr(equal_ignore);
 
 	/* ID for PHV (unique within planner run) */
 	Index		phid;
@@ -2572,7 +2637,7 @@ typedef struct AppendRelInfo
 	 * child column is dropped or doesn't exist in the parent.
 	 */
 	int			num_child_cols; /* length of array */
-	AttrNumber *parent_colnos;
+	AttrNumber *parent_colnos pg_node_attr(array_size(num_child_cols));
 
 	/*
 	 * We store the parent table's OID here for inheritance, or InvalidOid for
@@ -2600,7 +2665,7 @@ typedef struct AppendRelInfo
  * We add such a reference to root->processed_tlist when creating the entry,
  * and it propagates into the plan tree from there.
  */
-typedef struct RowIdentityVarInfo
+typedef struct RowIdentityVarInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -2643,7 +2708,10 @@ typedef struct PlaceHolderInfo
 	/* ID for PH (unique within planner run) */
 	Index		phid;
 
-	/* copy of PlaceHolderVar tree */
+	/*
+	 * copy of PlaceHolderVar tree (should be redundant for comparison, could
+	 * be ignored)
+	 */
 	PlaceHolderVar *ph_var;
 
 	/* lowest level we can evaluate value at */
@@ -2664,7 +2732,7 @@ typedef struct PlaceHolderInfo
  * function.  MinMaxAggPath contains a list of these, and if we accept that
  * path, the list is stored into root->minmax_aggs for use during setrefs.c.
  */
-typedef struct MinMaxAggInfo
+typedef struct MinMaxAggInfo pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
@@ -2677,8 +2745,11 @@ typedef struct MinMaxAggInfo
 	/* expression we are aggregating on */
 	Expr	   *target;
 
-	/* modified "root" for planning the subquery */
-	PlannerInfo *subroot;
+	/*
+	 * modified "root" for planning the subquery; not printed, too large, not
+	 * interesting enough
+	 */
+	PlannerInfo *subroot pg_node_attr(read_write_ignore);
 
 	/* access path for subquery */
 	Path	   *path;
@@ -2737,7 +2808,7 @@ typedef struct MinMaxAggInfo
  * Instead, we just record the assignment of the slot number by appending to
  * root->glob->paramExecTypes.
  */
-typedef struct PlannerParamItem
+typedef struct PlannerParamItem pg_node_attr(no_copy_equal)
 {
 	NodeTag		type;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d5c0ebe859..19b5ce2ec6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -286,16 +286,16 @@ typedef struct MergeAppend
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *sortColIdx;
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
 	/* OIDs of operators to sort them by */
-	Oid		   *sortOperators;
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));
 
 	/* OIDs of collations */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numCols));
 
 	/* NULLS FIRST/LAST directions */
-	bool	   *nullsFirst;
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));
 
 	/* Info for run-time subplan pruning; NULL if we're not doing that */
 	struct PartitionPruneInfo *part_prune_info;
@@ -322,11 +322,11 @@ typedef struct RecursiveUnion
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *dupColIdx;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *dupOperators;
-	Oid		   *dupCollations;
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 
 	/* estimated number of groups in input */
 	long		numGroups;
@@ -812,16 +812,16 @@ typedef struct MergeJoin
 	/* these are arrays, but have the same length as the mergeclauses list: */
 
 	/* per-clause OIDs of btree opfamilies */
-	Oid		   *mergeFamilies;
+	Oid		   *mergeFamilies pg_node_attr(array_size(mergeclauses));
 
 	/* per-clause OIDs of collations */
-	Oid		   *mergeCollations;
+	Oid		   *mergeCollations pg_node_attr(array_size(mergeclauses));
 
 	/* per-clause ordering (ASC or DESC) */
-	int		   *mergeStrategies;
+	int		   *mergeStrategies pg_node_attr(array_size(mergeclauses));
 
 	/* per-clause nulls ordering */
-	bool	   *mergeNullsFirst;
+	bool	   *mergeNullsFirst pg_node_attr(array_size(mergeclauses));
 } MergeJoin;
 
 /* ----------------
@@ -863,10 +863,10 @@ typedef struct Memoize
 	int			numKeys;
 
 	/* hash operators for each key */
-	Oid		   *hashOperators;
+	Oid		   *hashOperators pg_node_attr(array_size(numKeys));
 
 	/* collations for each key */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numKeys));
 
 	/* cache keys in the form of exprs containing parameters */
 	List	   *param_exprs;
@@ -905,16 +905,16 @@ typedef struct Sort
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *sortColIdx;
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
 	/* OIDs of operators to sort them by */
-	Oid		   *sortOperators;
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));
 
 	/* OIDs of collations */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numCols));
 
 	/* NULLS FIRST/LAST directions */
-	bool	   *nullsFirst;
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));
 } Sort;
 
 /* ----------------
@@ -941,11 +941,11 @@ typedef struct Group
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *grpColIdx;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *grpOperators;
-	Oid		   *grpCollations;
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 } Group;
 
 /* ---------------
@@ -976,11 +976,11 @@ typedef struct Agg
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *grpColIdx;
+	AttrNumber *grpColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *grpOperators;
-	Oid		   *grpCollations;
+	Oid		   *grpOperators pg_node_attr(array_size(numCols));
+	Oid		   *grpCollations pg_node_attr(array_size(numCols));
 
 	/* estimated number of groups in input */
 	long		numGroups;
@@ -1015,25 +1015,25 @@ typedef struct WindowAgg
 	int			partNumCols;
 
 	/* their indexes in the target list */
-	AttrNumber *partColIdx;
+	AttrNumber *partColIdx pg_node_attr(array_size(partNumCols));
 
 	/* equality operators for partition columns */
-	Oid		   *partOperators;
+	Oid		   *partOperators pg_node_attr(array_size(partNumCols));
 
 	/* collations for partition columns */
-	Oid		   *partCollations;
+	Oid		   *partCollations pg_node_attr(array_size(partNumCols));
 
 	/* number of columns in ordering clause */
 	int			ordNumCols;
 
 	/* their indexes in the target list */
-	AttrNumber *ordColIdx;
+	AttrNumber *ordColIdx pg_node_attr(array_size(ordNumCols));
 
 	/* equality operators for ordering columns */
-	Oid		   *ordOperators;
+	Oid		   *ordOperators pg_node_attr(array_size(ordNumCols));
 
 	/* collations for ordering columns */
-	Oid		   *ordCollations;
+	Oid		   *ordCollations pg_node_attr(array_size(ordNumCols));
 
 	/* frame_clause options, see WindowDef */
 	int			frameOptions;
@@ -1086,13 +1086,13 @@ typedef struct Unique
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *uniqColIdx;
+	AttrNumber *uniqColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *uniqOperators;
+	Oid		   *uniqOperators pg_node_attr(array_size(numCols));
 
 	/* collations for equality comparisons */
-	Oid		   *uniqCollations;
+	Oid		   *uniqCollations pg_node_attr(array_size(numCols));
 } Unique;
 
 /* ------------
@@ -1137,16 +1137,16 @@ typedef struct GatherMerge
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *sortColIdx;
+	AttrNumber *sortColIdx pg_node_attr(array_size(numCols));
 
 	/* OIDs of operators to sort them by */
-	Oid		   *sortOperators;
+	Oid		   *sortOperators pg_node_attr(array_size(numCols));
 
 	/* OIDs of collations */
-	Oid		   *collations;
+	Oid		   *collations pg_node_attr(array_size(numCols));
 
 	/* NULLS FIRST/LAST directions */
-	bool	   *nullsFirst;
+	bool	   *nullsFirst pg_node_attr(array_size(numCols));
 
 	/*
 	 * param id's of initplans which are referred at gather merge or one of
@@ -1197,11 +1197,11 @@ typedef struct SetOp
 	int			numCols;
 
 	/* their indexes in the target list */
-	AttrNumber *dupColIdx;
+	AttrNumber *dupColIdx pg_node_attr(array_size(numCols));
 
 	/* equality operators to compare with */
-	Oid		   *dupOperators;
-	Oid		   *dupCollations;
+	Oid		   *dupOperators pg_node_attr(array_size(numCols));
+	Oid		   *dupCollations pg_node_attr(array_size(numCols));
 
 	/* where is the flag column, if any */
 	AttrNumber	flagColIdx;
@@ -1253,13 +1253,13 @@ typedef struct Limit
 	int			uniqNumCols;
 
 	/* their indexes in the target list */
-	AttrNumber *uniqColIdx;
+	AttrNumber *uniqColIdx pg_node_attr(array_size(uniqNumCols));
 
 	/* equality operators to compare with */
-	Oid		   *uniqOperators;
+	Oid		   *uniqOperators pg_node_attr(array_size(uniqNumCols));
 
 	/* collations for equality comparisons */
-	Oid		   *uniqCollations;
+	Oid		   *uniqCollations pg_node_attr(array_size(uniqNumCols));
 } Limit;
 
 
@@ -1425,13 +1425,13 @@ typedef struct PartitionedRelPruneInfo
 	int			nparts;
 
 	/* subplan index by partition index, or -1 */
-	int		   *subplan_map;
+	int		   *subplan_map pg_node_attr(array_size(nparts));
 
 	/* subpart index by partition index, or -1 */
-	int		   *subpart_map;
+	int		   *subpart_map pg_node_attr(array_size(nparts));
 
 	/* relation OID by partition index, or 0 */
-	Oid		   *relid_map;
+	Oid		   *relid_map pg_node_attr(array_size(nparts));
 
 	/*
 	 * initial_pruning_steps shows how to prune during executor startup (i.e.,
@@ -1452,7 +1452,7 @@ typedef struct PartitionedRelPruneInfo
  *
  * step_id is the global identifier of the step within its pruning context.
  */
-typedef struct PartitionPruneStep
+typedef struct PartitionPruneStep pg_node_attr(abstract)
 {
 	NodeTag		type;
 	int			step_id;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 732c00c098..9e6b4bdb1d 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -64,8 +64,11 @@ typedef struct RangeVar
 {
 	NodeTag		type;
 
-	/* the catalog (database) name, or NULL */
-	char	   *catalogname;
+	/*
+	 * the catalog (database) name, or NULL; ignored for read/write, since it
+	 * is presently not semantically meaningful
+	 */
+	char	   *catalogname pg_node_attr(read_write_ignore);
 
 	/* the schema name, or NULL */
 	char	   *schemaname;
@@ -155,7 +158,7 @@ typedef struct IntoClause
  * contains NodeTag, this is a formality, but it is an easy form of
  * documentation.  See also the ExprState node types in execnodes.h.
  */
-typedef struct Expr
+typedef struct Expr pg_node_attr(abstract)
 {
 	NodeTag		type;
 } Expr;
@@ -233,10 +236,15 @@ typedef struct Var
 	 */
 	Index		varlevelsup;
 
+	/*
+	 * varnosyn/varattnosyn are ignored for equality, because Vars with
+	 * different syntactic identifiers are semantically the same as long as
+	 * their varno/varattno match.
+	 */
 	/* syntactic relation index (0 if unknown) */
-	Index		varnosyn;
+	Index		varnosyn pg_node_attr(equal_ignore);
 	/* syntactic attribute number */
-	AttrNumber	varattnosyn;
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -250,7 +258,7 @@ typedef struct Var
  * references).  This ensures that the Const node is self-contained and makes
  * it more likely that equal() will see logically identical values as equal.
  */
-typedef struct Const
+typedef struct Const pg_node_attr(custom_copy_equal, custom_read_write)
 {
 	Expr		xpr;
 	Oid			consttype;		/* pg_type OID of the constant's datatype */
@@ -374,8 +382,11 @@ typedef struct Aggref
 	/* OID of collation that function should use */
 	Oid			inputcollid;
 
-	/* type Oid of aggregate's transition value */
-	Oid			aggtranstype;
+	/*
+	 * type Oid of aggregate's transition value; ignored for equal since it
+	 * might not be set yet
+	 */
+	Oid			aggtranstype pg_node_attr(equal_ignore);
 
 	/* type Oids of direct and aggregated args */
 	List	   *aggargtypes;
@@ -455,10 +466,10 @@ typedef struct GroupingFunc
 	List	   *args;
 
 	/* ressortgrouprefs of arguments */
-	List	   *refs;
+	List	   *refs pg_node_attr(equal_ignore);
 
 	/* actual column positions set by planner */
-	List	   *cols;
+	List	   *cols pg_node_attr(equal_ignore);
 
 	/* same as Aggref.agglevelsup */
 	Index		agglevelsup;
@@ -634,7 +645,7 @@ typedef struct OpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of underlying function */
-	Oid			opfuncid;
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
 
 	/* PG_TYPE OID of result value */
 	Oid			opresulttype;
@@ -698,6 +709,10 @@ typedef OpExpr NullIfExpr;
  * corresponding function and won't be used during execution.  For
  * non-hashtable based NOT INs, negfuncid will be set to InvalidOid.  See
  * convert_saop_to_hashed_saop().
+ *
+ * Similar to OpExpr, opfuncid, hashfuncid, and negfuncid are not necessarily
+ * filled in right away, so will be ignored for equality if they are not set
+ * yet.
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -707,13 +722,13 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of comparison function */
-	Oid			opfuncid;
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
 
 	/* PG_PROC OID of hash func or InvalidOid */
-	Oid			hashfuncid;
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);
 
 	/* PG_PROC OID of negator of opfuncid function or InvalidOid.  See above */
-	Oid			negfuncid;
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);
 
 	/* true for ANY, false for ALL */
 	bool		useOr;
@@ -740,7 +755,7 @@ typedef enum BoolExprType
 	AND_EXPR, OR_EXPR, NOT_EXPR
 } BoolExprType;
 
-typedef struct BoolExpr
+typedef struct BoolExpr pg_node_attr(custom_read_write)
 {
 	Expr		xpr;
 	BoolExprType boolop;
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index eaf937051c..6193f51536 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -25,7 +25,7 @@
  * (There used to be a Value node, which encompassed all these different node types.  Hence the name of this file.)
  */
 
-typedef struct Integer
+typedef struct Integer pg_node_attr(special_read_write)
 {
 	NodeTag		type;
 	int			ival;
@@ -42,25 +42,25 @@ typedef struct Integer
  * Note that an integer-looking string will get lexed as T_Float if the value
  * is too large to fit in an 'int'.
  */
-typedef struct Float
+typedef struct Float pg_node_attr(special_read_write)
 {
 	NodeTag		type;
 	char	   *fval;
 } Float;
 
-typedef struct Boolean
+typedef struct Boolean pg_node_attr(special_read_write)
 {
 	NodeTag		type;
 	bool		boolval;
 } Boolean;
 
-typedef struct String
+typedef struct String pg_node_attr(special_read_write)
 {
 	NodeTag		type;
 	char	   *sval;
 } String;
 
-typedef struct BitString
+typedef struct BitString pg_node_attr(special_read_write)
 {
 	NodeTag		type;
 	char	   *bsval;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 1896a9a06d..3e6da86688 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -265,7 +265,7 @@ typedef struct RelationData
  * Currently, we mostly cache fields of interest to the planner, but the set
  * of fields has already grown the constraint OID for other uses.
  */
-typedef struct ForeignKeyCacheInfo
+typedef struct ForeignKeyCacheInfo pg_node_attr(no_read)
 {
 	NodeTag		type;
 	Oid			conoid;			/* oid of the constraint itself */
@@ -273,9 +273,12 @@ typedef struct ForeignKeyCacheInfo
 	Oid			confrelid;		/* relation referenced by the foreign key */
 	int			nkeys;			/* number of columns in the foreign key */
 	/* these arrays each have nkeys valid entries: */
-	AttrNumber	conkey[INDEX_MAX_KEYS]; /* cols in referencing table */
-	AttrNumber	confkey[INDEX_MAX_KEYS];	/* cols in referenced table */
-	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
+	/* cols in referencing table */
+	AttrNumber	conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
+	/* cols in referenced table */
+	AttrNumber	confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
+	/* PK = FK operator OIDs */
+	Oid			conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
 } ForeignKeyCacheInfo;
 
 
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index d30e8fcb11..286b5810c9 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -841,6 +841,52 @@ EOF
 		close($chs);
 	}
 
+	if (IsNewer('src/backend/nodes/node-support-stamp',
+		'src/backend/nodes/gen_node_support.pl'))
+	{
+		# XXX duplicates src/backend/nodes/Makefile
+
+		my @node_headers = qw(
+			nodes/nodes.h
+			nodes/execnodes.h
+			nodes/plannodes.h
+			nodes/primnodes.h
+			nodes/pathnodes.h
+			nodes/extensible.h
+			nodes/parsenodes.h
+			nodes/replnodes.h
+			nodes/value.h
+			commands/trigger.h
+			commands/event_trigger.h
+			foreign/fdwapi.h
+			access/amapi.h
+			access/tableam.h
+			access/tsmapi.h
+			utils/rel.h
+			nodes/supportnodes.h
+			executor/tuptable.h
+			nodes/lockoptions.h
+			access/sdir.h
+		);
+
+		chdir('src/backend/nodes');
+
+		my @node_files = map { "../../../src/include/$_" } @node_headers;
+
+		system("perl gen_node_support.pl @node_files");
+		open(my $f, '>', 'node-support-stamp') || confess "Could not touch node-support-stamp";
+		close($f);
+		chdir('../../..');
+	}
+
+	if (IsNewer(
+			'src/include/nodes/nodetags.h',
+			'src/backend/nodes/nodetags.h'))
+	{
+		copyFile('src/backend/nodes/nodetags.h',
+			'src/include/nodes/nodetags.h');
+	}
+
 	open(my $o, '>', "doc/src/sgml/version.sgml")
 	  || croak "Could not write to version.sgml\n";
 	print $o <<EOF;

base-commit: b55f62abb2c2e07dfae99e19a2b3d7ca9e58dc1a
-- 
2.36.1

v7-0002-toms-changes.patchtext/x-diff; charset=us-ascii; name=v7-0002-toms-changes.patchDownload
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 6aaf401a72..c77a130054 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -40,8 +40,10 @@ my @node_types = qw(Node);
 # collect info for each node type
 my %node_type_info;
 
-# node types we don't want copy/equal support for
-my @no_copy_equal;
+# node types we don't want 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
@@ -90,8 +92,21 @@ push @scalar_types, qw(QualCost);
 
 # XXX various things we are not publishing right now to stay level
 # with the manual system
-push @no_copy_equal, qw(CallContext InlineCodeBlock);
+push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_equal, qw(CallContext InlineCodeBlock);
 push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs 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
@@ -201,14 +216,16 @@ foreach my $infile (@ARGV)
 						qw(execnodes.h trigger.h event_trigger.h amapi.h tableam.h
 							tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h))
 					{
-						push @no_copy_equal, $in_struct;
+						push @no_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_equal, $in_struct if elem $supertype, @no_copy_equal;
+						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;
 					}
 				}
@@ -245,7 +262,9 @@ foreach my $infile (@ARGV)
 						foreach my $attr (@attrs)
 						{
 							if ($attr !~ /^array_size\(\w+\)$/ &&
-								!elem $attr, qw(copy_ignore equal_ignore equal_ignore_if_zero read_write_ignore
+								$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";
@@ -291,9 +310,18 @@ foreach my $infile (@ARGV)
 					{
 						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_equal, $in_struct;
+						push @no_copy, $in_struct;
+						push @no_equal, $in_struct;
 					}
 					elsif ($attr eq 'no_read')
 					{
@@ -391,16 +419,18 @@ print $eff $node_includes;
 foreach my $n (@node_types)
 {
 	next if elem $n, @abstract_types;
-	next if elem $n, @no_copy_equal;
+	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";
+	  "\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";
+	  "\t\t\tbreak;\n" unless $struct_no_equal;
 
 	next if elem $n, @custom_copy_equal;
 
@@ -410,21 +440,47 @@ _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 = (elem 'copy_ignore', @a);
-		my $equal_ignore = (elem 'equal_ignore', @a);
+		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*')
@@ -458,15 +514,6 @@ _equal${n}(const $n *a, const $n *b)
 		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
 		{
 			my $tt = $1;
-			my $array_size_field;
-			foreach my $a (@a)
-			{
-				if ($a =~ /^array_size.([\w.]+)/)
-				{
-					$array_size_field = $1;
-					last;
-				}
-			}
 			if (!$array_size_field)
 			{
 				die "no array size defined for $n.$f of type $t";
@@ -511,11 +558,11 @@ _equal${n}(const $n *a, const $n *b)
 	print $cff "
 \treturn newnode;
 }
-";
+" unless $struct_no_copy;
 	print $eff "
 \treturn true;
 }
-";
+" unless $struct_no_equal;
 }
 
 close $cff;
@@ -546,18 +593,17 @@ foreach my $n (@node_types)
 		next unless elem $n, @keep;
 	}
 
-	my $no_read = (elem $n, @no_read);
+	my $struct_no_read = (elem $n, @no_read);
 
-	# output format starts with upper case node type, underscores stripped
+	# output format starts with upper case node type name
 	my $N = uc $n;
-	$N =~ s/_//g;
 
 	print $ofs "\t\t\tcase T_${n}:\n".
 	  "\t\t\t\t_out${n}(str, obj);\n".
 	  "\t\t\t\tbreak;\n";
 
 	print $rfs "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
-	  "\t\treturn_value = _read${n}();\n" unless $no_read;
+	  "\t\treturn_value = _read${n}();\n" unless $struct_no_read;
 
 	next if elem $n, @custom_read_write;
 
@@ -575,18 +621,47 @@ _read${n}(void)
 {
 \tREAD_LOCALS($n);
 
-" unless $no_read;
+" 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} };
-		next if (elem 'read_write_ignore', @a);
+		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;
+			}
+		}
 
 		# XXX Previously, for subtyping, only the leaf field name is
 		# used. Ponder whether we want to keep it that way.
 
+		# 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')
 		{
@@ -712,7 +787,8 @@ _read${n}(void)
 		}
 		elsif ($t eq 'ParamPathInfo*' && elem 'write_only_req_outer', @a)
 		{
-			print $off "\tif (node->$f)\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";
@@ -754,7 +830,7 @@ _read${n}(void)
 	print $rff "
 \tREAD_DONE();
 }
-" unless $no_read;
+" unless $struct_no_read;
 }
 
 close $off;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d77aad6473..9166903606 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -590,26 +590,39 @@ typedef enum NodeTag
  * - custom_read_write: Has custom implementations in outfuncs.c and
  *   readfuncs.c.
  *
- * - no_copy_equal: Does not support copyObject() and equal() at all.
+ * - 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_ignore: Ignore the field for copy.
+ * - 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_write_ignore: Ignore the field for read/write.
+ * - 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.
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fb026c6b1f..d870d3b09c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -127,7 +127,7 @@ typedef struct Query pg_node_attr(custom_read_write)
 	 * query identifier (can be set by plugins); ignored for equal, might not
 	 * be set
 	 */
-	uint64		queryId pg_node_attr(equal_ignore);
+	uint64		queryId pg_node_attr(equal_ignore, read_as(0));
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 4212610d5e..77fb3c0b8a 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -92,7 +92,7 @@ typedef enum UpperRelationKind
  * the field type.)
  *----------
  */
-typedef struct PlannerGlobal pg_node_attr(no_copy_equal)
+typedef struct PlannerGlobal pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -163,7 +163,7 @@ typedef struct PlannerInfo PlannerInfo;
 #define HAVE_PLANNERINFO_TYPEDEF 1
 #endif
 
-struct PlannerInfo pg_node_attr(no_copy_equal)
+struct PlannerInfo pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -376,7 +376,7 @@ struct PlannerInfo pg_node_attr(no_copy_equal)
 
 	/* These fields are used only when hasRecursion is true: */
 	int			wt_param_id;	/* PARAM_EXEC ID for the work table */
-	struct Path *non_recursive_path;	/* a path for non-recursive term */
+	struct Path *non_recursive_path pg_node_attr(read_write_ignore);	/* a path for non-recursive term */
 
 	/* These fields are workspace for createplan.c */
 	Relids		curOuterRels;	/* outer rels above current node */
@@ -691,7 +691,7 @@ typedef enum RelOptKind
 	 (rel)->reloptkind == RELOPT_OTHER_JOINREL || \
 	 (rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
 
-typedef struct RelOptInfo pg_node_attr(no_copy_equal)
+typedef struct RelOptInfo pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -922,7 +922,7 @@ typedef struct IndexOptInfo IndexOptInfo;
 #define HAVE_INDEXOPTINFO_TYPEDEF 1
 #endif
 
-struct IndexOptInfo pg_node_attr(no_copy_equal)
+struct IndexOptInfo pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -1081,7 +1081,7 @@ typedef struct ForeignKeyOptInfo pg_node_attr(custom_read_write, no_copy_equal,
  * Each pg_statistic_ext row is represented by one or more nodes of this
  * type, or even zero if ANALYZE has not computed them.
  */
-typedef struct StatisticExtInfo pg_node_attr(no_copy_equal)
+typedef struct StatisticExtInfo pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -1146,6 +1146,10 @@ typedef struct StatisticExtInfo pg_node_attr(no_copy_equal)
  *
  * 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 pg_node_attr(custom_read_write, no_copy_equal, no_read)
 {
@@ -1197,7 +1201,7 @@ typedef struct EquivalenceClass pg_node_attr(custom_read_write, no_copy_equal, n
  * anyarray_ops would never work without this.  Use em_datatype when
  * looking up a specific btree operator to work with this expression.
  */
-typedef struct EquivalenceMember pg_node_attr(no_copy_equal)
+typedef struct EquivalenceMember pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -1226,7 +1230,7 @@ typedef struct EquivalenceMember pg_node_attr(no_copy_equal)
  * BTGreaterStrategyNumber (for DESC).  We assume that all ordering-capable
  * index types will use btree-compatible strategy numbers.
  */
-typedef struct PathKey
+typedef struct PathKey pg_node_attr(no_read)
 {
 	NodeTag		type;
 
@@ -1239,7 +1243,7 @@ typedef struct PathKey
 /*
  * Combines information about pathkeys and the associated clauses.
  */
-typedef struct PathKeyInfo
+typedef struct PathKeyInfo pg_node_attr(no_read)
 {
 	NodeTag		type;
 	List	   *pathkeys;
@@ -1320,7 +1324,7 @@ typedef struct PathTarget pg_node_attr(no_copy_equal, no_read)
  * on how the join is formed.  The relevant clauses will appear in each
  * parameterized join path's joinrestrictinfo list, instead.
  */
-typedef struct ParamPathInfo pg_node_attr(no_copy_equal)
+typedef struct ParamPathInfo pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -1498,7 +1502,7 @@ typedef struct IndexPath
  * column, i.e. the indexcol values must form a nondecreasing sequence.
  * (The order of multiple clauses for the same index column is unspecified.)
  */
-typedef struct IndexClause pg_node_attr(no_copy_equal)
+typedef struct IndexClause pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 	struct RestrictInfo *rinfo; /* original restriction or join clause */
@@ -1995,14 +1999,14 @@ typedef struct AggPath
  * Various annotations used for grouping sets in the planner.
  */
 
-typedef struct GroupingSetData pg_node_attr(no_copy_equal)
+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;
 
-typedef struct RollupData pg_node_attr(no_copy_equal)
+typedef struct RollupData pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 	List	   *groupClause;	/* applicable subset of parse->groupClause */
@@ -2275,7 +2279,7 @@ typedef struct LimitPath
  * recursion in plan tree dump.
  */
 
-typedef struct RestrictInfo
+typedef struct RestrictInfo pg_node_attr(no_read)
 {
 	NodeTag		type;
 
@@ -2370,10 +2374,10 @@ typedef struct RestrictInfo
 
 	/*
 	 * List of MergeScanSelCache structs.  Those aren't Nodes, so hard to
-	 * copy.  Ignoring it will have the effect that copying will just reset
-	 * the cache.
+	 * 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_ignore, equal_ignore);
+	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 =
@@ -2543,7 +2547,7 @@ typedef struct SpecialJoinInfo SpecialJoinInfo;
 #define HAVE_SPECIALJOININFO_TYPEDEF 1
 #endif
 
-struct SpecialJoinInfo
+struct SpecialJoinInfo pg_node_attr(no_read)
 {
 	NodeTag		type;
 	Relids		min_lefthand;	/* base relids in minimum LHS for join */
@@ -2665,7 +2669,7 @@ typedef struct AppendRelInfo
  * We add such a reference to root->processed_tlist when creating the entry,
  * and it propagates into the plan tree from there.
  */
-typedef struct RowIdentityVarInfo pg_node_attr(no_copy_equal)
+typedef struct RowIdentityVarInfo pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -2701,7 +2705,7 @@ typedef struct RowIdentityVarInfo pg_node_attr(no_copy_equal)
  * don't result in unnecessary constraints on join order.
  */
 
-typedef struct PlaceHolderInfo
+typedef struct PlaceHolderInfo pg_node_attr(no_read)
 {
 	NodeTag		type;
 
@@ -2732,7 +2736,7 @@ typedef struct PlaceHolderInfo
  * function.  MinMaxAggPath contains a list of these, and if we accept that
  * path, the list is stored into root->minmax_aggs for use during setrefs.c.
  */
-typedef struct MinMaxAggInfo pg_node_attr(no_copy_equal)
+typedef struct MinMaxAggInfo pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
@@ -2808,7 +2812,7 @@ typedef struct MinMaxAggInfo pg_node_attr(no_copy_equal)
  * Instead, we just record the assignment of the slot number by appending to
  * root->glob->paramExecTypes.
  */
-typedef struct PlannerParamItem pg_node_attr(no_copy_equal)
+typedef struct PlannerParamItem pg_node_attr(no_copy_equal, no_read)
 {
 	NodeTag		type;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 19b5ce2ec6..ab7fd8054b 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -38,9 +38,12 @@
  * 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
+typedef struct PlannedStmt pg_node_attr(no_equal)
 {
 	NodeTag		type;
 
@@ -108,7 +111,7 @@ typedef struct PlannedStmt
  * abstract superclass for all Plan-type nodes.
  * ----------------
  */
-typedef struct Plan
+typedef struct Plan pg_node_attr(abstract, no_equal)
 {
 	NodeTag		type;
 
@@ -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;
 
@@ -756,7 +765,7 @@ typedef struct CustomScan
  * the joinquals, ignoring plan.qual, due to where the executor tests it.)
  * ----------------
  */
-typedef struct Join
+typedef struct Join pg_node_attr(abstract)
 {
 	Plan		plan;
 	JoinType	jointype;
@@ -781,7 +790,7 @@ typedef struct NestLoop
 	List	   *nestParams;		/* list of NestLoopParam nodes */
 } NestLoop;
 
-typedef struct NestLoopParam
+typedef struct NestLoopParam pg_node_attr(no_equal)
 {
 	NodeTag		type;
 	int			paramno;		/* number of the PARAM_EXEC Param to set */
@@ -1343,7 +1352,7 @@ typedef enum RowMarkType
  * Note this means that all tables in an inheritance hierarchy share the
  * same resjunk column names.
  */
-typedef struct PlanRowMark
+typedef struct PlanRowMark pg_node_attr(no_equal)
 {
 	NodeTag		type;
 	Index		rti;			/* range table index of markable relation */
@@ -1387,7 +1396,7 @@ typedef struct PlanRowMark
  *						by any of the PartitionedRelPruneInfo nodes in
  *						"prune_infos".  These subplans must not be pruned.
  */
-typedef struct PartitionPruneInfo
+typedef struct PartitionPruneInfo pg_node_attr(no_equal)
 {
 	NodeTag		type;
 	List	   *prune_infos;
@@ -1411,7 +1420,7 @@ typedef struct PartitionPruneInfo
  * node, but partition indexes are valid only within a particular hierarchy.
  * relid_map[p] contains the partition's OID, or 0 if the partition was pruned.
  */
-typedef struct PartitionedRelPruneInfo
+typedef struct PartitionedRelPruneInfo pg_node_attr(no_equal)
 {
 	NodeTag		type;
 
@@ -1485,7 +1494,7 @@ typedef struct PartitionPruneStep pg_node_attr(abstract)
  * have an expression be present in 'exprs' for a given partition key and
  * the corresponding bit set in 'nullkeys'.
  */
-typedef struct PartitionPruneStepOp
+typedef struct PartitionPruneStepOp pg_node_attr(no_equal)
 {
 	PartitionPruneStep step;
 
@@ -1507,7 +1516,7 @@ typedef enum PartitionPruneCombineOp
 	PARTPRUNE_COMBINE_INTERSECT
 } PartitionPruneCombineOp;
 
-typedef struct PartitionPruneStepCombine
+typedef struct PartitionPruneStepCombine pg_node_attr(no_equal)
 {
 	PartitionPruneStep step;
 
@@ -1525,7 +1534,7 @@ typedef struct PartitionPruneStepCombine
  * to be used with the syscache invalidation mechanism, so it identifies a
  * system catalog entry by cache ID and hash value.
  */
-typedef struct PlanInvalItem
+typedef struct PlanInvalItem pg_node_attr(no_equal)
 {
 	NodeTag		type;
 	int			cacheId;		/* a syscache ID, see utils/syscache.h */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9e6b4bdb1d..6f3dcc74c3 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -68,7 +68,7 @@ typedef struct RangeVar
 	 * 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);
+	char	   *catalogname pg_node_attr(read_write_ignore, read_as(NULL));
 
 	/* the schema name, or NULL */
 	char	   *schemaname;
@@ -636,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
 {
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 3e6da86688..7b60450b26 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -265,7 +265,7 @@ typedef struct RelationData
  * Currently, we mostly cache fields of interest to the planner, but the set
  * of fields has already grown the constraint OID for other uses.
  */
-typedef struct ForeignKeyCacheInfo pg_node_attr(no_read)
+typedef struct ForeignKeyCacheInfo pg_node_attr(no_equal, no_read)
 {
 	NodeTag		type;
 	Oid			conoid;			/* oid of the constraint itself */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 7ef272cc7a..b648ee67ff 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -468,9 +468,6 @@ nodetag_to_string(NodeTag tag)
 		case T_TupleTableSlot:
 			return "TupleTableSlot";
 			break;
-		case T_Plan:
-			return "Plan";
-			break;
 		case T_Result:
 			return "Result";
 			break;
@@ -549,9 +546,6 @@ nodetag_to_string(NodeTag tag)
 		case T_CustomScan:
 			return "CustomScan";
 			break;
-		case T_Join:
-			return "Join";
-			break;
 		case T_NestLoop:
 			return "NestLoop";
 			break;
#45Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#44)
Re: automatically generating node support functions

I wrote:

I have gone through this and made some proposed changes (attached),
and I think it is almost committable.

I see from the cfbot that it now needs to be taught about RelFileNumber...

regards, tom lane

#46Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#44)
1 attachment(s)
Re: automatically generating node support functions

On 06.07.22 22:46, Tom Lane wrote:

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

[ v7-0001-Automatically-generate-node-support-functions.patch ]

I have gone through this and made some proposed changes (attached),

I have included those.

and I think it is almost committable. There is one nasty problem
we need a solution to, which is that pgindent is not at all on board
with this idea of attaching node attrs to typedefs. It pushes them
to the next line, like this:

@@ -691,7 +709,8 @@
(rel)->reloptkind == RELOPT_OTHER_JOINREL || \
(rel)->reloptkind == RELOPT_OTHER_UPPER_REL)

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

I have found that putting the attributes at the end of the struct
definition, right before the semicolon, works, so I have changed it that
way. (This is also where a gcc __attribute__() would go, so it seems
reasonable.)

The attached patch is stable under pgindent.

Finally, I have updated src/backend/nodes/README a bit.

I realize I've been confused various times about when a catversion
change is required when changing nodes. (I think the bump in 251154bebe
was probably not needed.) I have tried to put that in the README. This
could perhaps be expanded.

I think for this present patch, I would do a catversion bump, just to be
sure, in case some of the printed node fields are different now.

It was also my plan to remove the #ifdef OBSOLETE sections in a separate
commit right after, just to be clear.

Final thoughts?

Attachments:

v8-0001-Automatically-generate-node-support-functions.patchtext/plain; charset=UTF-8; name=v8-0001-Automatically-generate-node-support-functions.patchDownload
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-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
@@ -185,6 +189,7 @@ distprep:
 	$(MAKE) -C parser	gram.c gram.h scan.c
 	$(MAKE) -C bootstrap	bootparse.c bootscanner.c
 	$(MAKE) -C catalog	distprep
+	$(MAKE) -C nodes	distprep
 	$(MAKE) -C replication	repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
 	$(MAKE) -C storage/lmgr	lwlocknames.h lwlocknames.c
 	$(MAKE) -C utils	distprep
@@ -297,6 +302,7 @@ distclean: clean
 
 maintainer-clean: distclean
 	$(MAKE) -C catalog $@
+	$(MAKE) -C nodes $@
 	$(MAKE) -C utils $@
 	rm -f bootstrap/bootparse.c \
 	      bootstrap/bootscanner.c \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..0c14b5697b
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,4 @@
+/node-support-stamp
+/nodetags.h
+/*funcs.funcs.c
+/*funcs.switch.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..1a0d5b9314 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,62 @@ OBJS = \
 	value.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+node_headers = \
+	nodes/nodes.h \
+	nodes/execnodes.h \
+	nodes/plannodes.h \
+	nodes/primnodes.h \
+	nodes/pathnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.h \
+	nodes/value.h \
+	commands/trigger.h \
+	commands/event_trigger.h \
+	foreign/fdwapi.h \
+	access/amapi.h \
+	access/tableam.h \
+	access/tsmapi.h \
+	utils/rel.h \
+	nodes/supportnodes.h \
+	executor/tuptable.h \
+	nodes/lockoptions.h \
+	access/sdir.h
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-support-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+# node-support-stamp records the last time we ran gen_node_support.pl.
+# We don't rely on the timestamps of the individual output files,
+# because the Perl script won't update them if they didn't change (to
+# avoid unnecessary recompiles).
+node-support-stamp: gen_node_support.pl $(addprefix $(top_srcdir)/src/include/,$(node_headers))
+	$(PERL) $^
+	touch $@
+
+# These generated headers must be symlinked into builddir/src/include/,
+# using absolute links for the reasons explained in src/backend/Makefile.
+# We use header-stamp to record that we've done this because the symlinks
+# themselves may appear older than node-support-stamp.
+$(top_builddir)/src/include/nodes/header-stamp: node-support-stamp
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	cd '$(dir $@)' && for file in nodetags.h; do \
+	  rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \
+	done
+	touch $@
+
+copyfuncs.o: copyfuncs.c copyfuncs.funcs.c copyfuncs.switch.c | node-support-stamp
+equalfuncs.o: equalfuncs.c equalfuncs.funcs.c equalfuncs.switch.c | node-support-stamp
+outfuncs.o: outfuncs.c outfuncs.funcs.c outfuncs.switch.c | node-support-stamp
+readfuncs.o:  readfuncs.c readfuncs.funcs.c readfuncs.switch.c | node-support-stamp
+
+maintainer-clean: clean
+	rm -f node-support-stamp $(addsuffix funcs.funcs.c,copy equal out read) $(addsuffix funcs.switch.c,copy equal out read) nodetags.h
diff --git a/src/backend/nodes/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 TimeLineID XLogRecPtr
+);
+
+# collect enum types
+my @enum_types;
+
+my @abstract_types = qw(Node);
+
+# Special cases that either don't have their own struct or the struct
+# is not in a header file.  We just generate node tags for them, but
+# they otherwise don't participate in node support.
+my @extra_tags = qw(
+	IntList OidList XidList
+	AllocSetContext GenerationContext SlabContext
+	TIDBitmap
+	WindowObjectData
+);
+
+# This is a regular node, but we skip parsing it from its header file
+# since we won't use its internal structure here anyway.
+push @node_types, qw(List);
+# See special treatment in outNode() and nodeRead().
+push @no_read_write, qw(List);
+
+# Nodes with custom copy/equal implementations are skipped from
+# .funcs.c but need case statements in .switch.c.
+my @custom_copy_equal;
+
+# Similarly for custom read/write implementations.
+my @custom_read_write;
+
+# EquivalenceClasses are never moved, so just shallow-copy the pointer
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember*);
+
+# This is a struct, so we can copy it by assignment.  Equal support is
+# currently not required.
+push @scalar_types, qw(QualCost);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_equal, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs 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 ImportForeignSchemaStmt IndexStmt 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.c outfuncs.switch.c readfuncs.funcs.c readfuncs.switch.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 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

#47Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#46)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

On 06.07.22 22:46, Tom Lane wrote:

... There is one nasty problem
we need a solution to, which is that pgindent is not at all on board
with this idea of attaching node attrs to typedefs.

I have found that putting the attributes at the end of the struct
definition, right before the semicolon, works, so I have changed it that
way. (This is also where a gcc __attribute__() would go, so it seems
reasonable.)

That was the first solution I thought of as well, but I do not like
it from a cosmetic standpoint. The node attributes are a pretty
critical part of the node definition (especially "abstract"),
so shoving them to the very end is not helpful for readability.
IMO anyway.

I think for this present patch, I would do a catversion bump, just to be
sure, in case some of the printed node fields are different now.

I know from comparing the code that some printed node tags have
changed, and so has the print order of some fields. It might be
that none of those changes are in node types that can appear in
stored rules --- but I'm not sure, so I concur that doing a
catversion bump for this commit is advisable.

It was also my plan to remove the #ifdef OBSOLETE sections in a separate
commit right after, just to be clear.

Yup, my thought as well. There are a few other mop-up things
I want to do shortly after (e.g. add copyright-notice headers
to the emitted files), but let's wait for the buildfarm's
opinion of the main commit first.

Final thoughts?

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.

regards, tom lane

#48Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#47)
Re: automatically generating node support functions

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.

#49Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#46)
1 attachment(s)
Re: automatically generating node support functions

While going over this patch, I noticed that I forgot to add support for
XidList in copyfuncs.c. OK if I push this soon quickly?

--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/

Attachments:

0001-Forgot-to-add-copy-support-in-f10a025cfe97.patchtext/x-diff; charset=us-asciiDownload
From 24185e0421cc1e22f9a78f56d03e4585a142e78e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 8 Jul 2022 15:34:47 +0200
Subject: [PATCH] Forgot to add copy support in f10a025cfe97

---
 src/backend/nodes/copyfuncs.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2c834e4d0d..b8a5715981 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -5978,11 +5978,12 @@ copyObjectImpl(const void *from)
 			break;
 
 			/*
-			 * Lists of integers and OIDs don't need to be deep-copied, so we
-			 * perform a shallow copy via list_copy()
+			 * Lists of integers, OIDs and XIDs don't need to be deep-copied,
+			 * so we perform a shallow copy via list_copy()
 			 */
 		case T_IntList:
 		case T_OidList:
+		case T_XidList:
 			retval = list_copy(from);
 			break;
 
-- 
2.30.2

#50Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#49)
Re: automatically generating node support functions

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

While going over this patch, I noticed that I forgot to add support for
XidList in copyfuncs.c. OK if I push this soon quickly?

Yeah, go ahead, that part of copyfuncs is still going to be manually
maintained, so we need the fix.

What about equalfuncs etc?

regards, tom lane

#51Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#48)
4 attachment(s)
Re: automatically generating node support functions

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

Attachments:

v8-0002-miscellaneous-fixes.patchtext/x-diff; charset=us-ascii; name=v8-0002-miscellaneous-fixes.patchDownload
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:
  *
v8-0003-move-struct-attributes.patchtext/x-diff; charset=us-ascii; name=v8-0003-move-struct-attributes.patchDownload
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;
 
 
 /*
v8-0004-apply-pgperltidy.patchtext/x-diff; charset=us-ascii; name=v8-0004-apply-pgperltidy.patchDownload
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 TimeLineID XLogRecPtr
+  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 TimeLineID XLogRecPtr
 );
 
 # 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 InlineCodeBlock ObjectWithArgs OnConflictClause PartitionCmd RoleSpec VacuumRelation);
+push @no_read_write,
+  qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs 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);
+  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 ImportForeignSchemaStmt IndexStmt NotifyStmt PlannedStmt PLAssignStmt RawStmt ReturnStmt SelectStmt SetOperationStmt);
+		my @keep =
+		  qw(AlterStatsStmt CreateForeignTableStmt CreateStatsStmt CreateStmt DeclareCursorStmt ImportForeignSchemaStmt IndexStmt 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.c outfuncs.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.c readfuncs.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('../../..');
 	}
v8-0001-Automatically-generate-node-support-functions.patchtext/x-diff; charset=us-ascii; name=v8-0001-Automatically-generate-node-support-functions.patchDownload
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-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-nodes-headers submake-utils-headers
 
 $(top_builddir)/src/include/parser/gram.h: parser/gram.h
 	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
@@ -185,6 +189,7 @@ distprep:
 	$(MAKE) -C parser	gram.c gram.h scan.c
 	$(MAKE) -C bootstrap	bootparse.c bootscanner.c
 	$(MAKE) -C catalog	distprep
+	$(MAKE) -C nodes	distprep
 	$(MAKE) -C replication	repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
 	$(MAKE) -C storage/lmgr	lwlocknames.h lwlocknames.c
 	$(MAKE) -C utils	distprep
@@ -297,6 +302,7 @@ distclean: clean
 
 maintainer-clean: distclean
 	$(MAKE) -C catalog $@
+	$(MAKE) -C nodes $@
 	$(MAKE) -C utils $@
 	rm -f bootstrap/bootparse.c \
 	      bootstrap/bootscanner.c \
diff --git a/src/backend/nodes/.gitignore b/src/backend/nodes/.gitignore
new file mode 100644
index 0000000000..0c14b5697b
--- /dev/null
+++ b/src/backend/nodes/.gitignore
@@ -0,0 +1,4 @@
+/node-support-stamp
+/nodetags.h
+/*funcs.funcs.c
+/*funcs.switch.c
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 5d2b12a993..1a0d5b9314 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -30,3 +30,62 @@ OBJS = \
 	value.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+node_headers = \
+	nodes/nodes.h \
+	nodes/execnodes.h \
+	nodes/plannodes.h \
+	nodes/primnodes.h \
+	nodes/pathnodes.h \
+	nodes/extensible.h \
+	nodes/parsenodes.h \
+	nodes/replnodes.h \
+	nodes/value.h \
+	commands/trigger.h \
+	commands/event_trigger.h \
+	foreign/fdwapi.h \
+	access/amapi.h \
+	access/tableam.h \
+	access/tsmapi.h \
+	utils/rel.h \
+	nodes/supportnodes.h \
+	executor/tuptable.h \
+	nodes/lockoptions.h \
+	access/sdir.h
+
+# see also catalog/Makefile for an explanation of these make rules
+
+all: distprep generated-header-symlinks
+
+distprep: node-support-stamp
+
+.PHONY: generated-header-symlinks
+
+generated-header-symlinks: $(top_builddir)/src/include/nodes/header-stamp
+
+# node-support-stamp records the last time we ran gen_node_support.pl.
+# We don't rely on the timestamps of the individual output files,
+# because the Perl script won't update them if they didn't change (to
+# avoid unnecessary recompiles).
+node-support-stamp: gen_node_support.pl $(addprefix $(top_srcdir)/src/include/,$(node_headers))
+	$(PERL) $^
+	touch $@
+
+# These generated headers must be symlinked into builddir/src/include/,
+# using absolute links for the reasons explained in src/backend/Makefile.
+# We use header-stamp to record that we've done this because the symlinks
+# themselves may appear older than node-support-stamp.
+$(top_builddir)/src/include/nodes/header-stamp: node-support-stamp
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	cd '$(dir $@)' && for file in nodetags.h; do \
+	  rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \
+	done
+	touch $@
+
+copyfuncs.o: copyfuncs.c copyfuncs.funcs.c copyfuncs.switch.c | node-support-stamp
+equalfuncs.o: equalfuncs.c equalfuncs.funcs.c equalfuncs.switch.c | node-support-stamp
+outfuncs.o: outfuncs.c outfuncs.funcs.c outfuncs.switch.c | node-support-stamp
+readfuncs.o:  readfuncs.c readfuncs.funcs.c readfuncs.switch.c | node-support-stamp
+
+maintainer-clean: clean
+	rm -f node-support-stamp $(addsuffix funcs.funcs.c,copy equal out read) $(addsuffix funcs.switch.c,copy equal out read) nodetags.h
diff --git a/src/backend/nodes/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 TimeLineID XLogRecPtr
+);
+
+# collect enum types
+my @enum_types;
+
+my @abstract_types = qw(Node);
+
+# Special cases that either don't have their own struct or the struct
+# is not in a header file.  We just generate node tags for them, but
+# they otherwise don't participate in node support.
+my @extra_tags = qw(
+	IntList OidList XidList
+	AllocSetContext GenerationContext SlabContext
+	TIDBitmap
+	WindowObjectData
+);
+
+# This is a regular node, but we skip parsing it from its header file
+# since we won't use its internal structure here anyway.
+push @node_types, qw(List);
+# See special treatment in outNode() and nodeRead().
+push @no_read_write, qw(List);
+
+# Nodes with custom copy/equal implementations are skipped from
+# .funcs.c but need case statements in .switch.c.
+my @custom_copy_equal;
+
+# Similarly for custom read/write implementations.
+my @custom_read_write;
+
+# EquivalenceClasses are never moved, so just shallow-copy the pointer
+push @scalar_types, qw(EquivalenceClass* EquivalenceMember*);
+
+# This is a struct, so we can copy it by assignment.  Equal support is
+# currently not required.
+push @scalar_types, qw(QualCost);
+
+# XXX various things we are not publishing right now to stay level
+# with the manual system
+push @no_copy, qw(CallContext InlineCodeBlock);
+push @no_equal, qw(CallContext InlineCodeBlock);
+push @no_read_write, qw(AccessPriv AlterTableCmd CallContext CreateOpClassItem FunctionParameter InferClause InlineCodeBlock ObjectWithArgs 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 ImportForeignSchemaStmt IndexStmt 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.c outfuncs.switch.c readfuncs.funcs.c readfuncs.switch.c))
+{
+	Catalog::RenameTempFile($file, $tmpext);
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 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

#52Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#51)
Re: automatically generating node support functions

I wrote:

0003 moves the node-level attributes as discussed.

Meh. Just realized that I forgot to adjust the commentary in nodes.h
about where to put node attributes.

Maybe like

- * 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
+ * Attributes can be attached to a node as a whole (place the attribute
+ * specification on the first line after the struct's opening brace)
+ * or to a specific field (place it at the end of that field's line).  The
  * argument is a comma-separated list of attributes.  Unrecognized attributes
  * cause an error.

regards, tom lane

#53Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#51)
Re: automatically generating node support functions

On 08.07.22 22:03, Tom Lane wrote:

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

This is done now, after a brief vpath-shaped scare from the buildfarm
earlier today.

#54Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#53)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

On 08.07.22 22:03, Tom Lane wrote:

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

This is done now, after a brief vpath-shaped scare from the buildfarm
earlier today.

Doh ... never occurred to me either to try that :-(

regards, tom lane

#55Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#53)
4 attachment(s)
Re: automatically generating node support functions

Here's some follow-on patches, as I threatened yesterday.

0001 adds some material to nodes/README in hopes of compensating for
a couple of removed comments.

0002 fixes gen_node_support.pl's rather badly broken error reporting.
As it stands, it always says that an error is on line 1 of the respective
input file, because it relies for that on perl's "$." which is only
workable when we are reading the file a line at a time. The scheme
of sucking in the entire file so that we can suppress multi-line C
comments easily doesn't play well with that. I concluded that the
best way to fix that was to adjust the C-comment-deletion code to
preserve any newlines within a comment, and then we can easily count
lines manually. The new C-comment-deletion code is a bit brute-force;
maybe there is a better way?

0003 adds boilerplate header comments to the output files, using
wording pretty similar to those written by genbki.pl.

0004 fixes things so that we don't leave a mess of temporary files
if the script dies partway through. genbki.pl perhaps could use
this as well, but my experience is that genbki usually reports any
errors before starting to write files. gen_node_support.pl not
so much --- I had to manually clean up the mess several times while
reviewing/testing.

regards, tom lane

Attachments:

0001-readme-additions.patchtext/x-diff; charset=us-ascii; name=0001-readme-additions.patchDownload
diff --git a/src/backend/nodes/README b/src/backend/nodes/README
index b3dc9afaf7..d8ae35ce58 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -6,10 +6,30 @@ Node Structures
 Introduction
 ------------
 
+Postgres uses "node" types to organize parse trees, plan trees, and
+executor state trees.  All objects that can appear in such trees must
+be declared as node types.  In addition, a few object types that aren't
+part of parse/plan/execute node trees receive NodeTags anyway for
+identification purposes, usually because they are involved in APIs
+where we want to pass multiple object types through the same pointer.
+
 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.
 
+Node types typically have support for being copied by copyObject(),
+compared by equal(), serialized by outNode(), and deserialized by
+nodeRead().  For some classes of Nodes, not all of these support
+functions are required; for example, executor state nodes don't
+presently need any of them.  So far as the system is concerned,
+output and read functions are only needed for node types that can
+appear in parse trees stored in the catalogs.  However, we provide
+output functions for many other node types as well, because they
+are very handy for debugging.
+
+Relevant Files
+--------------
+
 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.
@@ -40,7 +60,7 @@ FILES IN THIS DIRECTORY (src/backend/nodes/)
 
 FILES IN src/include/nodes/
 
-    Node definitions:
+    Node definitions primarily appear in:
 	nodes.h		- define node tags (NodeTag) (*)
 	primnodes.h	- primitive nodes
 	parsenodes.h	- parse tree nodes
0002-better-error-reporting.patchtext/x-diff; charset=us-ascii; name=0002-better-error-reporting.patchDownload
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index dca5819f95..6816c36e2b 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -124,19 +124,31 @@ foreach my $infile (@ARGV)
 	my $supertype_field;
 
 	my $node_attrs = '';
+	my $node_attrs_lineno;
 	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> };
+	my $raw_file_content = do { local $/; <$ifh> };
 
-	# strip C comments
-	$file_content =~ s{/\*.*?\*/}{}gs;
+	# strip C comments, preserving newlines so we can count lines correctly
+	my $file_content = '';
+	while ($raw_file_content =~ m{^(.*?)(/\*.*?\*/)(.*)$}s)
+	{
+		$file_content .= $1;
+		my $comment = $2;
+		$raw_file_content = $3;
+		$comment =~ tr/\n//cd;
+		$file_content .= $comment;
+	}
+	$file_content .= $raw_file_content;
 
+	my $lineno = 0;
 	foreach my $line (split /\n/, $file_content)
 	{
+		$lineno++;
 		chomp $line;
 		$line =~ s/\s*$//;
 		next if $line eq '';
@@ -153,13 +165,14 @@ foreach my $infile (@ARGV)
 				$is_node_struct = 0;
 				$supertype      = undef;
 				next if $line eq '{';
-				die "$infile:$.: expected opening brace\n";
+				die "$infile:$lineno: expected opening brace\n";
 			}
 			# second line could be node attributes
 			elsif ($subline == 2
 				&& $line =~ /^\s*pg_node_attr\(([\w(), ]*)\)$/)
 			{
-				$node_attrs = $1;
+				$node_attrs        = $1;
+				$node_attrs_lineno = $lineno;
 				# hack: don't count the line
 				$subline--;
 				next;
@@ -236,7 +249,7 @@ foreach my $infile (@ARGV)
 						else
 						{
 							die
-							  "$infile:$.: unrecognized attribute \"$attr\"\n";
+							  "$infile:$node_attrs_lineno: unrecognized attribute \"$attr\"\n";
 						}
 					}
 
@@ -330,7 +343,9 @@ foreach my $infile (@ARGV)
 					# strip space between type and "*" (pointer) */
 					$type =~ s/\s+\*$/*/;
 
-					die if $type eq '';
+					die
+					  "$infile:$lineno: cannot parse data type in \"$line\"\n"
+					  if $type eq '';
 
 					my @attrs;
 					if ($attrs)
@@ -347,7 +362,7 @@ foreach my $infile (@ARGV)
 							  )
 							{
 								die
-								  "$infile:$.: unrecognized attribute \"$attr\"\n";
+								  "$infile:$lineno: unrecognized attribute \"$attr\"\n";
 							}
 						}
 					}
@@ -362,7 +377,7 @@ foreach my $infile (@ARGV)
 			{
 				if ($is_node_struct)
 				{
-					#warn "$infile:$.: could not parse \"$line\"\n";
+					#warn "$infile:$lineno: could not parse \"$line\"\n";
 				}
 			}
 		}
@@ -552,7 +567,7 @@ _equal${n}(const $n *a, const $n *b)
 			my $tt = $1;
 			if (!defined $array_size_field)
 			{
-				die "no array size defined for $n.$f of type $t";
+				die "no array size defined for $n.$f of type $t\n";
 			}
 			if ($node_type_info{$n}->{field_types}{$array_size_field} eq
 				'List*')
@@ -597,7 +612,8 @@ _equal${n}(const $n *a, const $n *b)
 		}
 		else
 		{
-			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+			die
+			  "could not handle type \"$t\" in struct \"$n\" field \"$f\"\n";
 		}
 	}
 
@@ -814,7 +830,7 @@ _read${n}(void)
 			}
 			if (!defined $array_size_field)
 			{
-				die "no array size defined for $n.$f of type $t";
+				die "no array size defined for $n.$f of type $t\n";
 			}
 			if ($node_type_info{$n}->{field_types}{$array_size_field} eq
 				'List*')
@@ -886,7 +902,8 @@ _read${n}(void)
 		}
 		else
 		{
-			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
+			die
+			  "could not handle type \"$t\" in struct \"$n\" field \"$f\"\n";
 		}
 
 		# for read_as() without read_write_ignore, we have to read the value
0003-add-copyright-boilerplate.patchtext/x-diff; charset=us-ascii; name=0003-add-copyright-boilerplate.patchDownload
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 6816c36e2b..41d824870b 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -426,10 +426,34 @@ foreach my $infile (@ARGV)
 
 my $tmpext = ".tmp$$";
 
+# opening boilerplate for output files
+my $header_comment =
+  '/*-------------------------------------------------------------------------
+ *
+ * %s
+ *    Generated node infrastructure code
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ *  ******************************
+ *  *** DO NOT EDIT THIS FILE! ***
+ *  ******************************
+ *
+ *  It has been GENERATED by src/backend/nodes/gen_node_support.pl
+ *
+ *-------------------------------------------------------------------------
+ */
+';
+
+
 # nodetags.h
 
 open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
 
+printf $nt $header_comment, 'nodetags.h';
+
 my $i = 1;
 foreach my $n (@node_types, @extra_tags)
 {
@@ -457,6 +481,11 @@ 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 $!;
 
+printf $cff $header_comment, 'copyfuncs.funcs.c';
+printf $eff $header_comment, 'equalfuncs.funcs.c';
+printf $cfs $header_comment, 'copyfuncs.switch.c';
+printf $efs $header_comment, 'equalfuncs.switch.c';
+
 # add required #include lines to each file set
 print $cff $node_includes;
 print $eff $node_includes;
@@ -640,6 +669,11 @@ 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 $!;
 
+printf $off $header_comment, 'outfuncs.funcs.c';
+printf $rff $header_comment, 'readfuncs.funcs.c';
+printf $ofs $header_comment, 'outfuncs.switch.c';
+printf $rfs $header_comment, 'readfuncs.switch.c';
+
 print $off $node_includes;
 print $rff $node_includes;
 
0004-clean-up-on-failure.patchtext/x-diff; charset=us-ascii; name=0004-clean-up-on-failure.patchDownload
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 41d824870b..4a7902e6bf 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -34,6 +34,8 @@ sub elem
 	return grep { $_ eq $x } @_;
 }
 
+# output file names
+my @output_files;
 
 # collect node names
 my @node_types = qw(Node);
@@ -450,6 +452,7 @@ my $header_comment =
 
 # nodetags.h
 
+push @output_files, 'nodetags.h';
 open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
 
 printf $nt $header_comment, 'nodetags.h';
@@ -476,9 +479,13 @@ 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 $!;
+push @output_files, 'copyfuncs.funcs.c';
+open my $cff, '>', 'copyfuncs.funcs.c' . $tmpext or die $!;
+push @output_files, 'equalfuncs.funcs.c';
+open my $eff, '>', 'equalfuncs.funcs.c' . $tmpext or die $!;
+push @output_files, 'copyfuncs.switch.c';
+open my $cfs, '>', 'copyfuncs.switch.c' . $tmpext or die $!;
+push @output_files, 'equalfuncs.switch.c';
 open my $efs, '>', 'equalfuncs.switch.c' . $tmpext or die $!;
 
 printf $cff $header_comment, 'copyfuncs.funcs.c';
@@ -664,9 +671,13 @@ 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 $!;
+push @output_files, 'outfuncs.funcs.c';
+open my $off, '>', 'outfuncs.funcs.c' . $tmpext or die $!;
+push @output_files, 'readfuncs.funcs.c';
+open my $rff, '>', 'readfuncs.funcs.c' . $tmpext or die $!;
+push @output_files, 'outfuncs.switch.c';
+open my $ofs, '>', 'outfuncs.switch.c' . $tmpext or die $!;
+push @output_files, 'readfuncs.switch.c';
 open my $rfs, '>', 'readfuncs.switch.c' . $tmpext or die $!;
 
 printf $off $header_comment, 'outfuncs.funcs.c';
@@ -962,10 +973,26 @@ close $ofs;
 close $rfs;
 
 
-# now rename the temporary files to their final name
-foreach my $file (
-	qw(nodetags.h copyfuncs.funcs.c copyfuncs.switch.c equalfuncs.funcs.c equalfuncs.switch.c outfuncs.funcs.c outfuncs.switch.c readfuncs.funcs.c readfuncs.switch.c)
-  )
+# now rename the temporary files to their final names
+foreach my $file (@output_files)
 {
 	Catalog::RenameTempFile($file, $tmpext);
 }
+
+
+# Automatically clean up any temp files if the script fails.
+END
+{
+	# take care not to change the script's exit value
+	my $exit_code = $?;
+
+	if ($exit_code != 0)
+	{
+		foreach my $file (@output_files)
+		{
+			unlink($file . $tmpext);
+		}
+	}
+
+	$? = $exit_code;
+}
#56Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#53)
Re: automatically generating node support functions

Hi,

On 2022-07-09 16:37:22 +0200, Peter Eisentraut wrote:

On 08.07.22 22:03, Tom Lane wrote:

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

This is done now, after a brief vpath-shaped scare from the buildfarm
earlier today.

I was just rebasing meson ontop of this and was wondering whether the input
filenames were in a particular order:

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

Can we either order them alphabetically or add a comment explaining the order?

- Andres

#57Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#56)
Re: automatically generating node support functions

Andres Freund <andres@anarazel.de> writes:

I was just rebasing meson ontop of this and was wondering whether the input
filenames were in a particular order:

That annoyed me too. I think it's sensible to list the "main" input
files first, but I'd put them in our traditional pipeline order:

nodes/nodes.h \
nodes/primnodes.h \
nodes/parsenodes.h \
nodes/pathnodes.h \
nodes/plannodes.h \
nodes/execnodes.h \

The rest could probably be alphabetical. I was also wondering if
all of them really need to be read at all --- I'm unclear on what
access/sdir.h is contributing, for example.

regards, tom lane

#58Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#57)
Re: automatically generating node support functions

On 11.07.22 01:09, Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

I was just rebasing meson ontop of this and was wondering whether the input
filenames were in a particular order:

First, things used by later files need to be found in earlier files. So
that constrains the order a bit.

Second, the order of the files determines the ordering of the output.
The current order of the files reflects approximately the order how the
manual code was arranged. That could be changed. We could also just
sort the node types in the script and dump out everything alphabetically.

That annoyed me too. I think it's sensible to list the "main" input
files first, but I'd put them in our traditional pipeline order:

nodes/nodes.h \
nodes/primnodes.h \
nodes/parsenodes.h \
nodes/pathnodes.h \
nodes/plannodes.h \
nodes/execnodes.h \

The seems worth trying out.

The rest could probably be alphabetical. I was also wondering if
all of them really need to be read at all --- I'm unclear on what
access/sdir.h is contributing, for example.

could not handle type "ScanDirection" in struct "IndexScan" field
"indexorderdir"

#59Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#58)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

On 11.07.22 01:09, Tom Lane wrote:

The rest could probably be alphabetical. I was also wondering if
all of them really need to be read at all --- I'm unclear on what
access/sdir.h is contributing, for example.

could not handle type "ScanDirection" in struct "IndexScan" field
"indexorderdir"

Ah, I see. Still, we could also handle that with

push @enum_types, qw(ScanDirection);

which would be exactly one place that needs to know about this, rather
than the three (soon to be four) places that know that access/sdir.h
needs to be read and then mostly ignored.

regards, tom lane

#60Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#58)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

On 11.07.22 01:09, Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

I was just rebasing meson ontop of this and was wondering whether the input
filenames were in a particular order:

First, things used by later files need to be found in earlier files. So
that constrains the order a bit.

Yeah, the script needs to see supertype nodes before subtype nodes,
else it will not realize that the subtypes are nodes at all. However,
there is not very much cross-header-file subtyping. I experimented with
rearranging the input-file order, and found that the *only* thing that
breaks it is to put primnodes.h after pathnodes.h (which fails because
PlaceHolderVar is a subtype of Expr). You don't even need nodes.h to be
first, which astonished me initially, but then I realized that both
NodeTag and struct Node are special-cased in gen_node_support.pl,
so we know enough to get by even before reading nodes.h.

More generally, the main *nodes.h files themselves are arranged in
pipeline order, eg parsenodes.h #includes primnodes.h. So that seems
to be a pretty safe thing to rely on even if we grow more cross-header
subtyping cases later. But I'd vote for putting the incidental files
in alphabetical order.

Second, the order of the files determines the ordering of the output.
The current order of the files reflects approximately the order how the
manual code was arranged. That could be changed. We could also just
sort the node types in the script and dump out everything alphabetically.

+1 for sorting alphabetically. I experimented with that and it's a
really trivial change.

regards, tom lane

#61Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#59)
Re: automatically generating node support functions

I wrote:

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

could not handle type "ScanDirection" in struct "IndexScan" field
"indexorderdir"

Ah, I see. Still, we could also handle that with
push @enum_types, qw(ScanDirection);

I tried that, and it does work.  The only other input file we could
get rid of that way is nodes/lockoptions.h, which likewise contributes
only a couple of enum type names.  Not sure it's worth messing with
--- both ways seem crufty, though for different reasons.

regards, tom lane

#62Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#61)
Re: automatically generating node support functions

Hi,

On 2022-07-11 12:07:09 -0400, Tom Lane wrote:

I wrote:

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

could not handle type "ScanDirection" in struct "IndexScan" field
"indexorderdir"

Ah, I see. Still, we could also handle that with
push @enum_types, qw(ScanDirection);

I tried that, and it does work. The only other input file we could
get rid of that way is nodes/lockoptions.h, which likewise contributes
only a couple of enum type names.

Kinda wonder if those headers are even worth having. Plenty other enums in
primnodes.h.

Not sure it's worth messing with --- both ways seem crufty, though for
different reasons.

Not sure either.

Greetings,

Andres Freund

#63Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#60)
Re: automatically generating node support functions

I wrote:

Andres Freund <andres@anarazel.de> writes:

I was just rebasing meson ontop of this and was wondering whether the input
filenames were in a particular order:

Pushed a patch to make that a bit less random-looking.

+1 for sorting alphabetically. I experimented with that and it's a
really trivial change.

I had second thoughts about that, after noticing that alphabetizing
the NodeTag enum increased the backend's size by 20K or so. Presumably
that's telling us that a bunch of switch statements got less dense,
which might possibly cause performance issues thanks to poorer cache
behavior or the like. Maybe it's still appropriate to do, but it's
not as open-and-shut as I first thought.

More generally, I'm having second thoughts about the wisdom of
auto-generating the NodeTag enum at all. With the current setup,
I am absolutely petrified about the risk of silent ABI breakage
thanks to the enum order changing. In particular, if the meson
build fails to use the same input-file order as the makefile build,
then we will get different enum orders from the two builds, causing
an ABI discrepancy that nobody would notice until we had catastrophic
extension-compatibility issues in the field.

Of course, sorting the tags by name is a simple way to fix that.
But I'm not sure I want to buy into being forced to do it like that,
because of the switch-density question.

So at this point I'm rather attracted to the idea of reverting to
a manually-maintained NodeTag enum. We know how to avoid ABI
breakage with that, and it's not exactly the most painful part
of adding a new node type. Plus, that'd remove (most of?) the
need for gen_node_support.pl to deal with "node-tag-only" structs
at all.

Thoughts?

regards, tom lane

#64Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#63)
Re: automatically generating node support functions

On Mon, Jul 11, 2022 at 1:57 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

More generally, I'm having second thoughts about the wisdom of
auto-generating the NodeTag enum at all. With the current setup,
I am absolutely petrified about the risk of silent ABI breakage
thanks to the enum order changing. In particular, if the meson
build fails to use the same input-file order as the makefile build,
then we will get different enum orders from the two builds, causing
an ABI discrepancy that nobody would notice until we had catastrophic
extension-compatibility issues in the field.

I think this is a valid concern, but having it be automatically
generated is awfully handy, so I think it would be nice to find some
way of preserving that.

--
Robert Haas
EDB: http://www.enterprisedb.com

#65Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#63)
Re: automatically generating node support functions

Hi,

On 2022-07-11 13:57:38 -0400, Tom Lane wrote:

More generally, I'm having second thoughts about the wisdom of
auto-generating the NodeTag enum at all. With the current setup,
I am absolutely petrified about the risk of silent ABI breakage
thanks to the enum order changing. In particular, if the meson
build fails to use the same input-file order as the makefile build,
then we will get different enum orders from the two builds, causing
an ABI discrepancy that nobody would notice until we had catastrophic
extension-compatibility issues in the field.

Ugh, yes. And it already exists due to Solution.pm, although that's perhaps
less likely to be encountered "in the wild".

Additionally, I think we've had to add tags to the enum in minor releases
before and I'm afraid this now would end up looking even more awkward?

Of course, sorting the tags by name is a simple way to fix that.
But I'm not sure I want to buy into being forced to do it like that,
because of the switch-density question.

So at this point I'm rather attracted to the idea of reverting to
a manually-maintained NodeTag enum.

+0.5 - there might be a better solution to this, but I'm not immediately
seeing it.

Greetings,

Andres Freund

#66Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#64)
Re: automatically generating node support functions

Robert Haas <robertmhaas@gmail.com> writes:

On Mon, Jul 11, 2022 at 1:57 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

More generally, I'm having second thoughts about the wisdom of
auto-generating the NodeTag enum at all. With the current setup,
I am absolutely petrified about the risk of silent ABI breakage
thanks to the enum order changing.

I think this is a valid concern, but having it be automatically
generated is awfully handy, so I think it would be nice to find some
way of preserving that.

Agreed. The fundamental problem seems to be that each build toolchain
has its own source of truth about the file processing order, but we now
see that there had better be only one. We could make the sole source
of truth about that be gen_node_support.pl itself, I think.

We can't simply move the file list into gen_node_support.pl, because
(a) the build system has to know about the dependencies involved, and
(b) gen_node_support.pl wouldn't know what to do in VPATH situations.
However, we could have gen_node_support.pl contain a canonical list
of the files it expects to be handed, and make it bitch if its
arguments don't match that.

That's ugly I admit, but the set of files of interest doesn't change
so often that maintaining one additional copy would be a big problem.

Anybody got a better idea?

regards, tom lane

#67Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#65)
Re: automatically generating node support functions

Andres Freund <andres@anarazel.de> writes:

Additionally, I think we've had to add tags to the enum in minor releases
before and I'm afraid this now would end up looking even more awkward?

Peter and I already had a discussion about that upthread --- we figured
that if there's a way to manually assign a nodetag's number, you could use
that option when you have to add a tag in a stable branch. We didn't
actually build out that idea, but I can go do that, if we can solve the
more fundamental problem of keeping the autogenerated numbers stable.

One issue with that idea, of course, is that you have to remember to do
it like that when back-patching a node addition. Ideally there'd be
something that'd carp if the last autogenerated tag moves in a stable
branch, but I'm not very sure where to put that.

regards, tom lane

#68Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#66)
Re: automatically generating node support functions

On Mon, Jul 11, 2022 at 3:54 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

We can't simply move the file list into gen_node_support.pl, because
(a) the build system has to know about the dependencies involved, and
(b) gen_node_support.pl wouldn't know what to do in VPATH situations.
However, we could have gen_node_support.pl contain a canonical list
of the files it expects to be handed, and make it bitch if its
arguments don't match that.

Sorry if I'm being dense, but why do we have to duplicate the list of
files instead of having gen_node_support.pl just sort whatever list
the build system provides to it?

--
Robert Haas
EDB: http://www.enterprisedb.com

#69Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#66)
Re: automatically generating node support functions

Hi,

On 2022-07-11 15:54:22 -0400, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Mon, Jul 11, 2022 at 1:57 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

More generally, I'm having second thoughts about the wisdom of
auto-generating the NodeTag enum at all. With the current setup,
I am absolutely petrified about the risk of silent ABI breakage
thanks to the enum order changing.

I think this is a valid concern, but having it be automatically
generated is awfully handy, so I think it would be nice to find some
way of preserving that.

Agreed. The fundamental problem seems to be that each build toolchain
has its own source of truth about the file processing order, but we now
see that there had better be only one. We could make the sole source
of truth about that be gen_node_support.pl itself, I think.

We can't simply move the file list into gen_node_support.pl, because

(a) the build system has to know about the dependencies involved

Meson has builtin support for tools like gen_node_support.pl reporting which
files they've read and then to use those as dependencies. It'd not be a lot of
effort to open-code that with make either.

Doesn't look like we have dependency handling in Solution.pm?

(b) gen_node_support.pl wouldn't know what to do in VPATH situations.

We could easily add a --include-path argument or such. That'd be trivial to
set for all of the build solutions.

FWIW, for meson I already needed to add an option to specify the location of
output files (since scripts are called from the root of the build directory).

Greetings,

Andres Freund

#70Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#68)
Re: automatically generating node support functions

Hi,

On 2022-07-11 16:17:28 -0400, Robert Haas wrote:

On Mon, Jul 11, 2022 at 3:54 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

We can't simply move the file list into gen_node_support.pl, because
(a) the build system has to know about the dependencies involved, and
(b) gen_node_support.pl wouldn't know what to do in VPATH situations.
However, we could have gen_node_support.pl contain a canonical list
of the files it expects to be handed, and make it bitch if its
arguments don't match that.

Sorry if I'm being dense, but why do we have to duplicate the list of
files instead of having gen_node_support.pl just sort whatever list
the build system provides to it?

Because right now there's two buildsystems already (look at
Solution.pm). Looks like we'll briefly have three, then two again.

Greetings,

Andres Freund

#71Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#70)
Re: automatically generating node support functions

Andres Freund <andres@anarazel.de> writes:

On 2022-07-11 16:17:28 -0400, Robert Haas wrote:

Sorry if I'm being dense, but why do we have to duplicate the list of
files instead of having gen_node_support.pl just sort whatever list
the build system provides to it?

Because right now there's two buildsystems already (look at
Solution.pm). Looks like we'll briefly have three, then two again.

There are two things we need: (1) be sure that the build system knows
about all the files of interest, and (2) process them in the correct
order, which is *not* alphabetical. "Just sort" won't achieve either.

regards, tom lane

#72Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#69)
Re: automatically generating node support functions

Andres Freund <andres@anarazel.de> writes:

On 2022-07-11 15:54:22 -0400, Tom Lane wrote:

We can't simply move the file list into gen_node_support.pl, because
(a) the build system has to know about the dependencies involved

Meson has builtin support for tools like gen_node_support.pl reporting which
files they've read and then to use those as dependencies. It'd not be a lot of
effort to open-code that with make either.

If you want to provide code for that, sure, but I don't know how to do it.

(b) gen_node_support.pl wouldn't know what to do in VPATH situations.

We could easily add a --include-path argument or such. That'd be trivial to
set for all of the build solutions.

True.

regards, tom lane

#73Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#67)
1 attachment(s)
Re: automatically generating node support functions

I wrote:

Andres Freund <andres@anarazel.de> writes:

Additionally, I think we've had to add tags to the enum in minor releases
before and I'm afraid this now would end up looking even more awkward?

Peter and I already had a discussion about that upthread --- we figured
that if there's a way to manually assign a nodetag's number, you could use
that option when you have to add a tag in a stable branch. We didn't
actually build out that idea, but I can go do that, if we can solve the
more fundamental problem of keeping the autogenerated numbers stable.

One issue with that idea, of course, is that you have to remember to do
it like that when back-patching a node addition. Ideally there'd be
something that'd carp if the last autogenerated tag moves in a stable
branch, but I'm not very sure where to put that.

One way to do it is to provide logic in gen_node_support.pl to check
that, and activate that logic only in back branches. If we make that
part of the branch-making procedure, we'd not forget to do it.

Proposed patch attached.

regards, tom lane

Attachments:

add-ABI-stability-provisions-for-nodetag-list.patchtext/x-diff; charset=us-ascii; name=add-ABI-stability-provisions-for-nodetag-list.patchDownload
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 2c06609726..2c6766f537 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -34,6 +34,20 @@ sub elem
 	return grep { $_ eq $x } @_;
 }
 
+
+# ARM ABI STABILITY CHECK HERE:
+#
+# In stable branches, set $last_nodetag to the name of the last node type
+# that should receive an auto-generated nodetag number, and $last_nodetag_no
+# to its number.  The script will then complain if those values don't match
+# reality, providing a cross-check that we haven't broken ABI by adding or
+# removing nodetags.
+# In HEAD, these variables should be left undef, since we don't promise
+# ABI stability during development.
+
+my $last_nodetag    = undef;
+my $last_nodetag_no = undef;
+
 # output file names
 my @output_files;
 
@@ -88,6 +102,9 @@ my @custom_copy_equal;
 # Similarly for custom read/write implementations.
 my @custom_read_write;
 
+# Track node types with manually assigned NodeTag numbers.
+my %manual_nodetag_number;
+
 # EquivalenceClasses are never moved, so just shallow-copy the pointer
 push @scalar_types, qw(EquivalenceClass* EquivalenceMember*);
 
@@ -267,6 +284,10 @@ foreach my $infile (@ARGV)
 							# does in fact exist.
 							push @no_read_write, $in_struct;
 						}
+						elsif ($attr =~ /^nodetag_number\((\d+)\)$/)
+						{
+							$manual_nodetag_number{$in_struct} = $1;
+						}
 						else
 						{
 							die
@@ -472,14 +493,31 @@ open my $nt, '>', 'nodetags.h' . $tmpext or die $!;
 
 printf $nt $header_comment, 'nodetags.h';
 
-my $i = 1;
+my $tagno    = 0;
+my $last_tag = undef;
 foreach my $n (@node_types, @extra_tags)
 {
 	next if elem $n, @abstract_types;
-	print $nt "\tT_${n} = $i,\n";
-	$i++;
+	if (defined $manual_nodetag_number{$n})
+	{
+		# do not change $tagno or $last_tag
+		print $nt "\tT_${n} = $manual_nodetag_number{$n},\n";
+	}
+	else
+	{
+		$tagno++;
+		$last_tag = $n;
+		print $nt "\tT_${n} = $tagno,\n";
+	}
 }
 
+# verify that last auto-assigned nodetag stays stable
+die "ABI stability break: last nodetag is $last_tag not $last_nodetag\n"
+  if (defined $last_nodetag && $last_nodetag ne $last_tag);
+die
+  "ABI stability break: last nodetag number is $tagno not $last_nodetag_no\n"
+  if (defined $last_nodetag_no && $last_nodetag_no ne $tagno);
+
 close $nt;
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index adc549002a..e0b336cd28 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -63,6 +63,11 @@ typedef enum NodeTag
  *
  * - special_read_write: Has special treatment in outNode() and nodeRead().
  *
+ * - nodetag_number(VALUE): assign the specified nodetag number instead of
+ *   an auto-generated number.  Typically this would only be used in stable
+ *   branches, to give a newly-added node type a number without breaking ABI
+ *   by changing the numbers of existing node types.
+ *
  * 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, and
diff --git a/src/tools/RELEASE_CHANGES b/src/tools/RELEASE_CHANGES
index e8de724fcd..73b02fa2a4 100644
--- a/src/tools/RELEASE_CHANGES
+++ b/src/tools/RELEASE_CHANGES
@@ -107,6 +107,10 @@ Starting a New Development Cycle
   placeholder), "git rm" the previous one, and update release.sgml and
   filelist.sgml to match.
 
+* In the newly-made branch, change src/backend/nodes/gen_node_support.pl
+  to enforce ABI stability of the NodeTag list (see "ARM ABI STABILITY
+  CHECK HERE" therein).
+
 * Notify the private committers email list, to ensure all committers
   are aware of the new branch even if they're not paying close attention
   to pgsql-hackers.
#74Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#72)
Re: automatically generating node support functions

Hi,

On 2022-07-11 16:38:05 -0400, Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

On 2022-07-11 15:54:22 -0400, Tom Lane wrote:

We can't simply move the file list into gen_node_support.pl, because
(a) the build system has to know about the dependencies involved

Meson has builtin support for tools like gen_node_support.pl reporting which
files they've read and then to use those as dependencies. It'd not be a lot of
effort to open-code that with make either.

If you want to provide code for that, sure, but I don't know how to do it.

It'd basically be something like a --deps option providing a path to a file
(e.g. .deps/nodetags.Po) where the script would emit something roughly
equivalent to

path/to/nodetags.h: path/to/nodes/nodes.h
path/to/nodetags.h: path/to/nodes/primnodes.h
...
path/to/readfuncs.c: path/to/nodetags.h

It might or might not make sense to output this as one rule instead of
multiple ones.

I think our existing dependency support would do the rest.

We'd still need a dependency on node-support-stamp (or nodetags.h or ...), to
trigger the first invocation of gen_node_support.pl.

I don't think it's worth worrying about this not working reliably for non
--enable-depend builds, there's a lot more broken than this. But it might be a
bit annoying to deal with either a) creating the .deps directory even without
--enable-depend, or b) specifying --deps only optionally.

I can give it a go if this doesn't sound insane.

Greetings,

Andres Freund

#75Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#74)
Re: automatically generating node support functions

Andres Freund <andres@anarazel.de> writes:

I don't think it's worth worrying about this not working reliably for non
--enable-depend builds, there's a lot more broken than this.

Well, *I* care about that, and I won't stand for making the
non-enable-depend case significantly more broken than it is now.
In particular, what you're proposing would mean that "make clean"
followed by rebuild wouldn't be sufficient to update everything
anymore; you'd have to resort to maintainer-clean or "git clean -dfx"
after touching any node definition file, else gen_node_support.pl
would not get re-run. Up with that I will not put.

regards, tom lane

#76Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#75)
Re: automatically generating node support functions

Hi,

On 2022-07-11 18:09:15 -0400, Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

I don't think it's worth worrying about this not working reliably for non
--enable-depend builds, there's a lot more broken than this.

Well, *I* care about that, and I won't stand for making the
non-enable-depend case significantly more broken than it is now.

In particular, what you're proposing would mean that "make clean"
followed by rebuild wouldn't be sufficient to update everything
anymore; you'd have to resort to maintainer-clean or "git clean -dfx"
after touching any node definition file, else gen_node_support.pl
would not get re-run. Up with that I will not put.

I'm not sure it'd have to mean that, but we could just implement the
dependency stuff independent of the existing autodepend logic. Something like:

# ensure that dependencies of
-include gen_node_support.pl.deps
node-support-stamp: gen_node_support.pl
$(PERL) --deps $^.deps $^

I guess we'd have to distribute gen_node_support.pl.deps to make this work in
tarball builds - which is probably fine? Not really different than including
stamp files.

I'm not entirely sure how well either the existing or the sketch above works
when doing a VPATH build using tarball sources, and updating the files.

Greetings,

Andres Freund

#77Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#76)
Re: automatically generating node support functions

Andres Freund <andres@anarazel.de> writes:

I'm not entirely sure how well either the existing or the sketch above works
when doing a VPATH build using tarball sources, and updating the files.

Seems like an awful lot of effort to avoid having multiple copies
of the file list. I think we should just do what I sketched earlier,
ie put the master list into gen_node_support.pl and have it cross-check
that against its command line. If the meson system can avoid having
its own copy of the list, great; but I don't feel like we have to make
that happen for the makefiles or Solution.pm.

regards, tom lane

#78Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#77)
Re: automatically generating node support functions

On 2022-07-11 18:39:44 -0400, Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

I'm not entirely sure how well either the existing or the sketch above works
when doing a VPATH build using tarball sources, and updating the files.

Seems like an awful lot of effort to avoid having multiple copies
of the file list. I think we should just do what I sketched earlier,
ie put the master list into gen_node_support.pl and have it cross-check
that against its command line. If the meson system can avoid having
its own copy of the list, great; but I don't feel like we have to make
that happen for the makefiles or Solution.pm.

WFM.

#79Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#63)
Re: automatically generating node support functions

On 11.07.22 19:57, Tom Lane wrote:

So at this point I'm rather attracted to the idea of reverting to
a manually-maintained NodeTag enum. We know how to avoid ABI
breakage with that, and it's not exactly the most painful part
of adding a new node type.

One of the nicer features is that you now get to see the numbers
assigned to the enum tags, like

T_LockingClause = 91,
T_XmlSerialize = 92,
T_PartitionElem = 93,

so that when you get an error like "unsupported node type: %d", you can
just look up what it is.

#80Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#79)
Re: automatically generating node support functions

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

On 11.07.22 19:57, Tom Lane wrote:

So at this point I'm rather attracted to the idea of reverting to
a manually-maintained NodeTag enum. We know how to avoid ABI
breakage with that, and it's not exactly the most painful part
of adding a new node type.

One of the nicer features is that you now get to see the numbers
assigned to the enum tags, like

T_LockingClause = 91,
T_XmlSerialize = 92,
T_PartitionElem = 93,

so that when you get an error like "unsupported node type: %d", you can
just look up what it is.

Yeah, I wasn't thrilled about reverting that either. I think the
defenses I installed in eea9fa9b2 should be sufficient to deal
with the risk.

regards, tom lane

#81Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#78)
1 attachment(s)
Re: automatically generating node support functions

Just one more thing here ... I really don't like the fact that
gen_node_support.pl's response to unparseable input is to silently
ignore it. That's maybe tolerable outside a node struct, but
I think we need a higher standard inside. I experimented with
promoting the commented-out "warn" to "die", and soon learned
that there are two shortcomings:

* We can't cope with the embedded union inside A_Const.
Simplest fix is to move it outside.

* We can't cope with function-pointer fields. The only real
problem there is that some of them spread across multiple lines,
but really that was a shortcoming we'd have to fix sometime
anyway.

Proposed patch attached.

regards, tom lane

Attachments:

improve-gen_node_support-field-parsing.patchtext/x-diff; charset=us-ascii; name=improve-gen_node_support-field-parsing.patchDownload
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 96af17516a..35af4e231f 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -213,15 +213,34 @@ foreach my $infile (@ARGV)
 	}
 	$file_content .= $raw_file_content;
 
-	my $lineno = 0;
+	my $lineno   = 0;
+	my $prevline = '';
 	foreach my $line (split /\n/, $file_content)
 	{
+		# per-physical-line processing
 		$lineno++;
 		chomp $line;
 		$line =~ s/\s*$//;
 		next if $line eq '';
 		next if $line =~ /^#(define|ifdef|endif)/;
 
+		# within a node struct, don't process until we have whole logical line
+		if ($in_struct && $subline > 1)
+		{
+			if ($line =~ m/;$/)
+			{
+				# found the end, re-attach any previous line(s)
+				$line     = $prevline . $line;
+				$prevline = '';
+			}
+			else
+			{
+				# set it aside for a moment
+				$prevline .= $line . ' ';
+				next;
+			}
+		}
+
 		# we are analyzing a struct definition
 		if ($in_struct)
 		{
@@ -394,7 +413,7 @@ foreach my $infile (@ARGV)
 			}
 			# normal struct field
 			elsif ($line =~
-				/^\s*(.+)\s*\b(\w+)(\[\w+\])?\s*(?:pg_node_attr\(([\w(), ]*)\))?;/
+				/^\s*(.+)\s*\b(\w+)(\[[\w\s+]+\])?\s*(?:pg_node_attr\(([\w(), ]*)\))?;/
 			  )
 			{
 				if ($is_node_struct)
@@ -441,13 +460,46 @@ foreach my $infile (@ARGV)
 					$my_field_attrs{$name} = \@attrs;
 				}
 			}
-			else
+			# function pointer field
+			elsif ($line =~
+				/^\s*([\w\s*]+)\s*\(\*(\w+)\)\s*\((.*)\)\s*(?:pg_node_attr\(([\w(), ]*)\))?;/
+			  )
 			{
 				if ($is_node_struct)
 				{
-					#warn "$infile:$lineno: could not parse \"$line\"\n";
+					my $type  = $1;
+					my $name  = $2;
+					my $args  = $3;
+					my $attrs = $4;
+
+					my @attrs;
+					if ($attrs)
+					{
+						@attrs = split /,\s*/, $attrs;
+						foreach my $attr (@attrs)
+						{
+							if (   $attr !~ /^copy_as\(\w+\)$/
+								&& $attr !~ /^read_as\(\w+\)$/
+								&& !elem $attr,
+								qw(equal_ignore read_write_ignore))
+							{
+								die
+								  "$infile:$lineno: unrecognized attribute \"$attr\"\n";
+							}
+						}
+					}
+
+					push @my_fields, $name;
+					$my_field_types{$name} = 'function pointer';
+					$my_field_attrs{$name} = \@attrs;
 				}
 			}
+			else
+			{
+				# We're not too picky about what's outside structs,
+				# but we'd better understand everything inside.
+				die "$infile:$lineno: could not parse \"$line\"\n";
+			}
 		}
 		# not in a struct
 		else
@@ -709,6 +761,12 @@ _equal${n}(const $n *a, const $n *b)
 				  unless $equal_ignore;
 			}
 		}
+		elsif ($t eq 'function pointer')
+		{
+			# we can copy and compare as a scalar
+			print $cff "\tCOPY_SCALAR_FIELD($f);\n"    unless $copy_ignore;
+			print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore;
+		}
 		# node type
 		elsif ($t =~ /(\w+)\*/ and elem $1, @node_types)
 		{
@@ -980,6 +1038,12 @@ _read${n}(void)
 				  unless $no_read;
 			}
 		}
+		elsif ($t eq 'function pointer')
+		{
+			# We don't print these, and we can't read them either
+			die "cannot read function pointer in struct \"$n\" field \"$f\"\n"
+			  unless $no_read;
+		}
 		# Special treatments of several Path node fields
 		elsif ($t eq 'RelOptInfo*' && elem 'write_only_relids', @a)
 		{
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b0c9c5f2ef..98fe1abaa2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -303,26 +303,26 @@ typedef struct A_Expr
 
 /*
  * A_Const - a literal constant
+ *
+ * Value nodes are inline for performance.  You can treat 'val' as a node,
+ * as in IsA(&val, Integer).  'val' is not valid if isnull is true.
  */
+union ValUnion
+{
+	Node		node;
+	Integer		ival;
+	Float		fval;
+	Boolean		boolval;
+	String		sval;
+	BitString	bsval;
+};
+
 typedef struct A_Const
 {
 	pg_node_attr(custom_copy_equal, custom_read_write, no_read)
 
 	NodeTag		type;
-
-	/*
-	 * Value nodes are inline for performance.  You can treat 'val' as a node,
-	 * as in IsA(&val, Integer).  'val' is not valid if isnull is true.
-	 */
-	union ValUnion
-	{
-		Node		node;
-		Integer		ival;
-		Float		fval;
-		Boolean		boolval;
-		String		sval;
-		BitString	bsval;
-	}			val;
+	union ValUnion val;
 	bool		isnull;			/* SQL NULL constant */
 	int			location;		/* token location, or -1 if unknown */
 } A_Const;
#82Amit Kapila
amit.kapila16@gmail.com
In reply to: Peter Eisentraut (#79)
Re: automatically generating node support functions

On Wed, Jul 13, 2022 at 12:34 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:

I have a question related to commit 964d01ae90. Today, after getting
the latest code, when I compiled it on my windows machine, it lead to
a compilation error because the outfuncs.funcs.c was not regenerated.
I did the usual steps which I normally perform after getting the
latest code (a) run "perl mkvcbuild.pl" and (b) then build the code
using MSVC. Now, after that, I manually removed "node-support-stamp"
from folder src/backend/nodes/ and re-did the steps and I see that the
outfuncs.funcs.c got regenerated, and the build is also successful. I
see that there is handling to clean the file "node-support-stamp" in
nodes/Makefile but not sure how it works for windows. I think I am
missing something here. Can you please guide me?

--
With Regards,
Amit Kapila.

#83Tom Lane
tgl@sss.pgh.pa.us
In reply to: Amit Kapila (#82)
Re: automatically generating node support functions

Amit Kapila <amit.kapila16@gmail.com> writes:

I have a question related to commit 964d01ae90. Today, after getting
the latest code, when I compiled it on my windows machine, it lead to
a compilation error because the outfuncs.funcs.c was not regenerated.
I did the usual steps which I normally perform after getting the
latest code (a) run "perl mkvcbuild.pl" and (b) then build the code
using MSVC. Now, after that, I manually removed "node-support-stamp"
from folder src/backend/nodes/ and re-did the steps and I see that the
outfuncs.funcs.c got regenerated, and the build is also successful. I
see that there is handling to clean the file "node-support-stamp" in
nodes/Makefile but not sure how it works for windows. I think I am
missing something here. Can you please guide me?

More likely, we need to add something explicit to Mkvcbuild.pm
for this. I recall that it has stanzas to deal with updating
other autogenerated files; I bet we either missed that or
fat-fingered it for node-support-stamp.

regards, tom lane

#84Amit Kapila
amit.kapila16@gmail.com
In reply to: Tom Lane (#83)
Re: automatically generating node support functions

On Wed, Aug 3, 2022 at 7:16 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Amit Kapila <amit.kapila16@gmail.com> writes:

I have a question related to commit 964d01ae90. Today, after getting
the latest code, when I compiled it on my windows machine, it lead to
a compilation error because the outfuncs.funcs.c was not regenerated.
I did the usual steps which I normally perform after getting the
latest code (a) run "perl mkvcbuild.pl" and (b) then build the code
using MSVC. Now, after that, I manually removed "node-support-stamp"
from folder src/backend/nodes/ and re-did the steps and I see that the
outfuncs.funcs.c got regenerated, and the build is also successful. I
see that there is handling to clean the file "node-support-stamp" in
nodes/Makefile but not sure how it works for windows. I think I am
missing something here. Can you please guide me?

More likely, we need to add something explicit to Mkvcbuild.pm
for this. I recall that it has stanzas to deal with updating
other autogenerated files; I bet we either missed that or
fat-fingered it for node-support-stamp.

I see below logic added by commit which seems to help regenerate the
required files.

+++ b/src/tools/msvc/Solution.pm
@@ -839,6 +839,54 @@ EOF
        close($chs);
    }
+   if (IsNewer(
+           'src/backend/nodes/node-support-stamp',
+           'src/backend/nodes/gen_node_support.pl'))
...
...

Now, in commit 1349d2790b, we didn't change anything in
gen_node_support.pl but changed "typedef struct AggInfo" due to which
we expect the files like outfuncs.funcs.c gets regenerated. However,
as there is no change in gen_node_support.pl, the files didn't get
regenerated.

--
With Regards,
Amit Kapila.

#85Tom Lane
tgl@sss.pgh.pa.us
In reply to: Amit Kapila (#84)
1 attachment(s)
Re: automatically generating node support functions

Amit Kapila <amit.kapila16@gmail.com> writes:

On Wed, Aug 3, 2022 at 7:16 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

More likely, we need to add something explicit to Mkvcbuild.pm
for this. I recall that it has stanzas to deal with updating
other autogenerated files; I bet we either missed that or
fat-fingered it for node-support-stamp.

I see below logic added by commit which seems to help regenerate the
required files.

Meh ... it's not checking the data files themselves. Here's
a patch based on the logic for invoking genbki. Completely
untested, would somebody try it?

regards, tom lane

Attachments:

msvc-check-for-obsolete-node-support-1.patchtext/x-diff; charset=us-ascii; name=msvc-check-for-obsolete-node-support-1.patchDownload
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index caacb965bb..40c962d43c 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -800,36 +800,29 @@ EOF
 		close($chs);
 	}
 
-	if (IsNewer(
-			'src/backend/nodes/node-support-stamp',
-			'src/backend/nodes/gen_node_support.pl'))
+	my $nmf = Project::read_file('src/backend/nodes/Makefile');
+	$nmf =~ s{\\\r?\n}{}g;
+	$nmf =~ /^node_headers\s*:?=(.*)$/gm
+	  || croak "Could not find node_headers in Makefile\n";
+	my @node_headers = split /\s+/, $1;
+	my @node_files = map { "src/include/$_" } @node_headers;
+
+	my $need_node_support = 0;
+	foreach my $nodefile (@node_files)
 	{
-		# XXX duplicates node_headers list in src/backend/nodes/Makefile
-		my @node_headers = qw(
-		  nodes/nodes.h
-		  nodes/primnodes.h
-		  nodes/parsenodes.h
-		  nodes/pathnodes.h
-		  nodes/plannodes.h
-		  nodes/execnodes.h
-		  access/amapi.h
-		  access/sdir.h
-		  access/tableam.h
-		  access/tsmapi.h
-		  commands/event_trigger.h
-		  commands/trigger.h
-		  executor/tuptable.h
-		  foreign/fdwapi.h
-		  nodes/extensible.h
-		  nodes/lockoptions.h
-		  nodes/replnodes.h
-		  nodes/supportnodes.h
-		  nodes/value.h
-		  utils/rel.h
-		);
-
-		my @node_files = map { "src/include/$_" } @node_headers;
+		if (IsNewer('src/backend/nodes/node-support-stamp', $nodefile))
+		{
+			$need_node_support = 1;
+			last;
+		}
+	}
+	$need_node_support = 1
+	  if IsNewer(
+		'src/backend/nodes/node-support-stamp',
+		'src/backend/nodes/gen_node_support.pl');
 
+	if ($need_node_support)
+	{
 		system("perl src/backend/nodes/gen_node_support.pl --outdir src/backend/nodes @node_files");
 		open(my $f, '>', 'src/backend/nodes/node-support-stamp')
 		  || confess "Could not touch node-support-stamp";
#86Amit Kapila
amit.kapila16@gmail.com
In reply to: Tom Lane (#85)
1 attachment(s)
Re: automatically generating node support functions

On Sun, Aug 7, 2022 at 8:19 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Amit Kapila <amit.kapila16@gmail.com> writes:

On Wed, Aug 3, 2022 at 7:16 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

More likely, we need to add something explicit to Mkvcbuild.pm
for this. I recall that it has stanzas to deal with updating
other autogenerated files; I bet we either missed that or
fat-fingered it for node-support-stamp.

I see below logic added by commit which seems to help regenerate the
required files.

Meh ... it's not checking the data files themselves. Here's
a patch based on the logic for invoking genbki. Completely
untested, would somebody try it?

I tried it on commit a69959fab2 just before the commit (1349d2790b)
which was causing problems for me. On running "perl mkvcbuild.pl", I
got the below error:
wrong number of input files, expected nodes/nodes.h nodes/primnodes.h
nodes/parsenodes.h nodes/pathnodes.h nodes/plannodes.h
nodes/execnodes.h access/amapi.h access/sdir.h access/tableam.h
access/tsmapi.h commands/event_trigger.h commands/trigger.h
executor/tuptable.h foreign/fdwapi.h nodes/extensible.h
nodes/lockoptions.h nodes/replnodes.h nodes/supportnodes.h
nodes/value.h utils/rel.h

This error seems to be originating from gen_node_support.pl. If I
changed the @node_headers to what it was instead of getting it from
Makefile then the patch works and the build is also successful. See
attached.

--
With Regards,
Amit Kapila.

Attachments:

msvc-check-for-obsolete-node-support-2.patchapplication/octet-stream; name=msvc-check-for-obsolete-node-support-2.patchDownload
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index b09872e018..085f48f984 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -830,36 +830,48 @@ EOF
 		close($chs);
 	}
 
-	if (IsNewer(
-			'src/backend/nodes/node-support-stamp',
-			'src/backend/nodes/gen_node_support.pl'))
-	{
-		# XXX duplicates node_headers list in src/backend/nodes/Makefile
-		my @node_headers = qw(
-		  nodes/nodes.h
-		  nodes/primnodes.h
-		  nodes/parsenodes.h
-		  nodes/pathnodes.h
-		  nodes/plannodes.h
-		  nodes/execnodes.h
-		  access/amapi.h
-		  access/sdir.h
-		  access/tableam.h
-		  access/tsmapi.h
-		  commands/event_trigger.h
-		  commands/trigger.h
-		  executor/tuptable.h
-		  foreign/fdwapi.h
-		  nodes/extensible.h
-		  nodes/lockoptions.h
-		  nodes/replnodes.h
-		  nodes/supportnodes.h
-		  nodes/value.h
-		  utils/rel.h
+	# XXX duplicates node_headers list in src/backend/nodes/Makefile
+	my @node_headers = qw(
+		nodes/nodes.h
+		nodes/primnodes.h
+		nodes/parsenodes.h
+		nodes/pathnodes.h
+		nodes/plannodes.h
+		nodes/execnodes.h
+		access/amapi.h
+		access/sdir.h
+		access/tableam.h
+		access/tsmapi.h
+		commands/event_trigger.h
+		commands/trigger.h
+		executor/tuptable.h
+		foreign/fdwapi.h
+		nodes/extensible.h
+		nodes/lockoptions.h
+		nodes/replnodes.h
+		nodes/supportnodes.h
+		nodes/value.h
+		utils/rel.h
 		);
 
-		my @node_files = map { "src/include/$_" } @node_headers;
+	my @node_files = map { "src/include/$_" } @node_headers;
 
+	my $need_node_support = 0;
+	foreach my $nodefile (@node_files)
+	{
+		if (IsNewer('src/backend/nodes/node-support-stamp', $nodefile))
+		{
+			$need_node_support = 1;
+			last;
+		}
+	}
+	$need_node_support = 1
+	  if IsNewer(
+		'src/backend/nodes/node-support-stamp',
+		'src/backend/nodes/gen_node_support.pl');
+
+	if ($need_node_support)
+	{
 		system("perl src/backend/nodes/gen_node_support.pl --outdir src/backend/nodes @node_files");
 		open(my $f, '>', 'src/backend/nodes/node-support-stamp')
 		  || confess "Could not touch node-support-stamp";
#87Tom Lane
tgl@sss.pgh.pa.us
In reply to: Amit Kapila (#86)
1 attachment(s)
Re: automatically generating node support functions

Amit Kapila <amit.kapila16@gmail.com> writes:

On Sun, Aug 7, 2022 at 8:19 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Meh ... it's not checking the data files themselves. Here's
a patch based on the logic for invoking genbki. Completely
untested, would somebody try it?

I tried it on commit a69959fab2 just before the commit (1349d2790b)
which was causing problems for me. On running "perl mkvcbuild.pl", I
got the below error:
wrong number of input files, expected nodes/nodes.h nodes/primnodes.h
nodes/parsenodes.h nodes/pathnodes.h nodes/plannodes.h
nodes/execnodes.h access/amapi.h access/sdir.h access/tableam.h
access/tsmapi.h commands/event_trigger.h commands/trigger.h
executor/tuptable.h foreign/fdwapi.h nodes/extensible.h
nodes/lockoptions.h nodes/replnodes.h nodes/supportnodes.h
nodes/value.h utils/rel.h

Ah. It'd help if that complaint said what the command input actually
is :-(. But on looking closer, I missed stripping the empty strings
that "split" will produce at the ends of the array. I think the
attached will do the trick, and I really do want to get rid of this
copy of the file list if possible.

regards, tom lane

Attachments:

msvc-check-for-obsolete-node-support-3.patchtext/x-diff; charset=us-ascii; name=msvc-check-for-obsolete-node-support-3.patchDownload
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 86cf1b39d0..b707a09f56 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -174,7 +174,7 @@ push @scalar_types, qw(QualCost);
 
 
 ## check that we have the expected number of files on the command line
-die "wrong number of input files, expected @all_input_files\n"
+die "wrong number of input files, expected:\n@all_input_files\ngot:\n@ARGV\n"
   if ($#ARGV != $#all_input_files);
 
 ## read input
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 383b8a7c62..cc82668457 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -797,36 +797,30 @@ EOF
 		close($chs);
 	}
 
-	if (IsNewer(
-			'src/backend/nodes/node-support-stamp',
-			'src/backend/nodes/gen_node_support.pl'))
+	my $nmf = Project::read_file('src/backend/nodes/Makefile');
+	$nmf =~ s{\\\r?\n}{}g;
+	$nmf =~ /^node_headers\s*:?=(.*)$/gm
+	  || croak "Could not find node_headers in Makefile\n";
+	my @node_headers = split /\s+/, $1;
+	@node_headers = grep { $_ ne '' } @node_headers;
+	my @node_files = map { "src/include/$_" } @node_headers;
+
+	my $need_node_support = 0;
+	foreach my $nodefile (@node_files)
 	{
-		# XXX duplicates node_headers list in src/backend/nodes/Makefile
-		my @node_headers = qw(
-		  nodes/nodes.h
-		  nodes/primnodes.h
-		  nodes/parsenodes.h
-		  nodes/pathnodes.h
-		  nodes/plannodes.h
-		  nodes/execnodes.h
-		  access/amapi.h
-		  access/sdir.h
-		  access/tableam.h
-		  access/tsmapi.h
-		  commands/event_trigger.h
-		  commands/trigger.h
-		  executor/tuptable.h
-		  foreign/fdwapi.h
-		  nodes/extensible.h
-		  nodes/lockoptions.h
-		  nodes/replnodes.h
-		  nodes/supportnodes.h
-		  nodes/value.h
-		  utils/rel.h
-		);
-
-		my @node_files = map { "src/include/$_" } @node_headers;
+		if (IsNewer('src/backend/nodes/node-support-stamp', $nodefile))
+		{
+			$need_node_support = 1;
+			last;
+		}
+	}
+	$need_node_support = 1
+	  if IsNewer(
+		'src/backend/nodes/node-support-stamp',
+		'src/backend/nodes/gen_node_support.pl');
 
+	if ($need_node_support)
+	{
 		system("perl src/backend/nodes/gen_node_support.pl --outdir src/backend/nodes @node_files");
 		open(my $f, '>', 'src/backend/nodes/node-support-stamp')
 		  || confess "Could not touch node-support-stamp";
#88Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#87)
Re: automatically generating node support functions

I wrote:

Ah. It'd help if that complaint said what the command input actually
is :-(. But on looking closer, I missed stripping the empty strings
that "split" will produce at the ends of the array. I think the
attached will do the trick, and I really do want to get rid of this
copy of the file list if possible.

I tried this version on the cfbot, and it seems happy, so pushed.

regards, tom lane

#89Amit Kapila
amit.kapila16@gmail.com
In reply to: Tom Lane (#88)
Re: automatically generating node support functions

On Tue, Aug 9, 2022 at 12:14 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

Ah. It'd help if that complaint said what the command input actually
is :-(. But on looking closer, I missed stripping the empty strings
that "split" will produce at the ends of the array. I think the
attached will do the trick, and I really do want to get rid of this
copy of the file list if possible.

I tried this version on the cfbot, and it seems happy, so pushed.

Thank you. I have verified the committed patch and it works.

--
With Regards,
Amit Kapila.