Generating code for query jumbling through gen_node_support.pl

Started by Michael Paquierabout 3 years ago44 messages
#1Michael Paquier
michael@paquier.xyz
1 attachment(s)

Hi all,

This thread is a follow-up of the recent discussion about query
jumbling with DDL statements, where the conclusion was that we'd want
to generate all this code automatically for all the nodes:
/messages/by-id/36e5bffe-e989-194f-85c8-06e7bc88e6f7@amazon.com

What this patch allows to do it to compute the same query ID for
utility statements using their parsed Node state instead of their
string, meaning that things like "BEGIN", "bEGIN" or "begin" would be
treated the same, for example. But the main idea is not only that.

I have implemented that as of the attached, where the following things
are done:
- queryjumble.c is moved to src/backend/nodes/, to stick with the
other things for node equal/read/write/copy, renamed to
jumblefuncs.c.
- gen_node_support.c is extended to generate the functions and the
switch for the jumbling. There are a few exceptions, as of the Lists
and RangeTblEntry to do the jumbling consistently.
- Two pg_node_attr() are added in consistency with the existing ones:
no_jumble to discard completely a node from the the query jumbling
and jumble_ignore to discard one field from the jumble.

The patch is in a rather good shape, passes check-world and the CI,
but there are a few things that need to be discussed IMO. Things
could be perhaps divided in more patches, now the areas touched are
quite different so it did not look like a big deal to me as the
changes touch different areas and are straight-forward.

The location of the Nodes is quite invasive because we only care about
that for T_Const now in the query jumbling, and this could be
compensated with a third pg_node_attr() that tracks for the "int
location" of a Node whether it should participate in the jumbling or
not. There is also an argument where we would want to not include by
default new fields added to a Node, but that would require added more
pg_node_attr() than what's done here.

Note that the plan is to extend the normalization to some other parts
of the Nodes, like CALL and SET as mentioned on the other thread. I
have done nothing about that yet but doing so can be done in a few
lines with the facility presented here (aka just add a location
field). Hence, the normalization is consistent with the existing
queryjumble.c for the fields and the nodes processed.

In this patch, things are done so as the query ID is not computed
anymore from the query string but from the Query. I still need to
study the performance impact of that with short queries. If things
prove to be noticeable in some cases, this stuff could be switched to
use a new GUC where we could have a code path for the computation of
utilityStmt using its string as a fallback. I am not sure that I want
to enter in this level of complications, though, to keep things
simple, but that's yet to be done.

A bit more could be cut but pg_ident got in the way.. There are also
a few things for pg_stat_statements where a query ID of 0 can be
implied for utility statements in some cases.

Generating this code leads to an overall removal of code as what
queryjumble.c is generated automatically:
13 files changed, 901 insertions(+), 1113 deletions(-)

I am adding that to the next commit fest.

Thoughts?
--
Michael

Attachments:

0001-Support-for-automated-query-jumble-with-all-Nodes.patchtext/x-diff; charset=us-asciiDownload
From 55b6d9154a5524e67865299b9a73ef074bd12adf Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 7 Dec 2022 16:36:47 +0900
Subject: [PATCH] Support for automated query jumble with all Nodes

This applies query jumbling in a consistent way to all the Nodes,
including DDLs & friends.
---
 src/include/nodes/bitmapset.h         |   2 +-
 src/include/nodes/nodes.h             |   4 +
 src/include/nodes/parsenodes.h        | 267 +++++---
 src/include/nodes/plannodes.h         |   3 +-
 src/include/nodes/primnodes.h         | 419 ++++++++-----
 src/backend/nodes/Makefile            |   1 +
 src/backend/nodes/README              |   1 +
 src/backend/nodes/gen_node_support.pl |  95 ++-
 src/backend/nodes/jumblefuncs.c       | 358 +++++++++++
 src/backend/nodes/meson.build         |   1 +
 src/backend/utils/misc/Makefile       |   1 -
 src/backend/utils/misc/meson.build    |   1 -
 src/backend/utils/misc/queryjumble.c  | 861 --------------------------
 13 files changed, 901 insertions(+), 1113 deletions(-)
 create mode 100644 src/backend/nodes/jumblefuncs.c
 delete mode 100644 src/backend/utils/misc/queryjumble.c

diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 2792281658..29d531224c 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -50,7 +50,7 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
-	pg_node_attr(custom_copy_equal, special_read_write)
+	pg_node_attr(custom_copy_equal, special_read_write, no_jumble)
 
 	NodeTag		type;
 	int			nwords;			/* number of words in array */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 1f33902947..afaeeaf2c9 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -59,6 +59,8 @@ typedef enum NodeTag
  *
  * - no_copy_equal: Shorthand for both no_copy and no_equal.
  *
+ * - no_jumble: Does not support jumble() at all.
+ *
  * - no_read: Does not support nodeRead() at all.
  *
  * - nodetag_only: Does not support copyObject(), equal(), outNode(),
@@ -97,6 +99,8 @@ typedef enum NodeTag
  * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
  *   (Otherwise, compare normally.)
  *
+ * - jumble_ignore: Ignore the field for the query jumbling.
+ *
  * - 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
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6a6d3293e4..16b6591c00 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -124,47 +124,61 @@ typedef struct Query
 
 	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
 
-	QuerySource querySource;	/* where did I come from? */
+	/* where did I come from? */
+	QuerySource querySource pg_node_attr(jumble_ignore);
 
 	/*
 	 * query identifier (can be set by plugins); ignored for equal, as it
 	 * might not be set; also not stored
 	 */
-	uint64		queryId pg_node_attr(equal_ignore, read_write_ignore, read_as(0));
+	uint64		queryId pg_node_attr(equal_ignore, jumble_ignore, read_write_ignore, read_as(0));
 
-	bool		canSetTag;		/* do I set the command result tag? */
+	/* do I set the command result tag? */
+	bool		canSetTag pg_node_attr(jumble_ignore);
 
 	Node	   *utilityStmt;	/* non-null if commandType == CMD_UTILITY */
 
-	int			resultRelation; /* rtable index of target relation for
-								 * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
+	/*
+	 * rtable index of target relation for INSERT/UPDATE/DELETE/MERGE; 0 for
+	 * SELECT.
+	 */
+	int			resultRelation pg_node_attr(jumble_ignore);
 
-	bool		hasAggs;		/* has aggregates in tlist or havingQual */
-	bool		hasWindowFuncs; /* has window functions in tlist */
-	bool		hasTargetSRFs;	/* has set-returning functions in tlist */
-	bool		hasSubLinks;	/* has subquery SubLink */
-	bool		hasDistinctOn;	/* distinctClause is from DISTINCT ON */
-	bool		hasRecursive;	/* WITH RECURSIVE was specified */
-	bool		hasModifyingCTE;	/* has INSERT/UPDATE/DELETE in WITH */
-	bool		hasForUpdate;	/* FOR [KEY] UPDATE/SHARE was specified */
-	bool		hasRowSecurity; /* rewriter has applied some RLS policy */
+	/* has aggregates in tlist or havingQual */
+	bool		hasAggs pg_node_attr(jumble_ignore);
+	/* has window functions in tlist */
+	bool		hasWindowFuncs pg_node_attr(jumble_ignore);
+	/* has set-returning functions in tlist */
+	bool		hasTargetSRFs pg_node_attr(jumble_ignore);
+	bool		hasSubLinks pg_node_attr(jumble_ignore);	/* has subquery SubLink */
+	/* distinctClause is from DISTINCT ON */
+	bool		hasDistinctOn pg_node_attr(jumble_ignore);
+	/* WITH RECURSIVE was specified */
+	bool		hasRecursive pg_node_attr(jumble_ignore);
+	/* has INSERT/UPDATE/DELETE in WITH */
+	bool		hasModifyingCTE pg_node_attr(jumble_ignore);
+	/* FOR [KEY] UPDATE/SHARE was specified */
+	bool		hasForUpdate pg_node_attr(jumble_ignore);
+	/* rewriter has applied some RLS policy */
+	bool		hasRowSecurity pg_node_attr(jumble_ignore);
 
-	bool		isReturn;		/* is a RETURN statement */
+	bool		isReturn pg_node_attr(jumble_ignore);	/* is a RETURN statement */
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
-	List	   *rteperminfos;	/* list of RTEPermissionInfo nodes for the
-								 * rtable entries having perminfoindex > 0 */
+	/* list of RTEPermissionInfo nodes for the rtable entries having perminfoindex > 0 */
+	List	   *rteperminfos pg_node_attr(jumble_ignore);
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
 	List	   *mergeActionList;	/* list of actions for MERGE (only) */
-	bool		mergeUseOuterJoin;	/* whether to use outer join */
+	/* whether to use outer join */
+	bool		mergeUseOuterJoin pg_node_attr(jumble_ignore);
 
 	List	   *targetList;		/* target list (of TargetEntry) */
 
-	OverridingKind override;	/* OVERRIDING clause */
+	OverridingKind override pg_node_attr(jumble_ignore);	/* OVERRIDING clause */
 
 	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
 
@@ -192,11 +206,14 @@ typedef struct Query
 	Node	   *setOperations;	/* set-operation tree if this is top level of
 								 * a UNION/INTERSECT/EXCEPT query */
 
-	List	   *constraintDeps; /* a list of pg_constraint OIDs that the query
-								 * depends on to be semantically valid */
+	/*
+	 * A list of pg_constraint OIDs that the query depends on to be
+	 * semantically valid
+	 */
+	List	   *constraintDeps pg_node_attr(jumble_ignore);
 
-	List	   *withCheckOptions;	/* a list of WithCheckOption's (added
-									 * during rewrite) */
+	/* a list of WithCheckOption's (added during rewrite) */
+	List	   *withCheckOptions pg_node_attr(jumble_ignore);
 
 	/*
 	 * The following two fields identify the portion of the source text string
@@ -204,8 +221,10 @@ typedef struct Query
 	 * Queries, not in sub-queries.  When not set, they might both be zero, or
 	 * both be -1 meaning "unknown".
 	 */
-	int			stmt_location;	/* start location, or -1 if unknown */
-	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
+	/* start location, or -1 if unknown */
+	int			stmt_location pg_node_attr(jumble_ignore);
+	/* length in bytes; 0 means "rest of string" */
+	int			stmt_len pg_node_attr(jumble_ignore);
 } Query;
 
 
@@ -240,7 +259,8 @@ typedef struct TypeName
 	List	   *typmods;		/* type modifier expression(s) */
 	int32		typemod;		/* prespecified type modifier */
 	List	   *arrayBounds;	/* array bounds */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } TypeName;
 
 /*
@@ -260,7 +280,8 @@ typedef struct ColumnRef
 {
 	NodeTag		type;
 	List	   *fields;			/* field names (String nodes) or A_Star */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } ColumnRef;
 
 /*
@@ -270,7 +291,8 @@ typedef struct ParamRef
 {
 	NodeTag		type;
 	int			number;			/* the number of the parameter */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } ParamRef;
 
 /*
@@ -303,7 +325,8 @@ typedef struct A_Expr
 	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 */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } A_Expr;
 
 /*
@@ -329,7 +352,8 @@ typedef struct A_Const
 	NodeTag		type;
 	union ValUnion val;
 	bool		isnull;			/* SQL NULL constant */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } A_Const;
 
 /*
@@ -340,7 +364,8 @@ typedef struct TypeCast
 	NodeTag		type;
 	Node	   *arg;			/* the expression being casted */
 	TypeName   *typeName;		/* the target type */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } TypeCast;
 
 /*
@@ -351,7 +376,8 @@ typedef struct CollateClause
 	NodeTag		type;
 	Node	   *arg;			/* input expression */
 	List	   *collname;		/* possibly-qualified collation name */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } CollateClause;
 
 /*
@@ -371,7 +397,8 @@ typedef struct RoleSpec
 	NodeTag		type;
 	RoleSpecType roletype;		/* Type of this rolespec */
 	char	   *rolename;		/* filled only for ROLESPEC_CSTRING */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } RoleSpec;
 
 /*
@@ -401,7 +428,8 @@ typedef struct FuncCall
 	bool		agg_distinct;	/* arguments were labeled DISTINCT */
 	bool		func_variadic;	/* last argument was labeled VARIADIC */
 	CoercionForm funcformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } FuncCall;
 
 /*
@@ -458,7 +486,8 @@ typedef struct A_ArrayExpr
 {
 	NodeTag		type;
 	List	   *elements;		/* array element expressions */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } A_ArrayExpr;
 
 /*
@@ -485,7 +514,8 @@ typedef struct ResTarget
 	char	   *name;			/* column name or NULL */
 	List	   *indirection;	/* subscripts, field names, and '*', or NIL */
 	Node	   *val;			/* the value expression to compute or assign */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } ResTarget;
 
 /*
@@ -515,7 +545,8 @@ typedef struct SortBy
 	SortByDir	sortby_dir;		/* ASC/DESC/USING/default */
 	SortByNulls sortby_nulls;	/* NULLS FIRST/LAST */
 	List	   *useOp;			/* name of op to use, if SORTBY_USING */
-	int			location;		/* operator location, or -1 if none/unknown */
+	int			location pg_node_attr(jumble_ignore);	/* operator location, or
+														 * -1 if none/unknown */
 } SortBy;
 
 /*
@@ -536,7 +567,8 @@ typedef struct WindowDef
 	int			frameOptions;	/* frame_clause options, see below */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
-	int			location;		/* parse location, or -1 if none/unknown */
+	int			location pg_node_attr(jumble_ignore);	/* parse location, or -1
+														 * if none/unknown */
 } WindowDef;
 
 /*
@@ -626,7 +658,8 @@ typedef struct RangeTableFunc
 	List	   *namespaces;		/* list of namespaces as ResTarget */
 	List	   *columns;		/* list of RangeTableFuncCol */
 	Alias	   *alias;			/* table alias & optional column aliases */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } RangeTableFunc;
 
 /*
@@ -644,7 +677,8 @@ typedef struct RangeTableFuncCol
 	bool		is_not_null;	/* does it have NOT NULL? */
 	Node	   *colexpr;		/* column filter expression */
 	Node	   *coldefexpr;		/* column default value expression */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } RangeTableFuncCol;
 
 /*
@@ -664,7 +698,8 @@ typedef struct RangeTableSample
 	List	   *method;			/* sampling method name (possibly qualified) */
 	List	   *args;			/* argument(s) for sampling method */
 	Node	   *repeatable;		/* REPEATABLE expression, or NULL if none */
-	int			location;		/* method name location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* method name location,
+														 * or -1 if unknown */
 } RangeTableSample;
 
 /*
@@ -707,7 +742,8 @@ typedef struct ColumnDef
 	Oid			collOid;		/* collation OID (InvalidOid if not set) */
 	List	   *constraints;	/* other constraints on column */
 	List	   *fdwoptions;		/* per-column FDW options */
-	int			location;		/* parse location, or -1 if none/unknown */
+	int			location pg_node_attr(jumble_ignore);	/* parse location, or -1
+														 * if none/unknown */
 } ColumnDef;
 
 /*
@@ -781,7 +817,8 @@ typedef struct DefElem
 	Node	   *arg;			/* typically Integer, Float, String, or
 								 * TypeName */
 	DefElemAction defaction;	/* unspecified action, or SET/ADD/DROP */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } DefElem;
 
 /*
@@ -810,7 +847,8 @@ typedef struct XmlSerialize
 	XmlOptionType xmloption;	/* DOCUMENT or CONTENT */
 	Node	   *expr;
 	TypeName   *typeName;
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } XmlSerialize;
 
 /* Partitioning related definitions */
@@ -828,7 +866,8 @@ typedef struct PartitionElem
 	Node	   *expr;			/* expression to partition on, or NULL */
 	List	   *collation;		/* name of collation; NIL = default */
 	List	   *opclass;		/* name of desired opclass; NIL = default */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } PartitionElem;
 
 typedef enum PartitionStrategy
@@ -848,7 +887,8 @@ typedef struct PartitionSpec
 	NodeTag		type;
 	PartitionStrategy strategy;
 	List	   *partParams;		/* List of PartitionElems */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } PartitionSpec;
 
 /*
@@ -875,7 +915,8 @@ struct PartitionBoundSpec
 	List	   *lowerdatums;	/* List of PartitionRangeDatums */
 	List	   *upperdatums;	/* List of PartitionRangeDatums */
 
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 };
 
 /*
@@ -898,7 +939,8 @@ typedef struct PartitionRangeDatum
 	Node	   *value;			/* Const (or A_Const in raw tree), if kind is
 								 * PARTITION_RANGE_DATUM_VALUE, else NULL */
 
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } PartitionRangeDatum;
 
 /*
@@ -995,7 +1037,7 @@ typedef enum RTEKind
 
 typedef struct RangeTblEntry
 {
-	pg_node_attr(custom_read_write)
+	pg_node_attr(custom_read_write, no_jumble)
 
 	NodeTag		type;
 
@@ -1234,14 +1276,20 @@ typedef struct RangeTblFunction
 	NodeTag		type;
 
 	Node	   *funcexpr;		/* expression tree for func call */
-	int			funccolcount;	/* number of columns it contributes to RTE */
+	int			funccolcount pg_node_attr(jumble_ignore);	/* number of columns it
+															 * contributes to RTE */
 	/* These fields record the contents of a column definition list, if any: */
-	List	   *funccolnames;	/* column names (list of String) */
-	List	   *funccoltypes;	/* OID list of column type OIDs */
-	List	   *funccoltypmods; /* integer list of column typmods */
-	List	   *funccolcollations;	/* OID list of column collation OIDs */
+	List	   *funccolnames pg_node_attr(jumble_ignore);	/* column names (list of
+															 * String) */
+	List	   *funccoltypes pg_node_attr(jumble_ignore);	/* OID list of column
+															 * type OIDs */
+	List	   *funccoltypmods pg_node_attr(jumble_ignore); /* integer list of
+															 * column typmods */
+	List	   *funccolcollations pg_node_attr(jumble_ignore);	/* OID list of column
+																 * collation OIDs */
 	/* This is set during planning for use by the executor: */
-	Bitmapset  *funcparams;		/* PARAM_EXEC Param IDs affecting this func */
+	Bitmapset  *funcparams pg_node_attr(jumble_ignore); /* PARAM_EXEC Param IDs
+														 * affecting this func */
 } RangeTblFunction;
 
 /*
@@ -1348,7 +1396,9 @@ typedef struct SortGroupClause
 	Oid			eqop;			/* the equality operator ('=' op) */
 	Oid			sortop;			/* the ordering operator ('<' op), or 0 */
 	bool		nulls_first;	/* do NULLs come before normal values? */
-	bool		hashable;		/* can eqop be implemented by hashing? */
+	bool		hashable pg_node_attr(jumble_ignore);	/* can eqop be
+														 * implemented by
+														 * hashing? */
 } SortGroupClause;
 
 /*
@@ -1413,9 +1463,9 @@ typedef enum GroupingSetKind
 typedef struct GroupingSet
 {
 	NodeTag		type;
-	GroupingSetKind kind;
+	GroupingSetKind kind pg_node_attr(jumble_ignore);
 	List	   *content;
-	int			location;
+	int			location pg_node_attr(jumble_ignore);
 } GroupingSet;
 
 /*
@@ -1438,21 +1488,30 @@ typedef struct GroupingSet
 typedef struct WindowClause
 {
 	NodeTag		type;
-	char	   *name;			/* window name (NULL in an OVER clause) */
-	char	   *refname;		/* referenced window name, if any */
+	char	   *name pg_node_attr(jumble_ignore);	/* window name (NULL in an
+													 * OVER clause) */
+	char	   *refname pg_node_attr(jumble_ignore);	/* referenced window
+														 * name, if any */
 	List	   *partitionClause;	/* PARTITION BY list */
-	List	   *orderClause;	/* ORDER BY list */
+	List	   *orderClause pg_node_attr(jumble_ignore);	/* ORDER BY list */
 	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 */
-	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? */
+	/* qual to help short-circuit execution */
+	List	   *runCondition pg_node_attr(jumble_ignore);
+	/* in_range function for startOffset */
+	Oid			startInRangeFunc pg_node_attr(jumble_ignore);
+	/* in_range function for endOffset */
+	Oid			endInRangeFunc pg_node_attr(jumble_ignore);
+	/* collation for in_range tests */
+	Oid			inRangeColl pg_node_attr(jumble_ignore);
+	/* use ASC sort order for in_range tests? */
+	bool		inRangeAsc pg_node_attr(jumble_ignore);
+	/* nulls sort first for in_range tests? */
+	bool		inRangeNullsFirst pg_node_attr(jumble_ignore);
 	Index		winref;			/* ID referenced by window functions */
-	bool		copiedOrder;	/* did we copy orderClause from refname? */
+	/* did we copy orderClause from refname? */
+	bool		copiedOrder pg_node_attr(jumble_ignore);
 } WindowClause;
 
 /*
@@ -1488,7 +1547,8 @@ typedef struct WithClause
 	NodeTag		type;
 	List	   *ctes;			/* list of CommonTableExprs */
 	bool		recursive;		/* true = WITH RECURSIVE */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } WithClause;
 
 /*
@@ -1503,7 +1563,8 @@ typedef struct InferClause
 	List	   *indexElems;		/* IndexElems to infer unique index */
 	Node	   *whereClause;	/* qualification (partial-index predicate) */
 	char	   *conname;		/* Constraint name, or NULL if unnamed */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } InferClause;
 
 /*
@@ -1519,7 +1580,8 @@ typedef struct OnConflictClause
 	InferClause *infer;			/* Optional index inference clause */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	Node	   *whereClause;	/* qualifications */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } OnConflictClause;
 
 /*
@@ -1540,7 +1602,7 @@ typedef struct CTESearchClause
 	List	   *search_col_list;
 	bool		search_breadth_first;
 	char	   *search_seq_column;
-	int			location;
+	int			location pg_node_attr(jumble_ignore);
 } CTESearchClause;
 
 typedef struct CTECycleClause
@@ -1551,7 +1613,7 @@ typedef struct CTECycleClause
 	Node	   *cycle_mark_value;
 	Node	   *cycle_mark_default;
 	char	   *cycle_path_column;
-	int			location;
+	int			location pg_node_attr(jumble_ignore);
 	/* These fields are set during parse analysis: */
 	Oid			cycle_mark_type;	/* common type of _value and _default */
 	int			cycle_mark_typmod;
@@ -1567,17 +1629,26 @@ typedef struct CommonTableExpr
 	CTEMaterialize ctematerialized; /* is this an optimization fence? */
 	/* SelectStmt/InsertStmt/etc before parse analysis, Query afterwards: */
 	Node	   *ctequery;		/* the CTE's subquery */
-	CTESearchClause *search_clause;
-	CTECycleClause *cycle_clause;
-	int			location;		/* token location, or -1 if unknown */
+	CTESearchClause *search_clause pg_node_attr(jumble_ignore);
+	CTECycleClause *cycle_clause pg_node_attr(jumble_ignore);
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 	/* These fields are set during parse analysis: */
-	bool		cterecursive;	/* is this CTE actually recursive? */
-	int			cterefcount;	/* number of RTEs referencing this CTE
-								 * (excluding internal self-references) */
-	List	   *ctecolnames;	/* list of output column names */
-	List	   *ctecoltypes;	/* OID list of output column type OIDs */
-	List	   *ctecoltypmods;	/* integer list of output column typmods */
-	List	   *ctecolcollations;	/* OID list of column collation OIDs */
+	/* is this CTE actually recursive? */
+	bool		cterecursive pg_node_attr(jumble_ignore);
+	/*
+	 * Number of RTEs referencing this CTE (excluding internal
+	 * self-references)
+	 */
+	int			cterefcount pg_node_attr(jumble_ignore);
+	List	   *ctecolnames pg_node_attr(jumble_ignore);	/* list of output column
+															 * names */
+	List	   *ctecoltypes pg_node_attr(jumble_ignore);	/* OID list of output
+															 * column type OIDs */
+	List	   *ctecoltypmods pg_node_attr(jumble_ignore);	/* integer list of
+															 * output column typmods */
+	List	   *ctecolcollations pg_node_attr(jumble_ignore);	/* OID list of column
+																 * collation OIDs */
 } CommonTableExpr;
 
 /* Convenience macro to get the output tlist of a CTE's query */
@@ -1614,10 +1685,11 @@ typedef struct MergeAction
 	NodeTag		type;
 	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
 	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
-	OverridingKind override;	/* OVERRIDING clause */
+	OverridingKind override pg_node_attr(jumble_ignore);	/* OVERRIDING clause */
 	Node	   *qual;			/* transformed WHEN conditions */
 	List	   *targetList;		/* the target list (of TargetEntry) */
-	List	   *updateColnos;	/* target attribute numbers of an UPDATE */
+	List	   *updateColnos pg_node_attr(jumble_ignore);	/* target attribute
+															 * numbers of an UPDATE */
 } MergeAction;
 
 /*
@@ -1656,7 +1728,8 @@ typedef struct RawStmt
 {
 	NodeTag		type;
 	Node	   *stmt;			/* raw parse tree */
-	int			stmt_location;	/* start location, or -1 if unknown */
+	int			stmt_location pg_node_attr(jumble_ignore);	/* start location, or -1
+															 * if unknown */
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } RawStmt;
 
@@ -1827,10 +1900,14 @@ typedef struct SetOperationStmt
 	/* Eventually add fields for CORRESPONDING spec here */
 
 	/* Fields derived during parse analysis: */
-	List	   *colTypes;		/* OID list of output column type OIDs */
-	List	   *colTypmods;		/* integer list of output column typmods */
-	List	   *colCollations;	/* OID list of output column collation OIDs */
-	List	   *groupClauses;	/* a list of SortGroupClause's */
+	List	   *colTypes pg_node_attr(jumble_ignore);	/* OID list of output
+														 * column type OIDs */
+	List	   *colTypmods pg_node_attr(jumble_ignore); /* integer list of
+														 * output column typmods */
+	List	   *colCollations pg_node_attr(jumble_ignore);	/* OID list of output
+															 * column collation OIDs */
+	List	   *groupClauses pg_node_attr(jumble_ignore);	/* a list of
+															 * SortGroupClause's */
 	/* groupClauses is NIL if UNION ALL, but must be set otherwise */
 } SetOperationStmt;
 
@@ -1860,7 +1937,9 @@ typedef struct PLAssignStmt
 	List	   *indirection;	/* subscripts and field names, if any */
 	int			nnames;			/* number of names to use in ColumnRef */
 	SelectStmt *val;			/* the PL/pgSQL expression to assign */
-	int			location;		/* name's token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* name's token
+														 * location, or -1 if
+														 * unknown */
 } PLAssignStmt;
 
 
@@ -2371,7 +2450,8 @@ typedef struct Constraint
 	char	   *conname;		/* Constraint name, or NULL if unnamed */
 	bool		deferrable;		/* DEFERRABLE? */
 	bool		initdeferred;	/* INITIALLY DEFERRED? */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 
 	/* Fields used for constraints with expressions (CHECK and DEFAULT): */
 	bool		is_no_inherit;	/* is constraint non-inheritable? */
@@ -3780,7 +3860,8 @@ typedef struct PublicationObjSpec
 	PublicationObjSpecType pubobjtype;	/* type of this publication object */
 	char	   *name;
 	PublicationTable *pubtable;
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } PublicationObjSpec;
 
 typedef struct CreatePublicationStmt
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index bddfe86191..3d7e2942b1 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -99,7 +99,8 @@ typedef struct PlannedStmt
 	Node	   *utilityStmt;	/* non-null if this is utility stmt */
 
 	/* statement location in source string (copied from Query) */
-	int			stmt_location;	/* start location, or -1 if unknown */
+	int			stmt_location pg_node_attr(jumble_ignore);	/* start location, or -1
+															 * if unknown */
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } PlannedStmt;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 74f228d959..8a0458c3d3 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -86,7 +86,7 @@ typedef struct RangeVar
 	Alias	   *alias;
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(jumble_ignore);
 } RangeVar;
 
 /*
@@ -98,19 +98,29 @@ typedef struct RangeVar
 typedef struct TableFunc
 {
 	NodeTag		type;
-	List	   *ns_uris;		/* list of namespace URI expressions */
-	List	   *ns_names;		/* list of namespace names or NULL */
+	List	   *ns_uris pg_node_attr(jumble_ignore);	/* list of namespace URI
+														 * expressions */
+	List	   *ns_names pg_node_attr(jumble_ignore);	/* list of namespace
+														 * names or NULL */
 	Node	   *docexpr;		/* input document expression */
 	Node	   *rowexpr;		/* row filter expression */
-	List	   *colnames;		/* column names (list of String) */
-	List	   *coltypes;		/* OID list of column type OIDs */
-	List	   *coltypmods;		/* integer list of column typmods */
-	List	   *colcollations;	/* OID list of column collation OIDs */
+	List	   *colnames pg_node_attr(jumble_ignore);	/* column names (list of
+														 * String) */
+	List	   *coltypes pg_node_attr(jumble_ignore);	/* OID list of column
+														 * type OIDs */
+	List	   *coltypmods pg_node_attr(jumble_ignore); /* integer list of
+														 * column typmods */
+	List	   *colcollations pg_node_attr(jumble_ignore);	/* OID list of column
+															 * collation OIDs */
 	List	   *colexprs;		/* list of column filter expressions */
-	List	   *coldefexprs;	/* list of column default expressions */
-	Bitmapset  *notnulls;		/* nullability flag for each output column */
-	int			ordinalitycol;	/* counts from 0; -1 if none specified */
-	int			location;		/* token location, or -1 if unknown */
+	List	   *coldefexprs pg_node_attr(jumble_ignore);	/* list of column
+															 * default expressions */
+	Bitmapset  *notnulls pg_node_attr(jumble_ignore);	/* nullability flag for
+														 * each output column */
+	int			ordinalitycol pg_node_attr(jumble_ignore);	/* counts from 0; -1 if
+															 * none specified */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } TableFunc;
 
 /*
@@ -217,11 +227,11 @@ typedef struct Var
 	AttrNumber	varattno;
 
 	/* pg_type OID for the type of this var */
-	Oid			vartype;
+	Oid			vartype pg_node_attr(jumble_ignore);
 	/* pg_attribute typmod value */
-	int32		vartypmod;
+	int32		vartypmod pg_node_attr(jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			varcollid;
+	Oid			varcollid pg_node_attr(jumble_ignore);
 
 	/*
 	 * for subquery variables referencing outer relations; 0 in a normal var,
@@ -235,12 +245,12 @@ typedef struct Var
 	 * their varno/varattno match.
 	 */
 	/* syntactic relation index (0 if unknown) */
-	Index		varnosyn pg_node_attr(equal_ignore);
+	Index		varnosyn pg_node_attr(equal_ignore, jumble_ignore);
 	/* syntactic attribute number */
-	AttrNumber	varattnosyn pg_node_attr(equal_ignore);
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore, jumble_ignore);
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(jumble_ignore);
 } Var;
 
 /*
@@ -257,16 +267,20 @@ typedef struct Const
 
 	Expr		xpr;
 	Oid			consttype;		/* pg_type OID of the constant's datatype */
-	int32		consttypmod;	/* typmod value, if any */
-	Oid			constcollid;	/* OID of collation, or InvalidOid if none */
-	int			constlen;		/* typlen of the constant's datatype */
-	Datum		constvalue;		/* the constant's value */
-	bool		constisnull;	/* whether the constant is null (if true,
-								 * constvalue is undefined) */
-	bool		constbyval;		/* whether this datatype is passed by value.
-								 * If true, then all the information is stored
-								 * in the Datum. If false, then the Datum
-								 * contains a pointer to the information. */
+	int32		consttypmod pg_node_attr(jumble_ignore);	/* typmod value, if any */
+	Oid			constcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	int			constlen pg_node_attr(jumble_ignore);	/* typlen of the
+														 * constant's datatype */
+	Datum		constvalue pg_node_attr(jumble_ignore); /* the constant's value */
+	/* whether the constant is null (if true, constvalue is undefined) */
+	bool		constisnull pg_node_attr(jumble_ignore);
+	/*
+	 * Whether this datatype is passed by value.  If true, then all the
+	 * information is stored in the Datum.  If false, then the Datum contains
+	 * a pointer to the information.
+	 */
+	bool		constbyval pg_node_attr(jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } Const;
 
@@ -311,9 +325,12 @@ typedef struct Param
 	ParamKind	paramkind;		/* kind of parameter. See above */
 	int			paramid;		/* numeric ID for parameter */
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
-	int32		paramtypmod;	/* typmod value, if known */
-	Oid			paramcollid;	/* OID of collation, or InvalidOid if none */
-	int			location;		/* token location, or -1 if unknown */
+	int32		paramtypmod pg_node_attr(jumble_ignore);	/* typmod value, if
+															 * known */
+	Oid			paramcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } Param;
 
 /*
@@ -373,22 +390,22 @@ typedef struct Aggref
 	Oid			aggfnoid;
 
 	/* type Oid of result of the aggregate */
-	Oid			aggtype;
+	Oid			aggtype pg_node_attr(jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			aggcollid;
+	Oid			aggcollid pg_node_attr(jumble_ignore);
 
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(jumble_ignore);
 
 	/*
 	 * type Oid of aggregate's transition value; ignored for equal since it
 	 * might not be set yet
 	 */
-	Oid			aggtranstype pg_node_attr(equal_ignore);
+	Oid			aggtranstype pg_node_attr(equal_ignore, jumble_ignore);
 
 	/* type Oids of direct and aggregated args */
-	List	   *aggargtypes;
+	List	   *aggargtypes pg_node_attr(jumble_ignore);
 
 	/* direct arguments, if an ordered-set agg */
 	List	   *aggdirectargs;
@@ -406,34 +423,34 @@ typedef struct Aggref
 	Expr	   *aggfilter;
 
 	/* true if argument list was really '*' */
-	bool		aggstar;
+	bool		aggstar pg_node_attr(jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		aggvariadic;
+	bool		aggvariadic pg_node_attr(jumble_ignore);
 
 	/* aggregate kind (see pg_aggregate.h) */
-	char		aggkind;
+	char		aggkind pg_node_attr(jumble_ignore);
 
 	/* aggregate input already sorted */
-	bool		aggpresorted pg_node_attr(equal_ignore);
+	bool		aggpresorted pg_node_attr(equal_ignore, jumble_ignore);
 
 	/* > 0 if agg belongs to outer query */
-	Index		agglevelsup;
+	Index		agglevelsup pg_node_attr(jumble_ignore);
 
 	/* expected agg-splitting mode of parent Agg */
-	AggSplit	aggsplit;
+	AggSplit	aggsplit pg_node_attr(jumble_ignore);
 
 	/* unique ID within the Agg node */
-	int			aggno;
+	int			aggno pg_node_attr(jumble_ignore);
 
 	/* unique ID of transition state in the Agg */
-	int			aggtransno;
+	int			aggtransno pg_node_attr(jumble_ignore);
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(jumble_ignore);
 } Aggref;
 
 /*
@@ -465,19 +482,19 @@ typedef struct GroupingFunc
 	Expr		xpr;
 
 	/* arguments, not evaluated but kept for benefit of EXPLAIN etc. */
-	List	   *args;
+	List	   *args pg_node_attr(jumble_ignore);
 
 	/* ressortgrouprefs of arguments */
 	List	   *refs pg_node_attr(equal_ignore);
 
 	/* actual column positions set by planner */
-	List	   *cols pg_node_attr(equal_ignore);
+	List	   *cols pg_node_attr(equal_ignore, jumble_ignore);
 
 	/* same as Aggref.agglevelsup */
 	Index		agglevelsup;
 
 	/* token location */
-	int			location;
+	int			location pg_node_attr(jumble_ignore);
 } GroupingFunc;
 
 /*
@@ -487,15 +504,21 @@ typedef struct WindowFunc
 {
 	Expr		xpr;
 	Oid			winfnoid;		/* pg_proc Oid of the function */
-	Oid			wintype;		/* type Oid of result of the window function */
-	Oid			wincollid;		/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
+	Oid			wintype pg_node_attr(jumble_ignore);	/* type Oid of result of
+														 * the window function */
+	Oid			wincollid pg_node_attr(jumble_ignore);	/* OID of collation of
+														 * result */
+	Oid			inputcollid pg_node_attr(jumble_ignore);	/* OID of collation that
+															 * function should use */
 	List	   *args;			/* arguments to the window function */
 	Expr	   *aggfilter;		/* FILTER expression, if any */
 	Index		winref;			/* index of associated WindowClause */
-	bool		winstar;		/* true if argument list was really '*' */
-	bool		winagg;			/* is function a simple aggregate? */
-	int			location;		/* token location, or -1 if unknown */
+	bool		winstar pg_node_attr(jumble_ignore);	/* true if argument list
+														 * was really '*' */
+	bool		winagg pg_node_attr(jumble_ignore); /* is function a simple
+													 * aggregate? */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } WindowFunc;
 
 /*
@@ -539,11 +562,16 @@ typedef struct WindowFunc
 typedef struct SubscriptingRef
 {
 	Expr		xpr;
-	Oid			refcontainertype;	/* type of the container proper */
-	Oid			refelemtype;	/* the container type's pg_type.typelem */
-	Oid			refrestype;		/* type of the SubscriptingRef's result */
-	int32		reftypmod;		/* typmod of the result */
-	Oid			refcollid;		/* collation of result, or InvalidOid if none */
+	Oid			refcontainertype pg_node_attr(jumble_ignore);	/* type of the container
+																 * proper */
+	Oid			refelemtype pg_node_attr(jumble_ignore);	/* the container type's
+															 * pg_type.typelem */
+	Oid			refrestype pg_node_attr(jumble_ignore); /* type of the
+														 * SubscriptingRef's
+														 * result */
+	int32		reftypmod pg_node_attr(jumble_ignore);	/* typmod of the result */
+	Oid			refcollid pg_node_attr(jumble_ignore);	/* collation of result,
+														 * or InvalidOid if none */
 	List	   *refupperindexpr;	/* expressions that evaluate to upper
 									 * container indexes */
 	List	   *reflowerindexpr;	/* expressions that evaluate to lower
@@ -596,15 +624,23 @@ typedef struct FuncExpr
 {
 	Expr		xpr;
 	Oid			funcid;			/* PG_PROC OID of the function */
-	Oid			funcresulttype; /* PG_TYPE OID of result value */
-	bool		funcretset;		/* true if function returns set */
-	bool		funcvariadic;	/* true if variadic arguments have been
-								 * combined into an array last argument */
-	CoercionForm funcformat;	/* how to display this function call */
-	Oid			funccollid;		/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
+	Oid			funcresulttype pg_node_attr(jumble_ignore); /* PG_TYPE OID of result
+															 * value */
+	bool		funcretset pg_node_attr(jumble_ignore); /* true if function
+														 * returns set */
+	bool		funcvariadic pg_node_attr(jumble_ignore);	/* true if variadic
+															 * arguments have been
+															 * combined into an
+															 * array last argument */
+	CoercionForm funcformat pg_node_attr(jumble_ignore);	/* how to display this
+															 * function call */
+	Oid			funccollid pg_node_attr(jumble_ignore); /* OID of collation of
+														 * result */
+	Oid			inputcollid pg_node_attr(jumble_ignore);	/* OID of collation that
+															 * function should use */
 	List	   *args;			/* arguments to the function */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } FuncExpr;
 
 /*
@@ -625,9 +661,11 @@ typedef struct NamedArgExpr
 {
 	Expr		xpr;
 	Expr	   *arg;			/* the argument expression */
-	char	   *name;			/* the name */
+	char	   *name pg_node_attr(jumble_ignore);	/* the name */
 	int			argnumber;		/* argument's number in positional notation */
-	int			location;		/* argument name location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* argument name
+														 * location, or -1 if
+														 * unknown */
 } NamedArgExpr;
 
 /*
@@ -648,25 +686,25 @@ typedef struct OpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of underlying function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, jumble_ignore);
 
 	/* PG_TYPE OID of result value */
-	Oid			opresulttype;
+	Oid			opresulttype pg_node_attr(jumble_ignore);
 
 	/* true if operator returns set */
-	bool		opretset;
+	bool		opretset pg_node_attr(jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			opcollid;
+	Oid			opcollid pg_node_attr(jumble_ignore);
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(jumble_ignore);
 
 	/* arguments to the operator (1 or 2) */
 	List	   *args;
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(jumble_ignore);
 } OpExpr;
 
 /*
@@ -725,25 +763,25 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of comparison function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, jumble_ignore);
 
 	/* PG_PROC OID of hash func or InvalidOid */
-	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero, jumble_ignore);
 
 	/* PG_PROC OID of negator of opfuncid function or InvalidOid.  See above */
-	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero, jumble_ignore);
 
 	/* true for ANY, false for ALL */
 	bool		useOr;
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(jumble_ignore);
 
 	/* the scalar and array operands */
 	List	   *args;
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(jumble_ignore);
 } ScalarArrayOpExpr;
 
 /*
@@ -765,7 +803,8 @@ typedef struct BoolExpr
 	Expr		xpr;
 	BoolExprType boolop;
 	List	   *args;			/* arguments to this expression */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } BoolExpr;
 
 /*
@@ -838,9 +877,11 @@ typedef struct SubLink
 	SubLinkType subLinkType;	/* see above */
 	int			subLinkId;		/* ID (1..n); 0 if not MULTIEXPR */
 	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
-	List	   *operName;		/* originally specified operator name */
+	List	   *operName pg_node_attr(jumble_ignore);	/* originally specified
+														 * operator name */
 	Node	   *subselect;		/* subselect as Query* or raw parsetree */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } SubLink;
 
 /*
@@ -948,10 +989,13 @@ typedef struct FieldSelect
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	AttrNumber	fieldnum;		/* attribute number of field to extract */
-	Oid			resulttype;		/* type of the field (result type of this
-								 * node) */
-	int32		resulttypmod;	/* output typmod (usually -1) */
-	Oid			resultcollid;	/* OID of collation of the field */
+	Oid			resulttype pg_node_attr(jumble_ignore); /* type of the field
+														 * (result type of this
+														 * node) */
+	int32		resulttypmod pg_node_attr(jumble_ignore);	/* output typmod
+															 * (usually -1) */
+	Oid			resultcollid pg_node_attr(jumble_ignore);	/* OID of collation of
+															 * the field */
 } FieldSelect;
 
 /* ----------------
@@ -977,8 +1021,10 @@ typedef struct FieldStore
 	Expr		xpr;
 	Expr	   *arg;			/* input tuple value */
 	List	   *newvals;		/* new value(s) for field(s) */
-	List	   *fieldnums;		/* integer list of field attnums */
-	Oid			resulttype;		/* type of result (same as type of arg) */
+	List	   *fieldnums pg_node_attr(jumble_ignore);	/* integer list of field
+														 * attnums */
+	Oid			resulttype pg_node_attr(jumble_ignore); /* type of result (same
+														 * as type of arg) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 } FieldStore;
 
@@ -1000,10 +1046,14 @@ typedef struct RelabelType
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion expression */
-	int32		resulttypmod;	/* output typmod (usually -1) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm relabelformat; /* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	int32		resulttypmod pg_node_attr(jumble_ignore);	/* output typmod
+															 * (usually -1) */
+	Oid			resultcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	CoercionForm relabelformat pg_node_attr(jumble_ignore); /* how to display this
+															 * node */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } RelabelType;
 
 /* ----------------
@@ -1021,9 +1071,12 @@ typedef struct CoerceViaIO
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion */
 	/* output typmod is not stored, but is presumed -1 */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coerceformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	Oid			resultcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	CoercionForm coerceformat pg_node_attr(jumble_ignore);	/* how to display this
+															 * node */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } CoerceViaIO;
 
 /* ----------------
@@ -1045,10 +1098,14 @@ typedef struct ArrayCoerceExpr
 	Expr	   *arg;			/* input expression (yields an array) */
 	Expr	   *elemexpr;		/* expression representing per-element work */
 	Oid			resulttype;		/* output type of coercion (an array type) */
-	int32		resulttypmod;	/* output typmod (also element typmod) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coerceformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	int32		resulttypmod pg_node_attr(jumble_ignore);	/* output typmod (also
+															 * element typmod) */
+	Oid			resultcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	CoercionForm coerceformat pg_node_attr(jumble_ignore);	/* how to display this
+															 * node */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } ArrayCoerceExpr;
 
 /* ----------------
@@ -1070,8 +1127,10 @@ typedef struct ConvertRowtypeExpr
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type (always a composite type) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
-	CoercionForm convertformat; /* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	CoercionForm convertformat pg_node_attr(jumble_ignore); /* how to display this
+															 * node */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } ConvertRowtypeExpr;
 
 /*----------
@@ -1086,7 +1145,8 @@ typedef struct CollateExpr
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			collOid;		/* collation's OID */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } CollateExpr;
 
 /*----------
@@ -1114,12 +1174,15 @@ typedef struct CollateExpr
 typedef struct CaseExpr
 {
 	Expr		xpr;
-	Oid			casetype;		/* type of expression result */
-	Oid			casecollid;		/* OID of collation, or InvalidOid if none */
+	Oid			casetype pg_node_attr(jumble_ignore);	/* type of expression
+														 * result */
+	Oid			casecollid pg_node_attr(jumble_ignore); /* OID of collation, or
+														 * InvalidOid if none */
 	Expr	   *arg;			/* implicit equality comparison argument */
 	List	   *args;			/* the arguments (list of WHEN clauses) */
 	Expr	   *defresult;		/* the default result (ELSE clause) */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } CaseExpr;
 
 /*
@@ -1130,7 +1193,8 @@ typedef struct CaseWhen
 	Expr		xpr;
 	Expr	   *expr;			/* condition expression */
 	Expr	   *result;			/* substitution result */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } CaseWhen;
 
 /*
@@ -1157,8 +1221,10 @@ typedef struct CaseTestExpr
 {
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
+	int32		typeMod pg_node_attr(jumble_ignore);	/* typemod for
+														 * substituted value */
+	Oid			collation pg_node_attr(jumble_ignore);	/* collation for the
+														 * substituted value */
 } CaseTestExpr;
 
 /*
@@ -1172,12 +1238,17 @@ typedef struct CaseTestExpr
 typedef struct ArrayExpr
 {
 	Expr		xpr;
-	Oid			array_typeid;	/* type of expression result */
-	Oid			array_collid;	/* OID of collation, or InvalidOid if none */
-	Oid			element_typeid; /* common type of array elements */
+	Oid			array_typeid pg_node_attr(jumble_ignore);	/* type of expression
+															 * result */
+	Oid			array_collid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	Oid			element_typeid pg_node_attr(jumble_ignore); /* common type of array
+															 * elements */
 	List	   *elements;		/* the array elements or sub-arrays */
-	bool		multidims;		/* true if elements are sub-arrays */
-	int			location;		/* token location, or -1 if unknown */
+	bool		multidims pg_node_attr(jumble_ignore);	/* true if elements are
+														 * sub-arrays */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } ArrayExpr;
 
 /*
@@ -1205,7 +1276,8 @@ typedef struct RowExpr
 {
 	Expr		xpr;
 	List	   *args;			/* the fields */
-	Oid			row_typeid;		/* RECORDOID or a composite type's ID */
+	Oid			row_typeid pg_node_attr(jumble_ignore); /* RECORDOID or a
+														 * composite type's ID */
 
 	/*
 	 * row_typeid cannot be a domain over composite, only plain composite.  To
@@ -1219,9 +1291,12 @@ typedef struct RowExpr
 	 * We don't need to store a collation either.  The result type is
 	 * necessarily composite, and composite types never have a collation.
 	 */
-	CoercionForm row_format;	/* how to display this node */
-	List	   *colnames;		/* list of String, or NIL */
-	int			location;		/* token location, or -1 if unknown */
+	CoercionForm row_format pg_node_attr(jumble_ignore);	/* how to display this
+															 * node */
+	List	   *colnames pg_node_attr(jumble_ignore);	/* list of String, or
+														 * NIL */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } RowExpr;
 
 /*
@@ -1253,9 +1328,14 @@ typedef struct RowCompareExpr
 {
 	Expr		xpr;
 	RowCompareType rctype;		/* LT LE GE or GT, never EQ or NE */
-	List	   *opnos;			/* OID list of pairwise comparison ops */
-	List	   *opfamilies;		/* OID list of containing operator families */
-	List	   *inputcollids;	/* OID list of collations for comparisons */
+	List	   *opnos pg_node_attr(jumble_ignore);	/* OID list of pairwise
+													 * comparison ops */
+	List	   *opfamilies pg_node_attr(jumble_ignore); /* OID list of
+														 * containing operator
+														 * families */
+	List	   *inputcollids pg_node_attr(jumble_ignore);	/* OID list of
+															 * collations for
+															 * comparisons */
 	List	   *largs;			/* the left-hand input arguments */
 	List	   *rargs;			/* the right-hand input arguments */
 } RowCompareExpr;
@@ -1266,10 +1346,13 @@ typedef struct RowCompareExpr
 typedef struct CoalesceExpr
 {
 	Expr		xpr;
-	Oid			coalescetype;	/* type of expression result */
-	Oid			coalescecollid; /* OID of collation, or InvalidOid if none */
+	Oid			coalescetype pg_node_attr(jumble_ignore);	/* type of expression
+															 * result */
+	Oid			coalescecollid pg_node_attr(jumble_ignore); /* OID of collation, or
+															 * InvalidOid if none */
 	List	   *args;			/* the arguments */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } CoalesceExpr;
 
 /*
@@ -1284,12 +1367,16 @@ typedef enum MinMaxOp
 typedef struct MinMaxExpr
 {
 	Expr		xpr;
-	Oid			minmaxtype;		/* common type of arguments and result */
-	Oid			minmaxcollid;	/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
+	Oid			minmaxtype pg_node_attr(jumble_ignore); /* common type of
+														 * arguments and result */
+	Oid			minmaxcollid pg_node_attr(jumble_ignore);	/* OID of collation of
+															 * result */
+	Oid			inputcollid pg_node_attr(jumble_ignore);	/* OID of collation that
+															 * function should use */
 	MinMaxOp	op;				/* function to execute */
 	List	   *args;			/* the arguments */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } MinMaxExpr;
 
 /*
@@ -1325,14 +1412,18 @@ typedef struct XmlExpr
 {
 	Expr		xpr;
 	XmlExprOp	op;				/* xml function ID */
-	char	   *name;			/* name in xml(NAME foo ...) syntaxes */
+	char	   *name pg_node_attr(jumble_ignore);	/* name in xml(NAME foo
+													 * ...) syntaxes */
 	List	   *named_args;		/* non-XML expressions for xml_attributes */
-	List	   *arg_names;		/* parallel list of String values */
+	List	   *arg_names pg_node_attr(jumble_ignore);	/* parallel list of
+														 * String values */
 	List	   *args;			/* list of expressions */
-	XmlOptionType xmloption;	/* DOCUMENT or CONTENT */
-	Oid			type;			/* target type/typmod for XMLSERIALIZE */
-	int32		typmod;
-	int			location;		/* token location, or -1 if unknown */
+	XmlOptionType xmloption pg_node_attr(jumble_ignore);	/* DOCUMENT or CONTENT */
+	Oid			type pg_node_attr(jumble_ignore);	/* target type/typmod for
+													 * XMLSERIALIZE */
+	int32		typmod pg_node_attr(jumble_ignore);
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } XmlExpr;
 
 /* ----------------
@@ -1364,8 +1455,11 @@ typedef struct NullTest
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	NullTestType nulltesttype;	/* IS NULL, IS NOT NULL */
-	bool		argisrow;		/* T to perform field-by-field null checks */
-	int			location;		/* token location, or -1 if unknown */
+	bool		argisrow pg_node_attr(jumble_ignore);	/* T to perform
+														 * field-by-field null
+														 * checks */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } NullTest;
 
 /*
@@ -1387,7 +1481,8 @@ typedef struct BooleanTest
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	BoolTestType booltesttype;	/* test type */
-	int			location;		/* token location, or -1 if unknown */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } BooleanTest;
 
 /*
@@ -1404,10 +1499,14 @@ typedef struct CoerceToDomain
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* domain type ID (result type) */
-	int32		resulttypmod;	/* output typmod (currently always -1) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coercionformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	int32		resulttypmod pg_node_attr(jumble_ignore);	/* output typmod
+															 * (currently always -1) */
+	Oid			resultcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	CoercionForm coercionformat pg_node_attr(jumble_ignore);	/* how to display this
+																 * node */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } CoerceToDomain;
 
 /*
@@ -1423,9 +1522,12 @@ typedef struct CoerceToDomainValue
 {
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
-	int			location;		/* token location, or -1 if unknown */
+	int32		typeMod pg_node_attr(jumble_ignore);	/* typemod for
+														 * substituted value */
+	Oid			collation pg_node_attr(jumble_ignore);	/* collation for the
+														 * substituted value */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } CoerceToDomainValue;
 
 /*
@@ -1439,9 +1541,12 @@ typedef struct SetToDefault
 {
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
-	int			location;		/* token location, or -1 if unknown */
+	int32		typeMod pg_node_attr(jumble_ignore);	/* typemod for
+														 * substituted value */
+	Oid			collation pg_node_attr(jumble_ignore);	/* collation for the
+														 * substituted value */
+	int			location pg_node_attr(jumble_ignore);	/* token location, or -1
+														 * if unknown */
 } SetToDefault;
 
 /*
@@ -1554,13 +1659,16 @@ typedef struct TargetEntry
 	Expr		xpr;
 	Expr	   *expr;			/* expression to evaluate */
 	AttrNumber	resno;			/* attribute number (see notes above) */
-	char	   *resname;		/* name of the column (could be NULL) */
+	char	   *resname pg_node_attr(jumble_ignore);	/* name of the column
+														 * (could be NULL) */
 	Index		ressortgroupref;	/* nonzero if referenced by a sort/group
 									 * clause */
-	Oid			resorigtbl;		/* OID of column's source table */
-	AttrNumber	resorigcol;		/* column's number in source table */
-	bool		resjunk;		/* set to true to eliminate the attribute from
-								 * final target list */
+	Oid			resorigtbl pg_node_attr(jumble_ignore); /* OID of column's
+														 * source table */
+	AttrNumber	resorigcol pg_node_attr(jumble_ignore); /* column's number in
+														 * source table */
+	/* set to true to eliminate the attribute from final target list */
+	bool		resjunk pg_node_attr(jumble_ignore);
 } TargetEntry;
 
 
@@ -1642,10 +1750,13 @@ typedef struct JoinExpr
 	bool		isNatural;		/* Natural join? Will need to shape table */
 	Node	   *larg;			/* left subtree */
 	Node	   *rarg;			/* right subtree */
-	List	   *usingClause;	/* USING clause, if any (list of String) */
-	Alias	   *join_using_alias;	/* alias attached to USING clause, if any */
+	List	   *usingClause pg_node_attr(jumble_ignore);	/* USING clause, if any
+															 * (list of String) */
+	Alias	   *join_using_alias pg_node_attr(jumble_ignore);	/* alias attached to
+																 * USING clause, if any */
 	Node	   *quals;			/* qualifiers on join, if any */
-	Alias	   *alias;			/* user-written alias clause, if any */
+	Alias	   *alias pg_node_attr(jumble_ignore);	/* user-written alias
+													 * clause, if any */
 	int			rtindex;		/* RT index assigned for join, or 0 */
 } JoinExpr;
 
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 4368c30fdb..aac5f59feb 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -19,6 +19,7 @@ OBJS = \
 	copyfuncs.o \
 	equalfuncs.o \
 	extensible.o \
+	jumblefuncs.o \
 	list.o \
 	makefuncs.o \
 	multibitmapset.o \
diff --git a/src/backend/nodes/README b/src/backend/nodes/README
index 489a67eb89..e97a918e95 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -47,6 +47,7 @@ FILES IN THIS DIRECTORY (src/backend/nodes/)
     General-purpose node manipulation functions:
 	copyfuncs.c	- copy a node tree (*)
 	equalfuncs.c	- compare two node trees (*)
+	jumblefuncs.c   - compute a node tree for query jumbling (*)
 	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
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 7212bc486f..4d9cf83230 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -119,6 +119,8 @@ my %node_type_info;
 my @no_copy;
 # node types we don't want equal support for
 my @no_equal;
+# node types we don't want jumble support for
+my @no_jumble;
 # node types we don't want read support for
 my @no_read;
 # node types we don't want read/write support for
@@ -153,12 +155,13 @@ 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);
-# Lists are specially treated in all four support files, too.
+# Lists are specially treated in all five support files, too.
 # (Ideally we'd mark List as "special copy/equal" not "no copy/equal".
 # But until there's other use-cases for that, just hot-wire the tests
 # that would need to distinguish.)
 push @no_copy,            qw(List);
 push @no_equal,           qw(List);
+push @no_jumble,          qw(List);
 push @special_read_write, qw(List);
 
 # Nodes with custom copy/equal implementations are skipped from
@@ -330,6 +333,10 @@ foreach my $infile (@ARGV)
 							push @no_copy,  $in_struct;
 							push @no_equal, $in_struct;
 						}
+						elsif ($attr eq 'no_jumble')
+						{
+							push @no_jumble, $in_struct;
+						}
 						elsif ($attr eq 'no_read')
 						{
 							push @no_read, $in_struct;
@@ -455,6 +462,7 @@ foreach my $infile (@ARGV)
 								equal_as_scalar
 								equal_ignore
 								equal_ignore_if_zero
+								jumble_ignore
 								read_write_ignore
 								write_only_relids
 								write_only_nondefault_pathtarget
@@ -1223,6 +1231,91 @@ close $ofs;
 close $rfs;
 
 
+# jumblefuncs.c
+
+push @output_files, 'jumblefuncs.funcs.c';
+open my $jff, '>', "$output_path/jumblefuncs.funcs.c$tmpext" or die $!;
+push @output_files, 'jumblefuncs.switch.c';
+open my $jfs, '>', "$output_path/jumblefuncs.switch.c$tmpext" or die $!;
+
+printf $jff $header_comment, 'jumblefuncs.funcs.c';
+printf $jfs $header_comment, 'jumblefuncs.switch.c';
+
+print $jff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @nodetag_only;
+	my $struct_no_jumble = (elem $n, @no_jumble);
+
+	print $jfs "\t\t\tcase T_${n}:\n"
+	  . "\t\t\t\t_jumble${n}(jstate, expr);\n"
+	  . "\t\t\t\tbreak;\n"
+	  unless $struct_no_jumble;
+
+	print $jff "
+static void
+_jumble${n}(JumbleState *jstate, Node *node)
+{
+\t${n} *expr = (${n} *) node;\n
+" unless $struct_no_jumble;
+
+	# 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 $jumble_ignore = $struct_no_jumble;
+
+		# extract per-field attributes
+		foreach my $a (@a)
+		{
+			if ($a eq 'jumble_ignore')
+			{
+				$jumble_ignore = 1;
+			}
+		}
+
+		# node type
+		if (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+			and elem $1, @node_types)
+		{
+			print $jff "\tJUMBLE_NODE($f);\n"
+			  unless $jumble_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $jff "\tJUMBLE_LOCATION($f);\n"
+			  unless $jumble_ignore;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $jff "\tJUMBLE_STRING($f);\n"
+			  unless $jumble_ignore;
+		}
+		else
+		{
+			print $jff "\tJUMBLE_FIELD($f);\n"
+			  unless $jumble_ignore;
+		}
+	}
+
+	# Some nodes have no attributes like CheckPointStmt,
+	# so tweak things for empty contents.
+	if (scalar(@{ $node_type_info{$n}->{fields} }) == 0)
+	{
+		print $jff "\t(void) expr;\n"
+		  unless $struct_no_jumble;
+	}
+
+	print $jff "}
+" unless $struct_no_jumble;
+}
+
+close $jff;
+close $jfs;
+
 # now rename the temporary files to their final names
 foreach my $file (@output_files)
 {
diff --git a/src/backend/nodes/jumblefuncs.c b/src/backend/nodes/jumblefuncs.c
new file mode 100644
index 0000000000..b65e022d17
--- /dev/null
+++ b/src/backend/nodes/jumblefuncs.c
@@ -0,0 +1,358 @@
+/*-------------------------------------------------------------------------
+ *
+ * jumblefuncs.c
+ *	 Query normalization and fingerprinting.
+ *
+ * Normalization is a process whereby similar queries, typically differing only
+ * in their constants (though the exact rules are somewhat more subtle than
+ * that) are recognized as equivalent, and are tracked as a single entry.  This
+ * is particularly useful for non-prepared queries.
+ *
+ * Normalization is implemented by fingerprinting queries, selectively
+ * serializing those fields of each query tree's nodes that are judged to be
+ * essential to the query.  This is referred to as a query jumble.  This is
+ * distinct from a regular serialization in that various extraneous
+ * information is ignored as irrelevant or not essential to the query, such
+ * as the collations of Vars and, most notably, the values of constants.
+ *
+ * This jumble is acquired at the end of parse analysis of each query, and
+ * a 64-bit hash of it is stored into the query's Query.queryId field.
+ * The server then copies this value around, making it available in plan
+ * tree(s) generated from the query.  The executor can then use this value
+ * to blame query costs on the proper queryId.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/nodes/jumblefuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "common/hashfn.h"
+#include "miscadmin.h"
+#include "parser/scansup.h"
+#include "utils/queryjumble.h"
+
+#define JUMBLE_SIZE				1024	/* query serialization buffer size */
+
+/* GUC parameters */
+int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
+
+/* True when compute_query_id is ON, or AUTO and a module requests them */
+bool		query_id_enabled = false;
+
+static void AppendJumble(JumbleState *jstate,
+						 const unsigned char *item, Size size);
+static void RecordConstLocation(JumbleState *jstate, int location);
+static void _jumbleNode(JumbleState *jstate, Node *node);
+static void _jumbleList(JumbleState *jstate, Node *node);
+static void _jumbleRangeTblEntry(JumbleState *jstate, Node *node);
+
+/*
+ * Given a possibly multi-statement source string, confine our attention to the
+ * relevant part of the string.
+ */
+const char *
+CleanQuerytext(const char *query, int *location, int *len)
+{
+	int			query_location = *location;
+	int			query_len = *len;
+
+	/* First apply starting offset, unless it's -1 (unknown). */
+	if (query_location >= 0)
+	{
+		Assert(query_location <= strlen(query));
+		query += query_location;
+		/* Length of 0 (or -1) means "rest of string" */
+		if (query_len <= 0)
+			query_len = strlen(query);
+		else
+			Assert(query_len <= strlen(query));
+	}
+	else
+	{
+		/* If query location is unknown, distrust query_len as well */
+		query_location = 0;
+		query_len = strlen(query);
+	}
+
+	/*
+	 * Discard leading and trailing whitespace, too.  Use scanner_isspace()
+	 * not libc's isspace(), because we want to match the lexer's behavior.
+	 */
+	while (query_len > 0 && scanner_isspace(query[0]))
+		query++, query_location++, query_len--;
+	while (query_len > 0 && scanner_isspace(query[query_len - 1]))
+		query_len--;
+
+	*location = query_location;
+	*len = query_len;
+
+	return query;
+}
+
+JumbleState *
+JumbleQuery(Query *query, const char *querytext)
+{
+	JumbleState *jstate = NULL;
+
+	Assert(IsQueryIdEnabled());
+
+	jstate = (JumbleState *) palloc(sizeof(JumbleState));
+
+	/* Set up workspace for query jumbling */
+	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+	jstate->jumble_len = 0;
+	jstate->clocations_buf_size = 32;
+	jstate->clocations = (LocationLen *)
+		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+	jstate->clocations_count = 0;
+	jstate->highest_extern_param_id = 0;
+
+	/* Compute query ID and mark the Query node with it */
+	_jumbleNode(jstate, (Node *) query);
+	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+													  jstate->jumble_len,
+													  0));
+
+	/*
+	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+	 * prevent confusion with the utility-statement case.
+	 */
+	if (query->queryId == UINT64CONST(0))
+		query->queryId = UINT64CONST(1);
+
+	return jstate;
+}
+
+/*
+ * Enables query identifier computation.
+ *
+ * Third-party plugins can use this function to inform core that they require
+ * a query identifier to be computed.
+ */
+void
+EnableQueryId(void)
+{
+	if (compute_query_id != COMPUTE_QUERY_ID_OFF)
+		query_id_enabled = true;
+}
+
+/*
+ * AppendJumble: Append a value that is substantive in a given query to
+ * the current jumble.
+ */
+static void
+AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
+{
+	unsigned char *jumble = jstate->jumble;
+	Size		jumble_len = jstate->jumble_len;
+
+	/*
+	 * Whenever the jumble buffer is full, we hash the current contents and
+	 * reset the buffer to contain just that hash value, thus relying on the
+	 * hash to summarize everything so far.
+	 */
+	while (size > 0)
+	{
+		Size		part_size;
+
+		if (jumble_len >= JUMBLE_SIZE)
+		{
+			uint64		start_hash;
+
+			start_hash = DatumGetUInt64(hash_any_extended(jumble,
+														  JUMBLE_SIZE, 0));
+			memcpy(jumble, &start_hash, sizeof(start_hash));
+			jumble_len = sizeof(start_hash);
+		}
+		part_size = Min(size, JUMBLE_SIZE - jumble_len);
+		memcpy(jumble + jumble_len, item, part_size);
+		jumble_len += part_size;
+		item += part_size;
+		size -= part_size;
+	}
+	jstate->jumble_len = jumble_len;
+}
+
+/*
+ * Record location of constant within query string of query tree
+ * that is currently being walked.
+ */
+static void
+RecordConstLocation(JumbleState *jstate, int location)
+{
+	/* -1 indicates unknown or undefined location */
+	if (location >= 0)
+	{
+		/* enlarge array if needed */
+		if (jstate->clocations_count >= jstate->clocations_buf_size)
+		{
+			jstate->clocations_buf_size *= 2;
+			jstate->clocations = (LocationLen *)
+				repalloc(jstate->clocations,
+						 jstate->clocations_buf_size *
+						 sizeof(LocationLen));
+		}
+		jstate->clocations[jstate->clocations_count].location = location;
+		/* initialize lengths to -1 to simplify third-party module usage */
+		jstate->clocations[jstate->clocations_count].length = -1;
+		jstate->clocations_count++;
+	}
+}
+
+#define JUMBLE_NODE(item) \
+	_jumbleNode(jstate, (Node *) expr->item)
+#define JUMBLE_LOCATION(location) \
+	RecordConstLocation(jstate, expr->location)
+#define JUMBLE_FIELD(item) \
+	AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item))
+#define JUMBLE_FIELD_SINGLE(item) \
+	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
+#define JUMBLE_STRING(str) \
+do { \
+	if (expr->str) \
+		AppendJumble(jstate, (const unsigned char *) (expr->str), strlen(expr->str) + 1); \
+} while(0)
+
+#include "jumblefuncs.funcs.c"
+
+static void
+_jumbleNode(JumbleState *jstate, Node *node)
+{
+	Node	   *expr = node;
+
+	if (expr == NULL)
+		return;
+
+	/* Guard against stack overflow due to overly complex expressions */
+	check_stack_depth();
+
+	/*
+	 * We always emit the node's NodeTag, then any additional fields that are
+	 * considered significant, and then we recurse to any child nodes.
+	 */
+	JUMBLE_FIELD(type);
+
+	switch (nodeTag(expr))
+	{
+#include "jumblefuncs.switch.c"
+
+		case T_List:
+		case T_IntList:
+		case T_OidList:
+		case T_XidList:
+			_jumbleList(jstate, expr);
+			break;
+
+		case T_RangeTblEntry:
+			_jumbleRangeTblEntry(jstate, expr);
+			break;
+
+		default:
+			/* Only a warning, since we can stumble along anyway */
+			elog(WARNING, "unrecognized node type: %d",
+				 (int) nodeTag(expr));
+			break;
+	}
+
+	/* Special cases */
+	switch (nodeTag(expr))
+	{
+		case T_Param:
+			{
+				Param	   *p = (Param *) node;
+
+				/* Also, track the highest external Param id */
+				if (p->paramkind == PARAM_EXTERN &&
+					p->paramid > jstate->highest_extern_param_id)
+					jstate->highest_extern_param_id = p->paramid;
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+_jumbleList(JumbleState *jstate, Node *node)
+{
+	List	   *expr = (List *) node;
+	ListCell   *l;
+
+	switch (expr->type)
+	{
+		case T_List:
+			foreach(l, expr)
+				_jumbleNode(jstate, lfirst(l));
+			break;
+		case T_IntList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_int(l));
+			break;
+		case T_OidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_oid(l));
+			break;
+		case T_XidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_xid(l));
+			break;
+		default:
+			elog(ERROR, "unrecognized list node type: %d",
+				 (int) expr->type);
+			return;
+	}
+}
+
+static void
+_jumbleRangeTblEntry(JumbleState *jstate, Node *node)
+{
+	RangeTblEntry *expr = (RangeTblEntry *) node;
+
+	JUMBLE_FIELD(rtekind);
+	switch (expr->rtekind)
+	{
+		case RTE_RELATION:
+			JUMBLE_FIELD(relid);
+			JUMBLE_NODE(tablesample);
+			JUMBLE_FIELD(inh);
+			break;
+		case RTE_SUBQUERY:
+			JUMBLE_NODE(subquery);
+			break;
+		case RTE_JOIN:
+			JUMBLE_FIELD(jointype);
+			break;
+		case RTE_FUNCTION:
+			JUMBLE_NODE(functions);
+			break;
+		case RTE_TABLEFUNC:
+			JUMBLE_NODE(tablefunc);
+			break;
+		case RTE_VALUES:
+			JUMBLE_NODE(values_lists);
+			break;
+		case RTE_CTE:
+
+			/*
+			 * Depending on the CTE name here isn't ideal, but it's the only
+			 * info we have to identify the referenced WITH item.
+			 */
+			JUMBLE_STRING(ctename);
+			JUMBLE_FIELD(ctelevelsup);
+			break;
+		case RTE_NAMEDTUPLESTORE:
+			JUMBLE_STRING(enrname);
+			break;
+		case RTE_RESULT:
+			break;
+		default:
+			elog(ERROR, "unrecognized RTE kind: %d", (int) expr->rtekind);
+			break;
+	}
+}
diff --git a/src/backend/nodes/meson.build b/src/backend/nodes/meson.build
index c4f3897ef2..323a1f07d0 100644
--- a/src/backend/nodes/meson.build
+++ b/src/backend/nodes/meson.build
@@ -18,6 +18,7 @@ backend_sources += files(
 nodefunc_sources = files(
   'copyfuncs.c',
   'equalfuncs.c',
+  'jumblefuncs.c',
   'outfuncs.c',
   'readfuncs.c',
 )
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index b9ee4eb48a..2910032930 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -26,7 +26,6 @@ OBJS = \
 	pg_rusage.o \
 	ps_status.o \
 	queryenvironment.o \
-	queryjumble.o \
 	rls.o \
 	sampling.o \
 	superuser.o \
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index e7a9730229..6caa2f5a2c 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -9,7 +9,6 @@ backend_sources += files(
   'pg_rusage.c',
   'ps_status.c',
   'queryenvironment.c',
-  'queryjumble.c',
   'rls.c',
   'sampling.c',
   'superuser.c',
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
deleted file mode 100644
index 0ace74de78..0000000000
--- a/src/backend/utils/misc/queryjumble.c
+++ /dev/null
@@ -1,861 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * queryjumble.c
- *	 Query normalization and fingerprinting.
- *
- * Normalization is a process whereby similar queries, typically differing only
- * in their constants (though the exact rules are somewhat more subtle than
- * that) are recognized as equivalent, and are tracked as a single entry.  This
- * is particularly useful for non-prepared queries.
- *
- * Normalization is implemented by fingerprinting queries, selectively
- * serializing those fields of each query tree's nodes that are judged to be
- * essential to the query.  This is referred to as a query jumble.  This is
- * distinct from a regular serialization in that various extraneous
- * information is ignored as irrelevant or not essential to the query, such
- * as the collations of Vars and, most notably, the values of constants.
- *
- * This jumble is acquired at the end of parse analysis of each query, and
- * a 64-bit hash of it is stored into the query's Query.queryId field.
- * The server then copies this value around, making it available in plan
- * tree(s) generated from the query.  The executor can then use this value
- * to blame query costs on the proper queryId.
- *
- * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- *	  src/backend/utils/misc/queryjumble.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "common/hashfn.h"
-#include "miscadmin.h"
-#include "parser/scansup.h"
-#include "utils/queryjumble.h"
-
-#define JUMBLE_SIZE				1024	/* query serialization buffer size */
-
-/* GUC parameters */
-int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
-
-/* True when compute_query_id is ON, or AUTO and a module requests them */
-bool		query_id_enabled = false;
-
-static uint64 compute_utility_query_id(const char *query_text,
-									   int query_location, int query_len);
-static void AppendJumble(JumbleState *jstate,
-						 const unsigned char *item, Size size);
-static void JumbleQueryInternal(JumbleState *jstate, Query *query);
-static void JumbleRangeTable(JumbleState *jstate, List *rtable);
-static void JumbleRowMarks(JumbleState *jstate, List *rowMarks);
-static void JumbleExpr(JumbleState *jstate, Node *node);
-static void RecordConstLocation(JumbleState *jstate, int location);
-
-/*
- * Given a possibly multi-statement source string, confine our attention to the
- * relevant part of the string.
- */
-const char *
-CleanQuerytext(const char *query, int *location, int *len)
-{
-	int			query_location = *location;
-	int			query_len = *len;
-
-	/* First apply starting offset, unless it's -1 (unknown). */
-	if (query_location >= 0)
-	{
-		Assert(query_location <= strlen(query));
-		query += query_location;
-		/* Length of 0 (or -1) means "rest of string" */
-		if (query_len <= 0)
-			query_len = strlen(query);
-		else
-			Assert(query_len <= strlen(query));
-	}
-	else
-	{
-		/* If query location is unknown, distrust query_len as well */
-		query_location = 0;
-		query_len = strlen(query);
-	}
-
-	/*
-	 * Discard leading and trailing whitespace, too.  Use scanner_isspace()
-	 * not libc's isspace(), because we want to match the lexer's behavior.
-	 */
-	while (query_len > 0 && scanner_isspace(query[0]))
-		query++, query_location++, query_len--;
-	while (query_len > 0 && scanner_isspace(query[query_len - 1]))
-		query_len--;
-
-	*location = query_location;
-	*len = query_len;
-
-	return query;
-}
-
-JumbleState *
-JumbleQuery(Query *query, const char *querytext)
-{
-	JumbleState *jstate = NULL;
-
-	Assert(IsQueryIdEnabled());
-
-	if (query->utilityStmt)
-	{
-		query->queryId = compute_utility_query_id(querytext,
-												  query->stmt_location,
-												  query->stmt_len);
-	}
-	else
-	{
-		jstate = (JumbleState *) palloc(sizeof(JumbleState));
-
-		/* Set up workspace for query jumbling */
-		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-		jstate->jumble_len = 0;
-		jstate->clocations_buf_size = 32;
-		jstate->clocations = (LocationLen *)
-			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-		jstate->clocations_count = 0;
-		jstate->highest_extern_param_id = 0;
-
-		/* Compute query ID and mark the Query node with it */
-		JumbleQueryInternal(jstate, query);
-		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-														  jstate->jumble_len,
-														  0));
-
-		/*
-		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-		 * prevent confusion with the utility-statement case.
-		 */
-		if (query->queryId == UINT64CONST(0))
-			query->queryId = UINT64CONST(1);
-	}
-
-	return jstate;
-}
-
-/*
- * Enables query identifier computation.
- *
- * Third-party plugins can use this function to inform core that they require
- * a query identifier to be computed.
- */
-void
-EnableQueryId(void)
-{
-	if (compute_query_id != COMPUTE_QUERY_ID_OFF)
-		query_id_enabled = true;
-}
-
-/*
- * Compute a query identifier for the given utility query string.
- */
-static uint64
-compute_utility_query_id(const char *query_text, int query_location, int query_len)
-{
-	uint64		queryId;
-	const char *sql;
-
-	/*
-	 * Confine our attention to the relevant part of the string, if the query
-	 * is a portion of a multi-statement source string.
-	 */
-	sql = CleanQuerytext(query_text, &query_location, &query_len);
-
-	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
-											   query_len, 0));
-
-	/*
-	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
-	 * 2 instead, queryID 1 is already in use for normal statements.
-	 */
-	if (queryId == UINT64CONST(0))
-		queryId = UINT64CONST(2);
-
-	return queryId;
-}
-
-/*
- * AppendJumble: Append a value that is substantive in a given query to
- * the current jumble.
- */
-static void
-AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
-{
-	unsigned char *jumble = jstate->jumble;
-	Size		jumble_len = jstate->jumble_len;
-
-	/*
-	 * Whenever the jumble buffer is full, we hash the current contents and
-	 * reset the buffer to contain just that hash value, thus relying on the
-	 * hash to summarize everything so far.
-	 */
-	while (size > 0)
-	{
-		Size		part_size;
-
-		if (jumble_len >= JUMBLE_SIZE)
-		{
-			uint64		start_hash;
-
-			start_hash = DatumGetUInt64(hash_any_extended(jumble,
-														  JUMBLE_SIZE, 0));
-			memcpy(jumble, &start_hash, sizeof(start_hash));
-			jumble_len = sizeof(start_hash);
-		}
-		part_size = Min(size, JUMBLE_SIZE - jumble_len);
-		memcpy(jumble + jumble_len, item, part_size);
-		jumble_len += part_size;
-		item += part_size;
-		size -= part_size;
-	}
-	jstate->jumble_len = jumble_len;
-}
-
-/*
- * Wrappers around AppendJumble to encapsulate details of serialization
- * of individual local variable elements.
- */
-#define APP_JUMB(item) \
-	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
-#define APP_JUMB_STRING(str) \
-	AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1)
-
-/*
- * JumbleQueryInternal: Selectively serialize the query tree, appending
- * significant data to the "query jumble" while ignoring nonsignificant data.
- *
- * Rule of thumb for what to include is that we should ignore anything not
- * semantically significant (such as alias names) as well as anything that can
- * be deduced from child nodes (else we'd just be double-hashing that piece
- * of information).
- */
-static void
-JumbleQueryInternal(JumbleState *jstate, Query *query)
-{
-	Assert(IsA(query, Query));
-	Assert(query->utilityStmt == NULL);
-
-	APP_JUMB(query->commandType);
-	/* resultRelation is usually predictable from commandType */
-	JumbleExpr(jstate, (Node *) query->cteList);
-	JumbleRangeTable(jstate, query->rtable);
-	JumbleExpr(jstate, (Node *) query->jointree);
-	JumbleExpr(jstate, (Node *) query->mergeActionList);
-	JumbleExpr(jstate, (Node *) query->targetList);
-	JumbleExpr(jstate, (Node *) query->onConflict);
-	JumbleExpr(jstate, (Node *) query->returningList);
-	JumbleExpr(jstate, (Node *) query->groupClause);
-	APP_JUMB(query->groupDistinct);
-	JumbleExpr(jstate, (Node *) query->groupingSets);
-	JumbleExpr(jstate, query->havingQual);
-	JumbleExpr(jstate, (Node *) query->windowClause);
-	JumbleExpr(jstate, (Node *) query->distinctClause);
-	JumbleExpr(jstate, (Node *) query->sortClause);
-	JumbleExpr(jstate, query->limitOffset);
-	JumbleExpr(jstate, query->limitCount);
-	APP_JUMB(query->limitOption);
-	JumbleRowMarks(jstate, query->rowMarks);
-	JumbleExpr(jstate, query->setOperations);
-}
-
-/*
- * Jumble a range table
- */
-static void
-JumbleRangeTable(JumbleState *jstate, List *rtable)
-{
-	ListCell   *lc;
-
-	foreach(lc, rtable)
-	{
-		RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-		APP_JUMB(rte->rtekind);
-		switch (rte->rtekind)
-		{
-			case RTE_RELATION:
-				APP_JUMB(rte->relid);
-				JumbleExpr(jstate, (Node *) rte->tablesample);
-				APP_JUMB(rte->inh);
-				break;
-			case RTE_SUBQUERY:
-				JumbleQueryInternal(jstate, rte->subquery);
-				break;
-			case RTE_JOIN:
-				APP_JUMB(rte->jointype);
-				break;
-			case RTE_FUNCTION:
-				JumbleExpr(jstate, (Node *) rte->functions);
-				break;
-			case RTE_TABLEFUNC:
-				JumbleExpr(jstate, (Node *) rte->tablefunc);
-				break;
-			case RTE_VALUES:
-				JumbleExpr(jstate, (Node *) rte->values_lists);
-				break;
-			case RTE_CTE:
-
-				/*
-				 * Depending on the CTE name here isn't ideal, but it's the
-				 * only info we have to identify the referenced WITH item.
-				 */
-				APP_JUMB_STRING(rte->ctename);
-				APP_JUMB(rte->ctelevelsup);
-				break;
-			case RTE_NAMEDTUPLESTORE:
-				APP_JUMB_STRING(rte->enrname);
-				break;
-			case RTE_RESULT:
-				break;
-			default:
-				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
-				break;
-		}
-	}
-}
-
-/*
- * Jumble a rowMarks list
- */
-static void
-JumbleRowMarks(JumbleState *jstate, List *rowMarks)
-{
-	ListCell   *lc;
-
-	foreach(lc, rowMarks)
-	{
-		RowMarkClause *rowmark = lfirst_node(RowMarkClause, lc);
-
-		if (!rowmark->pushedDown)
-		{
-			APP_JUMB(rowmark->rti);
-			APP_JUMB(rowmark->strength);
-			APP_JUMB(rowmark->waitPolicy);
-		}
-	}
-}
-
-/*
- * Jumble an expression tree
- *
- * In general this function should handle all the same node types that
- * expression_tree_walker() does, and therefore it's coded to be as parallel
- * to that function as possible.  However, since we are only invoked on
- * queries immediately post-parse-analysis, we need not handle node types
- * that only appear in planning.
- *
- * Note: the reason we don't simply use expression_tree_walker() is that the
- * point of that function is to support tree walkers that don't care about
- * most tree node types, but here we care about all types.  We should complain
- * about any unrecognized node type.
- */
-static void
-JumbleExpr(JumbleState *jstate, Node *node)
-{
-	ListCell   *temp;
-
-	if (node == NULL)
-		return;
-
-	/* Guard against stack overflow due to overly complex expressions */
-	check_stack_depth();
-
-	/*
-	 * We always emit the node's NodeTag, then any additional fields that are
-	 * considered significant, and then we recurse to any child nodes.
-	 */
-	APP_JUMB(node->type);
-
-	switch (nodeTag(node))
-	{
-		case T_Var:
-			{
-				Var		   *var = (Var *) node;
-
-				APP_JUMB(var->varno);
-				APP_JUMB(var->varattno);
-				APP_JUMB(var->varlevelsup);
-			}
-			break;
-		case T_Const:
-			{
-				Const	   *c = (Const *) node;
-
-				/* We jumble only the constant's type, not its value */
-				APP_JUMB(c->consttype);
-				/* Also, record its parse location for query normalization */
-				RecordConstLocation(jstate, c->location);
-			}
-			break;
-		case T_Param:
-			{
-				Param	   *p = (Param *) node;
-
-				APP_JUMB(p->paramkind);
-				APP_JUMB(p->paramid);
-				APP_JUMB(p->paramtype);
-				/* Also, track the highest external Param id */
-				if (p->paramkind == PARAM_EXTERN &&
-					p->paramid > jstate->highest_extern_param_id)
-					jstate->highest_extern_param_id = p->paramid;
-			}
-			break;
-		case T_Aggref:
-			{
-				Aggref	   *expr = (Aggref *) node;
-
-				APP_JUMB(expr->aggfnoid);
-				JumbleExpr(jstate, (Node *) expr->aggdirectargs);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggorder);
-				JumbleExpr(jstate, (Node *) expr->aggdistinct);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_GroupingFunc:
-			{
-				GroupingFunc *grpnode = (GroupingFunc *) node;
-
-				JumbleExpr(jstate, (Node *) grpnode->refs);
-				APP_JUMB(grpnode->agglevelsup);
-			}
-			break;
-		case T_WindowFunc:
-			{
-				WindowFunc *expr = (WindowFunc *) node;
-
-				APP_JUMB(expr->winfnoid);
-				APP_JUMB(expr->winref);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_SubscriptingRef:
-			{
-				SubscriptingRef *sbsref = (SubscriptingRef *) node;
-
-				JumbleExpr(jstate, (Node *) sbsref->refupperindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refassgnexpr);
-			}
-			break;
-		case T_FuncExpr:
-			{
-				FuncExpr   *expr = (FuncExpr *) node;
-
-				APP_JUMB(expr->funcid);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_NamedArgExpr:
-			{
-				NamedArgExpr *nae = (NamedArgExpr *) node;
-
-				APP_JUMB(nae->argnumber);
-				JumbleExpr(jstate, (Node *) nae->arg);
-			}
-			break;
-		case T_OpExpr:
-		case T_DistinctExpr:	/* struct-equivalent to OpExpr */
-		case T_NullIfExpr:		/* struct-equivalent to OpExpr */
-			{
-				OpExpr	   *expr = (OpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_ScalarArrayOpExpr:
-			{
-				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				APP_JUMB(expr->useOr);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_BoolExpr:
-			{
-				BoolExpr   *expr = (BoolExpr *) node;
-
-				APP_JUMB(expr->boolop);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_SubLink:
-			{
-				SubLink    *sublink = (SubLink *) node;
-
-				APP_JUMB(sublink->subLinkType);
-				APP_JUMB(sublink->subLinkId);
-				JumbleExpr(jstate, (Node *) sublink->testexpr);
-				JumbleQueryInternal(jstate, castNode(Query, sublink->subselect));
-			}
-			break;
-		case T_FieldSelect:
-			{
-				FieldSelect *fs = (FieldSelect *) node;
-
-				APP_JUMB(fs->fieldnum);
-				JumbleExpr(jstate, (Node *) fs->arg);
-			}
-			break;
-		case T_FieldStore:
-			{
-				FieldStore *fstore = (FieldStore *) node;
-
-				JumbleExpr(jstate, (Node *) fstore->arg);
-				JumbleExpr(jstate, (Node *) fstore->newvals);
-			}
-			break;
-		case T_RelabelType:
-			{
-				RelabelType *rt = (RelabelType *) node;
-
-				APP_JUMB(rt->resulttype);
-				JumbleExpr(jstate, (Node *) rt->arg);
-			}
-			break;
-		case T_CoerceViaIO:
-			{
-				CoerceViaIO *cio = (CoerceViaIO *) node;
-
-				APP_JUMB(cio->resulttype);
-				JumbleExpr(jstate, (Node *) cio->arg);
-			}
-			break;
-		case T_ArrayCoerceExpr:
-			{
-				ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node;
-
-				APP_JUMB(acexpr->resulttype);
-				JumbleExpr(jstate, (Node *) acexpr->arg);
-				JumbleExpr(jstate, (Node *) acexpr->elemexpr);
-			}
-			break;
-		case T_ConvertRowtypeExpr:
-			{
-				ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node;
-
-				APP_JUMB(crexpr->resulttype);
-				JumbleExpr(jstate, (Node *) crexpr->arg);
-			}
-			break;
-		case T_CollateExpr:
-			{
-				CollateExpr *ce = (CollateExpr *) node;
-
-				APP_JUMB(ce->collOid);
-				JumbleExpr(jstate, (Node *) ce->arg);
-			}
-			break;
-		case T_CaseExpr:
-			{
-				CaseExpr   *caseexpr = (CaseExpr *) node;
-
-				JumbleExpr(jstate, (Node *) caseexpr->arg);
-				foreach(temp, caseexpr->args)
-				{
-					CaseWhen   *when = lfirst_node(CaseWhen, temp);
-
-					JumbleExpr(jstate, (Node *) when->expr);
-					JumbleExpr(jstate, (Node *) when->result);
-				}
-				JumbleExpr(jstate, (Node *) caseexpr->defresult);
-			}
-			break;
-		case T_CaseTestExpr:
-			{
-				CaseTestExpr *ct = (CaseTestExpr *) node;
-
-				APP_JUMB(ct->typeId);
-			}
-			break;
-		case T_ArrayExpr:
-			JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements);
-			break;
-		case T_RowExpr:
-			JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args);
-			break;
-		case T_RowCompareExpr:
-			{
-				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-
-				APP_JUMB(rcexpr->rctype);
-				JumbleExpr(jstate, (Node *) rcexpr->largs);
-				JumbleExpr(jstate, (Node *) rcexpr->rargs);
-			}
-			break;
-		case T_CoalesceExpr:
-			JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args);
-			break;
-		case T_MinMaxExpr:
-			{
-				MinMaxExpr *mmexpr = (MinMaxExpr *) node;
-
-				APP_JUMB(mmexpr->op);
-				JumbleExpr(jstate, (Node *) mmexpr->args);
-			}
-			break;
-		case T_XmlExpr:
-			{
-				XmlExpr    *xexpr = (XmlExpr *) node;
-
-				APP_JUMB(xexpr->op);
-				JumbleExpr(jstate, (Node *) xexpr->named_args);
-				JumbleExpr(jstate, (Node *) xexpr->args);
-			}
-			break;
-		case T_NullTest:
-			{
-				NullTest   *nt = (NullTest *) node;
-
-				APP_JUMB(nt->nulltesttype);
-				JumbleExpr(jstate, (Node *) nt->arg);
-			}
-			break;
-		case T_BooleanTest:
-			{
-				BooleanTest *bt = (BooleanTest *) node;
-
-				APP_JUMB(bt->booltesttype);
-				JumbleExpr(jstate, (Node *) bt->arg);
-			}
-			break;
-		case T_CoerceToDomain:
-			{
-				CoerceToDomain *cd = (CoerceToDomain *) node;
-
-				APP_JUMB(cd->resulttype);
-				JumbleExpr(jstate, (Node *) cd->arg);
-			}
-			break;
-		case T_CoerceToDomainValue:
-			{
-				CoerceToDomainValue *cdv = (CoerceToDomainValue *) node;
-
-				APP_JUMB(cdv->typeId);
-			}
-			break;
-		case T_SetToDefault:
-			{
-				SetToDefault *sd = (SetToDefault *) node;
-
-				APP_JUMB(sd->typeId);
-			}
-			break;
-		case T_CurrentOfExpr:
-			{
-				CurrentOfExpr *ce = (CurrentOfExpr *) node;
-
-				APP_JUMB(ce->cvarno);
-				if (ce->cursor_name)
-					APP_JUMB_STRING(ce->cursor_name);
-				APP_JUMB(ce->cursor_param);
-			}
-			break;
-		case T_NextValueExpr:
-			{
-				NextValueExpr *nve = (NextValueExpr *) node;
-
-				APP_JUMB(nve->seqid);
-				APP_JUMB(nve->typeId);
-			}
-			break;
-		case T_InferenceElem:
-			{
-				InferenceElem *ie = (InferenceElem *) node;
-
-				APP_JUMB(ie->infercollid);
-				APP_JUMB(ie->inferopclass);
-				JumbleExpr(jstate, ie->expr);
-			}
-			break;
-		case T_TargetEntry:
-			{
-				TargetEntry *tle = (TargetEntry *) node;
-
-				APP_JUMB(tle->resno);
-				APP_JUMB(tle->ressortgroupref);
-				JumbleExpr(jstate, (Node *) tle->expr);
-			}
-			break;
-		case T_RangeTblRef:
-			{
-				RangeTblRef *rtr = (RangeTblRef *) node;
-
-				APP_JUMB(rtr->rtindex);
-			}
-			break;
-		case T_JoinExpr:
-			{
-				JoinExpr   *join = (JoinExpr *) node;
-
-				APP_JUMB(join->jointype);
-				APP_JUMB(join->isNatural);
-				APP_JUMB(join->rtindex);
-				JumbleExpr(jstate, join->larg);
-				JumbleExpr(jstate, join->rarg);
-				JumbleExpr(jstate, join->quals);
-			}
-			break;
-		case T_FromExpr:
-			{
-				FromExpr   *from = (FromExpr *) node;
-
-				JumbleExpr(jstate, (Node *) from->fromlist);
-				JumbleExpr(jstate, from->quals);
-			}
-			break;
-		case T_OnConflictExpr:
-			{
-				OnConflictExpr *conf = (OnConflictExpr *) node;
-
-				APP_JUMB(conf->action);
-				JumbleExpr(jstate, (Node *) conf->arbiterElems);
-				JumbleExpr(jstate, conf->arbiterWhere);
-				JumbleExpr(jstate, (Node *) conf->onConflictSet);
-				JumbleExpr(jstate, conf->onConflictWhere);
-				APP_JUMB(conf->constraint);
-				APP_JUMB(conf->exclRelIndex);
-				JumbleExpr(jstate, (Node *) conf->exclRelTlist);
-			}
-			break;
-		case T_MergeAction:
-			{
-				MergeAction *mergeaction = (MergeAction *) node;
-
-				APP_JUMB(mergeaction->matched);
-				APP_JUMB(mergeaction->commandType);
-				JumbleExpr(jstate, mergeaction->qual);
-				JumbleExpr(jstate, (Node *) mergeaction->targetList);
-			}
-			break;
-		case T_List:
-			foreach(temp, (List *) node)
-			{
-				JumbleExpr(jstate, (Node *) lfirst(temp));
-			}
-			break;
-		case T_IntList:
-			foreach(temp, (List *) node)
-			{
-				APP_JUMB(lfirst_int(temp));
-			}
-			break;
-		case T_SortGroupClause:
-			{
-				SortGroupClause *sgc = (SortGroupClause *) node;
-
-				APP_JUMB(sgc->tleSortGroupRef);
-				APP_JUMB(sgc->eqop);
-				APP_JUMB(sgc->sortop);
-				APP_JUMB(sgc->nulls_first);
-			}
-			break;
-		case T_GroupingSet:
-			{
-				GroupingSet *gsnode = (GroupingSet *) node;
-
-				JumbleExpr(jstate, (Node *) gsnode->content);
-			}
-			break;
-		case T_WindowClause:
-			{
-				WindowClause *wc = (WindowClause *) node;
-
-				APP_JUMB(wc->winref);
-				APP_JUMB(wc->frameOptions);
-				JumbleExpr(jstate, (Node *) wc->partitionClause);
-				JumbleExpr(jstate, (Node *) wc->orderClause);
-				JumbleExpr(jstate, wc->startOffset);
-				JumbleExpr(jstate, wc->endOffset);
-			}
-			break;
-		case T_CommonTableExpr:
-			{
-				CommonTableExpr *cte = (CommonTableExpr *) node;
-
-				/* we store the string name because RTE_CTE RTEs need it */
-				APP_JUMB_STRING(cte->ctename);
-				APP_JUMB(cte->ctematerialized);
-				JumbleQueryInternal(jstate, castNode(Query, cte->ctequery));
-			}
-			break;
-		case T_SetOperationStmt:
-			{
-				SetOperationStmt *setop = (SetOperationStmt *) node;
-
-				APP_JUMB(setop->op);
-				APP_JUMB(setop->all);
-				JumbleExpr(jstate, setop->larg);
-				JumbleExpr(jstate, setop->rarg);
-			}
-			break;
-		case T_RangeTblFunction:
-			{
-				RangeTblFunction *rtfunc = (RangeTblFunction *) node;
-
-				JumbleExpr(jstate, rtfunc->funcexpr);
-			}
-			break;
-		case T_TableFunc:
-			{
-				TableFunc  *tablefunc = (TableFunc *) node;
-
-				JumbleExpr(jstate, tablefunc->docexpr);
-				JumbleExpr(jstate, tablefunc->rowexpr);
-				JumbleExpr(jstate, (Node *) tablefunc->colexprs);
-			}
-			break;
-		case T_TableSampleClause:
-			{
-				TableSampleClause *tsc = (TableSampleClause *) node;
-
-				APP_JUMB(tsc->tsmhandler);
-				JumbleExpr(jstate, (Node *) tsc->args);
-				JumbleExpr(jstate, (Node *) tsc->repeatable);
-			}
-			break;
-		default:
-			/* Only a warning, since we can stumble along anyway */
-			elog(WARNING, "unrecognized node type: %d",
-				 (int) nodeTag(node));
-			break;
-	}
-}
-
-/*
- * Record location of constant within query string of query tree
- * that is currently being walked.
- */
-static void
-RecordConstLocation(JumbleState *jstate, int location)
-{
-	/* -1 indicates unknown or undefined location */
-	if (location >= 0)
-	{
-		/* enlarge array if needed */
-		if (jstate->clocations_count >= jstate->clocations_buf_size)
-		{
-			jstate->clocations_buf_size *= 2;
-			jstate->clocations = (LocationLen *)
-				repalloc(jstate->clocations,
-						 jstate->clocations_buf_size *
-						 sizeof(LocationLen));
-		}
-		jstate->clocations[jstate->clocations_count].location = location;
-		/* initialize lengths to -1 to simplify third-party module usage */
-		jstate->clocations[jstate->clocations_count].length = -1;
-		jstate->clocations_count++;
-	}
-}
-- 
2.38.1

#2vignesh C
vignesh21@gmail.com
In reply to: Michael Paquier (#1)
Re: Generating code for query jumbling through gen_node_support.pl

On Wed, 7 Dec 2022 at 13:27, Michael Paquier <michael@paquier.xyz> wrote:

Hi all,

This thread is a follow-up of the recent discussion about query
jumbling with DDL statements, where the conclusion was that we'd want
to generate all this code automatically for all the nodes:
/messages/by-id/36e5bffe-e989-194f-85c8-06e7bc88e6f7@amazon.com

What this patch allows to do it to compute the same query ID for
utility statements using their parsed Node state instead of their
string, meaning that things like "BEGIN", "bEGIN" or "begin" would be
treated the same, for example. But the main idea is not only that.

I have implemented that as of the attached, where the following things
are done:
- queryjumble.c is moved to src/backend/nodes/, to stick with the
other things for node equal/read/write/copy, renamed to
jumblefuncs.c.
- gen_node_support.c is extended to generate the functions and the
switch for the jumbling. There are a few exceptions, as of the Lists
and RangeTblEntry to do the jumbling consistently.
- Two pg_node_attr() are added in consistency with the existing ones:
no_jumble to discard completely a node from the the query jumbling
and jumble_ignore to discard one field from the jumble.

The patch is in a rather good shape, passes check-world and the CI,
but there are a few things that need to be discussed IMO. Things
could be perhaps divided in more patches, now the areas touched are
quite different so it did not look like a big deal to me as the
changes touch different areas and are straight-forward.

The location of the Nodes is quite invasive because we only care about
that for T_Const now in the query jumbling, and this could be
compensated with a third pg_node_attr() that tracks for the "int
location" of a Node whether it should participate in the jumbling or
not. There is also an argument where we would want to not include by
default new fields added to a Node, but that would require added more
pg_node_attr() than what's done here.

Note that the plan is to extend the normalization to some other parts
of the Nodes, like CALL and SET as mentioned on the other thread. I
have done nothing about that yet but doing so can be done in a few
lines with the facility presented here (aka just add a location
field). Hence, the normalization is consistent with the existing
queryjumble.c for the fields and the nodes processed.

In this patch, things are done so as the query ID is not computed
anymore from the query string but from the Query. I still need to
study the performance impact of that with short queries. If things
prove to be noticeable in some cases, this stuff could be switched to
use a new GUC where we could have a code path for the computation of
utilityStmt using its string as a fallback. I am not sure that I want
to enter in this level of complications, though, to keep things
simple, but that's yet to be done.

A bit more could be cut but pg_ident got in the way.. There are also
a few things for pg_stat_statements where a query ID of 0 can be
implied for utility statements in some cases.

Generating this code leads to an overall removal of code as what
queryjumble.c is generated automatically:
13 files changed, 901 insertions(+), 1113 deletions(-)

I am adding that to the next commit fest.

The patch does not apply on top of HEAD as in [1]http://cfbot.cputube.org/patch_41_4047.log, please post a rebased patch:
=== Applying patches on top of PostgreSQL commit ID
b82557ecc2ebbf649142740a1c5ce8d19089f620 ===
=== applying patch
./0001-Support-for-automated-query-jumble-with-all-Nodes.patch
...
patching file src/backend/utils/misc/queryjumble.c
Hunk #1 FAILED at 1.
Not deleting file src/backend/utils/misc/queryjumble.c as content
differs from patch
1 out of 1 hunk FAILED -- saving rejects to file
src/backend/utils/misc/queryjumble.c.rej

[1]: http://cfbot.cputube.org/patch_41_4047.log

Regards,
Vignesh

#3Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Michael Paquier (#1)
Re: Generating code for query jumbling through gen_node_support.pl

On 07.12.22 08:56, Michael Paquier wrote:

The location of the Nodes is quite invasive because we only care about
that for T_Const now in the query jumbling, and this could be
compensated with a third pg_node_attr() that tracks for the "int
location" of a Node whether it should participate in the jumbling or
not.

The generation script already has a way to identify location fields, by
($t eq 'int' && $f =~ 'location$'), so you could use that as well.

There is also an argument where we would want to not include by
default new fields added to a Node, but that would require added more
pg_node_attr() than what's done here.

I'm concerned about the large number of additional field annotations
this adds. We have been careful so far to document the use of each
attribute, e.g., *why* does a field not need to be copied etc. This
patch adds dozens and dozens of annotations without any explanation at
all. Now, the code this replaces also has no documentation, but maybe
this is the time to add some.

#4Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#3)
Re: Generating code for query jumbling through gen_node_support.pl

On Sat, Jan 07, 2023 at 07:37:49AM +0100, Peter Eisentraut wrote:

The generation script already has a way to identify location fields, by ($t
eq 'int' && $f =~ 'location$'), so you could use that as well.

I recall that some of the nodes may need renames to map with this
choice. That could be just one patch on top of the actual feature.

I'm concerned about the large number of additional field annotations this
adds. We have been careful so far to document the use of each attribute,
e.g., *why* does a field not need to be copied etc. This patch adds dozens
and dozens of annotations without any explanation at all. Now, the code
this replaces also has no documentation, but maybe this is the time to add
some.

In most cases, the addition of the node marker would be enough to
self-explain why they are included, but there is a trend for a lot of
the nodes when it comes to collations and typmods where we don't want
to see these in the jumbling calculations. I'll look at providing
more info for all that. (Note: I'm out for now.)
--
Michael

#5Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#3)
1 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Sat, Jan 07, 2023 at 07:37:49AM +0100, Peter Eisentraut wrote:

On 07.12.22 08:56, Michael Paquier wrote:

The location of the Nodes is quite invasive because we only care about
that for T_Const now in the query jumbling, and this could be
compensated with a third pg_node_attr() that tracks for the "int
location" of a Node whether it should participate in the jumbling or
not.

The generation script already has a way to identify location fields, by ($t
eq 'int' && $f =~ 'location$'), so you could use that as well.

I did not recall exactly everything here, but there are two parts to
the logic:
- gen_node_support.pl uses exactly this condition when scanning the
nodes to put the correct macro to mark a location to track, calling
down RecordConstLocation().
- Marking a bunch of nodes as jumble_ignore is actually necessary, or
we may finish by silencing parts of queries that should be
semantically unrelevant to the queries jumbled (ColumnRef is one).
Using a "jumble_ignore" flag is equally invasive to using an
"jumble_include" flag for each field, I am afraid, as the number of
fields in the nodes included in jumbles is pretty equivalent to the
number of fields ignored. I tend to prefer the approach of ignoring
things though, which is more consistent with the practive for node
read, write and copy.

Anyway, when it comes to the location, another thing that can be
considered here would be to require a node-level flag for the nodes on
which we want to track the location.  This overlaps a bit with the
fields satisfying "($t eq 'int' && $f =~ 'location$')", but it removes
most of the code changes like this one as at the end we only care
about the location for Const nodes:
-   int         location;       /* token location, or -1 if unknown */
+   int         location pg_node_attr(jumble_ignore);   /* token location, or -1
+                                                        * if unknown */

I have taken this approach in v2 of the patch, shaving ~100 lines of
more code as there is no need to mark all these location fields with
"jumble_ignore" anymore, except for Const, of course.

There is also an argument where we would want to not include by
default new fields added to a Node, but that would require added more
pg_node_attr() than what's done here.

I'm concerned about the large number of additional field annotations this
adds. We have been careful so far to document the use of each attribute,
e.g., *why* does a field not need to be copied etc. This patch adds dozens
and dozens of annotations without any explanation at all. Now, the code
this replaces also has no documentation, but maybe this is the time to add
some.

That's fair, though it is not doing to buy us much to update all the
nodes with similar small comments, as well. As far as I know, there
are basiscally three things here: typmods, collation information, and
internal data of the nodes stored during parse-analyze. I have added
more documentation to track what looks like the most relevant areas.

I have begun running some performance tests with this stuff and HEAD
to see if this leads to any difference in the query ID compilation
(compute_query_id = on, on scissors roughly) with a simple set of
short commands (like BEGIN/COMMIT) or longer ones, and I am seeing a
speedup trend actually (?). I still need to think more about a set of
tests here, but I think that micro-benchmarking of JumbleQuery() is
the most adapted approach to minimize the noise, with a few nodes of
various sizes (Const, Query, ColumnRef, anything..).

Thoughts?
--
Michael

Attachments:

v2-0001-Support-for-automated-query-jumble-with-all-Nodes.patchtext/x-diff; charset=us-asciiDownload
From 28d8b2ede0c7cfe25a4efa2d49f05106d61b55d1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 13 Jan 2023 16:53:16 +0900
Subject: [PATCH v2] Support for automated query jumble with all Nodes

This applies query jumbling in a consistent way to all the Nodes,
including DDLs & friends.
---
 src/include/nodes/bitmapset.h         |   2 +-
 src/include/nodes/nodes.h             |   7 +
 src/include/nodes/parsenodes.h        | 184 ++++--
 src/include/nodes/primnodes.h         | 371 +++++++----
 src/backend/nodes/Makefile            |   1 +
 src/backend/nodes/README              |   1 +
 src/backend/nodes/gen_node_support.pl | 105 +++-
 src/backend/nodes/jumblefuncs.c       | 358 +++++++++++
 src/backend/nodes/meson.build         |   1 +
 src/backend/utils/misc/Makefile       |   1 -
 src/backend/utils/misc/meson.build    |   1 -
 src/backend/utils/misc/queryjumble.c  | 861 --------------------------
 12 files changed, 843 insertions(+), 1050 deletions(-)
 create mode 100644 src/backend/nodes/jumblefuncs.c
 delete mode 100644 src/backend/utils/misc/queryjumble.c

diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 0dca6bc5fa..618c967a2b 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -50,7 +50,7 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
-	pg_node_attr(custom_copy_equal, special_read_write)
+	pg_node_attr(custom_copy_equal, special_read_write, no_jumble)
 
 	NodeTag		type;
 	int			nwords;			/* number of words in array */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 10752e8011..52e219f838 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -59,6 +59,8 @@ typedef enum NodeTag
  *
  * - no_copy_equal: Shorthand for both no_copy and no_equal.
  *
+ * - no_jumble: Does not support jumble() at all.
+ *
  * - no_read: Does not support nodeRead() at all.
  *
  * - nodetag_only: Does not support copyObject(), equal(), outNode(),
@@ -97,6 +99,11 @@ typedef enum NodeTag
  * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
  *   (Otherwise, compare normally.)
  *
+ * - jumble_ignore: Ignore the field for the query jumbling.
+ *
+ * - jumble_location: Mark the field as a location to track.  This is only
+ *   allowed for integer fields that include "location" in their name.
+ *
  * - 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
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cfeca96d53..6d7c23dfec 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -116,6 +116,11 @@ typedef uint64 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.
+ *
+ *	  All the fields ignored for the query jumbling are not semantically
+ *	  significant (such as alias names), as is ignored anything that can
+ *	  be deduced from child nodes (else we'd just be double-hashing that
+ *	  piece of information).
  */
 typedef struct Query
 {
@@ -123,47 +128,67 @@ typedef struct Query
 
 	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
 
-	QuerySource querySource;	/* where did I come from? */
+	/* where did I come from? */
+	QuerySource querySource pg_node_attr(jumble_ignore);
 
 	/*
 	 * query identifier (can be set by plugins); ignored for equal, as it
-	 * might not be set; also not stored
+	 * might not be set; also not stored.  This is the result of the query
+	 * jumble, hence ignored.
 	 */
-	uint64		queryId pg_node_attr(equal_ignore, read_write_ignore, read_as(0));
+	uint64		queryId pg_node_attr(equal_ignore, jumble_ignore, read_write_ignore, read_as(0));
 
-	bool		canSetTag;		/* do I set the command result tag? */
+	/* do I set the command result tag? */
+	bool		canSetTag pg_node_attr(jumble_ignore);
 
 	Node	   *utilityStmt;	/* non-null if commandType == CMD_UTILITY */
 
-	int			resultRelation; /* rtable index of target relation for
-								 * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
+	/*
+	 * rtable index of target relation for INSERT/UPDATE/DELETE/MERGE; 0 for
+	 * SELECT.  This is ignored in the query jumble as unrelated to the
+	 * compilation of the query ID.
+	 */
+	int			resultRelation pg_node_attr(jumble_ignore);
 
-	bool		hasAggs;		/* has aggregates in tlist or havingQual */
-	bool		hasWindowFuncs; /* has window functions in tlist */
-	bool		hasTargetSRFs;	/* has set-returning functions in tlist */
-	bool		hasSubLinks;	/* has subquery SubLink */
-	bool		hasDistinctOn;	/* distinctClause is from DISTINCT ON */
-	bool		hasRecursive;	/* WITH RECURSIVE was specified */
-	bool		hasModifyingCTE;	/* has INSERT/UPDATE/DELETE in WITH */
-	bool		hasForUpdate;	/* FOR [KEY] UPDATE/SHARE was specified */
-	bool		hasRowSecurity; /* rewriter has applied some RLS policy */
+	/* has aggregates in tlist or havingQual */
+	bool		hasAggs pg_node_attr(jumble_ignore);
+	/* has window functions in tlist */
+	bool		hasWindowFuncs pg_node_attr(jumble_ignore);
+	/* has set-returning functions in tlist */
+	bool		hasTargetSRFs pg_node_attr(jumble_ignore);
+	bool		hasSubLinks pg_node_attr(jumble_ignore);	/* has subquery SubLink */
+	/* distinctClause is from DISTINCT ON */
+	bool		hasDistinctOn pg_node_attr(jumble_ignore);
+	/* WITH RECURSIVE was specified */
+	bool		hasRecursive pg_node_attr(jumble_ignore);
+	/* has INSERT/UPDATE/DELETE in WITH */
+	bool		hasModifyingCTE pg_node_attr(jumble_ignore);
+	/* FOR [KEY] UPDATE/SHARE was specified */
+	bool		hasForUpdate pg_node_attr(jumble_ignore);
+	/* rewriter has applied some RLS policy */
+	bool		hasRowSecurity pg_node_attr(jumble_ignore);
 
-	bool		isReturn;		/* is a RETURN statement */
+	bool		isReturn pg_node_attr(jumble_ignore);	/* is a RETURN statement */
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
-	List	   *rteperminfos;	/* list of RTEPermissionInfo nodes for the
-								 * rtable entries having perminfoindex > 0 */
+
+	/*
+	 * list of RTEPermissionInfo nodes for the rtable entries having
+	 * perminfoindex > 0
+	 */
+	List	   *rteperminfos pg_node_attr(jumble_ignore);
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
 	List	   *mergeActionList;	/* list of actions for MERGE (only) */
-	bool		mergeUseOuterJoin;	/* whether to use outer join */
+	/* whether to use outer join */
+	bool		mergeUseOuterJoin pg_node_attr(jumble_ignore);
 
 	List	   *targetList;		/* target list (of TargetEntry) */
 
-	OverridingKind override;	/* OVERRIDING clause */
+	OverridingKind override pg_node_attr(jumble_ignore);	/* OVERRIDING clause */
 
 	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
 
@@ -191,11 +216,14 @@ typedef struct Query
 	Node	   *setOperations;	/* set-operation tree if this is top level of
 								 * a UNION/INTERSECT/EXCEPT query */
 
-	List	   *constraintDeps; /* a list of pg_constraint OIDs that the query
-								 * depends on to be semantically valid */
+	/*
+	 * A list of pg_constraint OIDs that the query depends on to be
+	 * semantically valid
+	 */
+	List	   *constraintDeps pg_node_attr(jumble_ignore);
 
-	List	   *withCheckOptions;	/* a list of WithCheckOption's (added
-									 * during rewrite) */
+	/* a list of WithCheckOption's (added during rewrite) */
+	List	   *withCheckOptions pg_node_attr(jumble_ignore);
 
 	/*
 	 * The following two fields identify the portion of the source text string
@@ -204,7 +232,8 @@ typedef struct Query
 	 * both be -1 meaning "unknown".
 	 */
 	int			stmt_location;	/* start location, or -1 if unknown */
-	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
+	/* length in bytes; 0 means "rest of string" */
+	int			stmt_len pg_node_attr(jumble_ignore);
 } Query;
 
 
@@ -994,7 +1023,7 @@ typedef enum RTEKind
 
 typedef struct RangeTblEntry
 {
-	pg_node_attr(custom_read_write)
+	pg_node_attr(custom_read_write, no_jumble)
 
 	NodeTag		type;
 
@@ -1217,20 +1246,28 @@ typedef struct RTEPermissionInfo
  * time.  We do however remember how many columns we thought the type had
  * (including dropped columns!), so that we can successfully ignore any
  * columns added after the query was parsed.
+ *
+ * The query jumbling needs only to track the function expression.
  */
 typedef struct RangeTblFunction
 {
 	NodeTag		type;
 
 	Node	   *funcexpr;		/* expression tree for func call */
-	int			funccolcount;	/* number of columns it contributes to RTE */
+	int			funccolcount pg_node_attr(jumble_ignore);	/* number of columns it
+															 * contributes to RTE */
 	/* These fields record the contents of a column definition list, if any: */
-	List	   *funccolnames;	/* column names (list of String) */
-	List	   *funccoltypes;	/* OID list of column type OIDs */
-	List	   *funccoltypmods; /* integer list of column typmods */
-	List	   *funccolcollations;	/* OID list of column collation OIDs */
+	List	   *funccolnames pg_node_attr(jumble_ignore);	/* column names (list of
+															 * String) */
+	List	   *funccoltypes pg_node_attr(jumble_ignore);	/* OID list of column
+															 * type OIDs */
+	List	   *funccoltypmods pg_node_attr(jumble_ignore); /* integer list of
+															 * column typmods */
+	List	   *funccolcollations pg_node_attr(jumble_ignore);	/* OID list of column
+																 * collation OIDs */
 	/* This is set during planning for use by the executor: */
-	Bitmapset  *funcparams;		/* PARAM_EXEC Param IDs affecting this func */
+	Bitmapset  *funcparams pg_node_attr(jumble_ignore); /* PARAM_EXEC Param IDs
+														 * affecting this func */
 } RangeTblFunction;
 
 /*
@@ -1337,7 +1374,8 @@ typedef struct SortGroupClause
 	Oid			eqop;			/* the equality operator ('=' op) */
 	Oid			sortop;			/* the ordering operator ('<' op), or 0 */
 	bool		nulls_first;	/* do NULLs come before normal values? */
-	bool		hashable;		/* can eqop be implemented by hashing? */
+	/* can eqop be implemented by hashing? */
+	bool		hashable pg_node_attr(jumble_ignore);
 } SortGroupClause;
 
 /*
@@ -1402,7 +1440,7 @@ typedef enum GroupingSetKind
 typedef struct GroupingSet
 {
 	NodeTag		type;
-	GroupingSetKind kind;
+	GroupingSetKind kind pg_node_attr(jumble_ignore);
 	List	   *content;
 	int			location;
 } GroupingSet;
@@ -1423,25 +1461,37 @@ typedef struct GroupingSet
  * When refname isn't null, the partitionClause is always copied from there;
  * the orderClause might or might not be copied (see copiedOrder); the framing
  * options are never copied, per spec.
+ *
+ * The information relevant for the query jumbling is the partition clause
+ * type and its bounds.
  */
 typedef struct WindowClause
 {
 	NodeTag		type;
-	char	   *name;			/* window name (NULL in an OVER clause) */
-	char	   *refname;		/* referenced window name, if any */
+	char	   *name pg_node_attr(jumble_ignore);	/* window name (NULL in an
+													 * OVER clause) */
+	char	   *refname pg_node_attr(jumble_ignore);	/* referenced window
+														 * name, if any */
 	List	   *partitionClause;	/* PARTITION BY list */
-	List	   *orderClause;	/* ORDER BY list */
+	List	   *orderClause pg_node_attr(jumble_ignore);	/* ORDER BY list */
 	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 */
-	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? */
+	/* qual to help short-circuit execution */
+	List	   *runCondition pg_node_attr(jumble_ignore);
+	/* in_range function for startOffset */
+	Oid			startInRangeFunc pg_node_attr(jumble_ignore);
+	/* in_range function for endOffset */
+	Oid			endInRangeFunc pg_node_attr(jumble_ignore);
+	/* collation for in_range tests */
+	Oid			inRangeColl pg_node_attr(jumble_ignore);
+	/* use ASC sort order for in_range tests? */
+	bool		inRangeAsc pg_node_attr(jumble_ignore);
+	/* nulls sort first for in_range tests? */
+	bool		inRangeNullsFirst pg_node_attr(jumble_ignore);
 	Index		winref;			/* ID referenced by window functions */
-	bool		copiedOrder;	/* did we copy orderClause from refname? */
+	/* did we copy orderClause from refname? */
+	bool		copiedOrder pg_node_attr(jumble_ignore);
 } WindowClause;
 
 /*
@@ -1556,17 +1606,26 @@ typedef struct CommonTableExpr
 	CTEMaterialize ctematerialized; /* is this an optimization fence? */
 	/* SelectStmt/InsertStmt/etc before parse analysis, Query afterwards: */
 	Node	   *ctequery;		/* the CTE's subquery */
-	CTESearchClause *search_clause;
-	CTECycleClause *cycle_clause;
+	CTESearchClause *search_clause pg_node_attr(jumble_ignore);
+	CTECycleClause *cycle_clause pg_node_attr(jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 	/* These fields are set during parse analysis: */
-	bool		cterecursive;	/* is this CTE actually recursive? */
-	int			cterefcount;	/* number of RTEs referencing this CTE
-								 * (excluding internal self-references) */
-	List	   *ctecolnames;	/* list of output column names */
-	List	   *ctecoltypes;	/* OID list of output column type OIDs */
-	List	   *ctecoltypmods;	/* integer list of output column typmods */
-	List	   *ctecolcollations;	/* OID list of column collation OIDs */
+	/* is this CTE actually recursive? */
+	bool		cterecursive pg_node_attr(jumble_ignore);
+
+	/*
+	 * Number of RTEs referencing this CTE (excluding internal
+	 * self-references), irrelevant for query jumbling.
+	 */
+	int			cterefcount pg_node_attr(jumble_ignore);
+	List	   *ctecolnames pg_node_attr(jumble_ignore);	/* list of output column
+															 * names */
+	List	   *ctecoltypes pg_node_attr(jumble_ignore);	/* OID list of output
+															 * column type OIDs */
+	List	   *ctecoltypmods pg_node_attr(jumble_ignore);	/* integer list of
+															 * output column typmods */
+	List	   *ctecolcollations pg_node_attr(jumble_ignore);	/* OID list of column
+																 * collation OIDs */
 } CommonTableExpr;
 
 /* Convenience macro to get the output tlist of a CTE's query */
@@ -1603,10 +1662,11 @@ typedef struct MergeAction
 	NodeTag		type;
 	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
 	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
-	OverridingKind override;	/* OVERRIDING clause */
+	OverridingKind override pg_node_attr(jumble_ignore);	/* OVERRIDING clause */
 	Node	   *qual;			/* transformed WHEN conditions */
 	List	   *targetList;		/* the target list (of TargetEntry) */
-	List	   *updateColnos;	/* target attribute numbers of an UPDATE */
+	List	   *updateColnos pg_node_attr(jumble_ignore);	/* target attribute
+															 * numbers of an UPDATE */
 } MergeAction;
 
 /*
@@ -1815,11 +1875,15 @@ typedef struct SetOperationStmt
 	Node	   *rarg;			/* right child */
 	/* Eventually add fields for CORRESPONDING spec here */
 
-	/* Fields derived during parse analysis: */
-	List	   *colTypes;		/* OID list of output column type OIDs */
-	List	   *colTypmods;		/* integer list of output column typmods */
-	List	   *colCollations;	/* OID list of output column collation OIDs */
-	List	   *groupClauses;	/* a list of SortGroupClause's */
+	/* Fields derived during parse analysis, irrelevant for query jumbling */
+	List	   *colTypes pg_node_attr(jumble_ignore);	/* OID list of output
+														 * column type OIDs */
+	List	   *colTypmods pg_node_attr(jumble_ignore); /* integer list of
+														 * output column typmods */
+	List	   *colCollations pg_node_attr(jumble_ignore);	/* OID list of output
+															 * column collation OIDs */
+	List	   *groupClauses pg_node_attr(jumble_ignore);	/* a list of
+															 * SortGroupClause's */
 	/* groupClauses is NIL if UNION ALL, but must be set otherwise */
 } SetOperationStmt;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 83e40e56d3..3681587ca2 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -98,18 +98,27 @@ typedef struct RangeVar
 typedef struct TableFunc
 {
 	NodeTag		type;
-	List	   *ns_uris;		/* list of namespace URI expressions */
-	List	   *ns_names;		/* list of namespace names or NULL */
+	List	   *ns_uris pg_node_attr(jumble_ignore);	/* list of namespace URI
+														 * expressions */
+	List	   *ns_names pg_node_attr(jumble_ignore);	/* list of namespace
+														 * names or NULL */
 	Node	   *docexpr;		/* input document expression */
 	Node	   *rowexpr;		/* row filter expression */
-	List	   *colnames;		/* column names (list of String) */
-	List	   *coltypes;		/* OID list of column type OIDs */
-	List	   *coltypmods;		/* integer list of column typmods */
-	List	   *colcollations;	/* OID list of column collation OIDs */
+	List	   *colnames pg_node_attr(jumble_ignore);	/* column names (list of
+														 * String) */
+	List	   *coltypes pg_node_attr(jumble_ignore);	/* OID list of column
+														 * type OIDs */
+	List	   *coltypmods pg_node_attr(jumble_ignore); /* integer list of
+														 * column typmods */
+	List	   *colcollations pg_node_attr(jumble_ignore);	/* OID list of column
+															 * collation OIDs */
 	List	   *colexprs;		/* list of column filter expressions */
-	List	   *coldefexprs;	/* list of column default expressions */
-	Bitmapset  *notnulls;		/* nullability flag for each output column */
-	int			ordinalitycol;	/* counts from 0; -1 if none specified */
+	List	   *coldefexprs pg_node_attr(jumble_ignore);	/* list of column
+															 * default expressions */
+	Bitmapset  *notnulls pg_node_attr(jumble_ignore);	/* nullability flag for
+														 * each output column */
+	int			ordinalitycol pg_node_attr(jumble_ignore);	/* counts from 0; -1 if
+															 * none specified */
 	int			location;		/* token location, or -1 if unknown */
 } TableFunc;
 
@@ -217,11 +226,11 @@ typedef struct Var
 	AttrNumber	varattno;
 
 	/* pg_type OID for the type of this var */
-	Oid			vartype;
+	Oid			vartype pg_node_attr(jumble_ignore);
 	/* pg_attribute typmod value */
-	int32		vartypmod;
+	int32		vartypmod pg_node_attr(jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			varcollid;
+	Oid			varcollid pg_node_attr(jumble_ignore);
 
 	/*
 	 * for subquery variables referencing outer relations; 0 in a normal var,
@@ -235,9 +244,9 @@ typedef struct Var
 	 * their varno/varattno match.
 	 */
 	/* syntactic relation index (0 if unknown) */
-	Index		varnosyn pg_node_attr(equal_ignore);
+	Index		varnosyn pg_node_attr(equal_ignore, jumble_ignore);
 	/* syntactic attribute number */
-	AttrNumber	varattnosyn pg_node_attr(equal_ignore);
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore, jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -250,6 +259,8 @@ typedef struct Var
  * must be in non-extended form (4-byte header, no compression or external
  * references).  This ensures that the Const node is self-contained and makes
  * it more likely that equal() will see logically identical values as equal.
+ *
+ * Only the constant type OID is relevant for the query jumbling.
  */
 typedef struct Const
 {
@@ -257,17 +268,27 @@ typedef struct Const
 
 	Expr		xpr;
 	Oid			consttype;		/* pg_type OID of the constant's datatype */
-	int32		consttypmod;	/* typmod value, if any */
-	Oid			constcollid;	/* OID of collation, or InvalidOid if none */
-	int			constlen;		/* typlen of the constant's datatype */
-	Datum		constvalue;		/* the constant's value */
-	bool		constisnull;	/* whether the constant is null (if true,
-								 * constvalue is undefined) */
-	bool		constbyval;		/* whether this datatype is passed by value.
-								 * If true, then all the information is stored
-								 * in the Datum. If false, then the Datum
-								 * contains a pointer to the information. */
-	int			location;		/* token location, or -1 if unknown */
+	int32		consttypmod pg_node_attr(jumble_ignore);	/* typmod value, if any */
+	Oid			constcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	int			constlen pg_node_attr(jumble_ignore);	/* typlen of the
+														 * constant's datatype */
+	Datum		constvalue pg_node_attr(jumble_ignore); /* the constant's value */
+	/* whether the constant is null (if true, constvalue is undefined) */
+	bool		constisnull pg_node_attr(jumble_ignore);
+
+	/*
+	 * Whether this datatype is passed by value.  If true, then all the
+	 * information is stored in the Datum.  If false, then the Datum contains
+	 * a pointer to the information.
+	 */
+	bool		constbyval pg_node_attr(jumble_ignore);
+
+	/*
+	 * token location, or -1 if unknown.  All constants are tracked as
+	 * locations in query jumbling, to be marked as parameters.
+	 */
+	int			location pg_node_attr(jumble_location);
 } Const;
 
 /*
@@ -305,14 +326,17 @@ typedef enum ParamKind
 	PARAM_MULTIEXPR
 } ParamKind;
 
+/* typmod and collation information are irrelevant for the query jumbling. */
 typedef struct Param
 {
 	Expr		xpr;
 	ParamKind	paramkind;		/* kind of parameter. See above */
 	int			paramid;		/* numeric ID for parameter */
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
-	int32		paramtypmod;	/* typmod value, if known */
-	Oid			paramcollid;	/* OID of collation, or InvalidOid if none */
+	int32		paramtypmod pg_node_attr(jumble_ignore);	/* typmod value, if
+															 * known */
+	Oid			paramcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
 	int			location;		/* token location, or -1 if unknown */
 } Param;
 
@@ -364,6 +388,9 @@ typedef struct Param
  * and can share the result.  Aggregates with same 'transno' but different
  * 'aggno' can share the same transition state, only the final function needs
  * to be called separately.
+ *
+ * Information related to collations, transition types and internal states
+ * are irrelevant for the query jumbling.
  */
 typedef struct Aggref
 {
@@ -373,22 +400,22 @@ typedef struct Aggref
 	Oid			aggfnoid;
 
 	/* type Oid of result of the aggregate */
-	Oid			aggtype;
+	Oid			aggtype pg_node_attr(jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			aggcollid;
+	Oid			aggcollid pg_node_attr(jumble_ignore);
 
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(jumble_ignore);
 
 	/*
 	 * type Oid of aggregate's transition value; ignored for equal since it
 	 * might not be set yet
 	 */
-	Oid			aggtranstype pg_node_attr(equal_ignore);
+	Oid			aggtranstype pg_node_attr(equal_ignore, jumble_ignore);
 
 	/* type Oids of direct and aggregated args */
-	List	   *aggargtypes;
+	List	   *aggargtypes pg_node_attr(jumble_ignore);
 
 	/* direct arguments, if an ordered-set agg */
 	List	   *aggdirectargs;
@@ -406,31 +433,31 @@ typedef struct Aggref
 	Expr	   *aggfilter;
 
 	/* true if argument list was really '*' */
-	bool		aggstar;
+	bool		aggstar pg_node_attr(jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		aggvariadic;
+	bool		aggvariadic pg_node_attr(jumble_ignore);
 
 	/* aggregate kind (see pg_aggregate.h) */
-	char		aggkind;
+	char		aggkind pg_node_attr(jumble_ignore);
 
 	/* aggregate input already sorted */
-	bool		aggpresorted pg_node_attr(equal_ignore);
+	bool		aggpresorted pg_node_attr(equal_ignore, jumble_ignore);
 
 	/* > 0 if agg belongs to outer query */
-	Index		agglevelsup;
+	Index		agglevelsup pg_node_attr(jumble_ignore);
 
 	/* expected agg-splitting mode of parent Agg */
-	AggSplit	aggsplit;
+	AggSplit	aggsplit pg_node_attr(jumble_ignore);
 
 	/* unique ID within the Agg node */
-	int			aggno;
+	int			aggno pg_node_attr(jumble_ignore);
 
 	/* unique ID of transition state in the Agg */
-	int			aggtransno;
+	int			aggtransno pg_node_attr(jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -459,19 +486,22 @@ typedef struct Aggref
  *
  * In raw parse output we have only the args list; parse analysis fills in the
  * refs list, and the planner fills in the cols list.
+ *
+ * All the fields used as information for an internal state are irrelevant
+ * for the query jumbling.
  */
 typedef struct GroupingFunc
 {
 	Expr		xpr;
 
 	/* arguments, not evaluated but kept for benefit of EXPLAIN etc. */
-	List	   *args;
+	List	   *args pg_node_attr(jumble_ignore);
 
 	/* ressortgrouprefs of arguments */
 	List	   *refs pg_node_attr(equal_ignore);
 
 	/* actual column positions set by planner */
-	List	   *cols pg_node_attr(equal_ignore);
+	List	   *cols pg_node_attr(equal_ignore, jumble_ignore);
 
 	/* same as Aggref.agglevelsup */
 	Index		agglevelsup;
@@ -482,19 +512,27 @@ typedef struct GroupingFunc
 
 /*
  * WindowFunc
+ *
+ * Collation information is irrelevant for the query jumbling, as is the
+ * internal state information of the node like "winstar" and "winagg".
  */
 typedef struct WindowFunc
 {
 	Expr		xpr;
 	Oid			winfnoid;		/* pg_proc Oid of the function */
-	Oid			wintype;		/* type Oid of result of the window function */
-	Oid			wincollid;		/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
+	Oid			wintype pg_node_attr(jumble_ignore);	/* type Oid of result of
+														 * the window function */
+	Oid			wincollid pg_node_attr(jumble_ignore);	/* OID of collation of
+														 * result */
+	Oid			inputcollid pg_node_attr(jumble_ignore);	/* OID of collation that
+															 * function should use */
 	List	   *args;			/* arguments to the window function */
 	Expr	   *aggfilter;		/* FILTER expression, if any */
 	Index		winref;			/* index of associated WindowClause */
-	bool		winstar;		/* true if argument list was really '*' */
-	bool		winagg;			/* is function a simple aggregate? */
+	bool		winstar pg_node_attr(jumble_ignore);	/* true if argument list
+														 * was really '*' */
+	bool		winagg pg_node_attr(jumble_ignore); /* is function a simple
+													 * aggregate? */
 	int			location;		/* token location, or -1 if unknown */
 } WindowFunc;
 
@@ -532,6 +570,8 @@ typedef struct WindowFunc
  * subscripting logic.  Likewise, reftypmod and refcollid will match the
  * container's properties in a store, but could be different in a fetch.
  *
+ * Any internal state data is ignored for the query jumbling.
+ *
  * Note: for the cases where a container is returned, if refexpr yields a R/W
  * expanded container, then the implementation is allowed to modify that
  * object in-place and return the same object.
@@ -539,11 +579,16 @@ typedef struct WindowFunc
 typedef struct SubscriptingRef
 {
 	Expr		xpr;
-	Oid			refcontainertype;	/* type of the container proper */
-	Oid			refelemtype;	/* the container type's pg_type.typelem */
-	Oid			refrestype;		/* type of the SubscriptingRef's result */
-	int32		reftypmod;		/* typmod of the result */
-	Oid			refcollid;		/* collation of result, or InvalidOid if none */
+	Oid			refcontainertype pg_node_attr(jumble_ignore);	/* type of the container
+																 * proper */
+	Oid			refelemtype pg_node_attr(jumble_ignore);	/* the container type's
+															 * pg_type.typelem */
+	Oid			refrestype pg_node_attr(jumble_ignore); /* type of the
+														 * SubscriptingRef's
+														 * result */
+	int32		reftypmod pg_node_attr(jumble_ignore);	/* typmod of the result */
+	Oid			refcollid pg_node_attr(jumble_ignore);	/* collation of result,
+														 * or InvalidOid if none */
 	List	   *refupperindexpr;	/* expressions that evaluate to upper
 									 * container indexes */
 	List	   *reflowerindexpr;	/* expressions that evaluate to lower
@@ -591,18 +636,30 @@ typedef enum CoercionForm
 
 /*
  * FuncExpr - expression node for a function call
+ *
+ * Collation information is irrelevant for the query jumbling, only the
+ * arguments and the function OID matter.
  */
 typedef struct FuncExpr
 {
 	Expr		xpr;
 	Oid			funcid;			/* PG_PROC OID of the function */
-	Oid			funcresulttype; /* PG_TYPE OID of result value */
-	bool		funcretset;		/* true if function returns set */
-	bool		funcvariadic;	/* true if variadic arguments have been
-								 * combined into an array last argument */
-	CoercionForm funcformat;	/* how to display this function call */
-	Oid			funccollid;		/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
+	/* PG_TYPE OID of result value */
+	Oid			funcresulttype pg_node_attr(jumble_ignore);
+	/* true if function returns set */
+	bool		funcretset pg_node_attr(jumble_ignore);
+
+	/*
+	 * true if variadic arguments have been combined into an array last
+	 * argument
+	 */
+	bool		funcvariadic pg_node_attr(jumble_ignore);
+	CoercionForm funcformat pg_node_attr(jumble_ignore);	/* how to display this
+															 * function call */
+	Oid			funccollid pg_node_attr(jumble_ignore); /* OID of collation of
+														 * result */
+	Oid			inputcollid pg_node_attr(jumble_ignore);	/* OID of collation that
+															 * function should use */
 	List	   *args;			/* arguments to the function */
 	int			location;		/* token location, or -1 if unknown */
 } FuncExpr;
@@ -625,7 +682,7 @@ typedef struct NamedArgExpr
 {
 	Expr		xpr;
 	Expr	   *arg;			/* the argument expression */
-	char	   *name;			/* the name */
+	char	   *name pg_node_attr(jumble_ignore);	/* the name */
 	int			argnumber;		/* argument's number in positional notation */
 	int			location;		/* argument name location, or -1 if unknown */
 } NamedArgExpr;
@@ -639,6 +696,9 @@ typedef struct NamedArgExpr
  * 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.
+ *
+ * Internal state information and collation data is irrelevant for the query
+ * jumbling.
  */
 typedef struct OpExpr
 {
@@ -648,19 +708,19 @@ typedef struct OpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of underlying function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, jumble_ignore);
 
 	/* PG_TYPE OID of result value */
-	Oid			opresulttype;
+	Oid			opresulttype pg_node_attr(jumble_ignore);
 
 	/* true if operator returns set */
-	bool		opretset;
+	bool		opretset pg_node_attr(jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			opcollid;
+	Oid			opcollid pg_node_attr(jumble_ignore);
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(jumble_ignore);
 
 	/* arguments to the operator (1 or 2) */
 	List	   *args;
@@ -716,6 +776,9 @@ typedef OpExpr NullIfExpr;
  * 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.
+ *
+ * OID entruues of the internal function types are irrelevant for the query
+ * jumbling, but the operator OID and the arguments are.
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -725,19 +788,19 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of comparison function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, jumble_ignore);
 
 	/* PG_PROC OID of hash func or InvalidOid */
-	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero, jumble_ignore);
 
 	/* PG_PROC OID of negator of opfuncid function or InvalidOid.  See above */
-	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero, jumble_ignore);
 
 	/* true for ANY, false for ALL */
 	bool		useOr;
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(jumble_ignore);
 
 	/* the scalar and array operands */
 	List	   *args;
@@ -838,7 +901,8 @@ typedef struct SubLink
 	SubLinkType subLinkType;	/* see above */
 	int			subLinkId;		/* ID (1..n); 0 if not MULTIEXPR */
 	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
-	List	   *operName;		/* originally specified operator name */
+	List	   *operName pg_node_attr(jumble_ignore);	/* originally specified
+														 * operator name */
 	Node	   *subselect;		/* subselect as Query* or raw parsetree */
 	int			location;		/* token location, or -1 if unknown */
 } SubLink;
@@ -948,10 +1012,13 @@ typedef struct FieldSelect
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	AttrNumber	fieldnum;		/* attribute number of field to extract */
-	Oid			resulttype;		/* type of the field (result type of this
-								 * node) */
-	int32		resulttypmod;	/* output typmod (usually -1) */
-	Oid			resultcollid;	/* OID of collation of the field */
+	Oid			resulttype pg_node_attr(jumble_ignore); /* type of the field
+														 * (result type of this
+														 * node) */
+	int32		resulttypmod pg_node_attr(jumble_ignore);	/* output typmod
+															 * (usually -1) */
+	Oid			resultcollid pg_node_attr(jumble_ignore);	/* OID of collation of
+															 * the field */
 } FieldSelect;
 
 /* ----------------
@@ -977,8 +1044,10 @@ typedef struct FieldStore
 	Expr		xpr;
 	Expr	   *arg;			/* input tuple value */
 	List	   *newvals;		/* new value(s) for field(s) */
-	List	   *fieldnums;		/* integer list of field attnums */
-	Oid			resulttype;		/* type of result (same as type of arg) */
+	List	   *fieldnums pg_node_attr(jumble_ignore);	/* integer list of field
+														 * attnums */
+	Oid			resulttype pg_node_attr(jumble_ignore); /* type of result (same
+														 * as type of arg) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 } FieldStore;
 
@@ -1000,9 +1069,12 @@ typedef struct RelabelType
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion expression */
-	int32		resulttypmod;	/* output typmod (usually -1) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm relabelformat; /* how to display this node */
+	int32		resulttypmod pg_node_attr(jumble_ignore);	/* output typmod
+															 * (usually -1) */
+	Oid			resultcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	CoercionForm relabelformat pg_node_attr(jumble_ignore); /* how to display this
+															 * node */
 	int			location;		/* token location, or -1 if unknown */
 } RelabelType;
 
@@ -1021,8 +1093,10 @@ typedef struct CoerceViaIO
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion */
 	/* output typmod is not stored, but is presumed -1 */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coerceformat;	/* how to display this node */
+	Oid			resultcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	CoercionForm coerceformat pg_node_attr(jumble_ignore);	/* how to display this
+															 * node */
 	int			location;		/* token location, or -1 if unknown */
 } CoerceViaIO;
 
@@ -1045,9 +1119,12 @@ typedef struct ArrayCoerceExpr
 	Expr	   *arg;			/* input expression (yields an array) */
 	Expr	   *elemexpr;		/* expression representing per-element work */
 	Oid			resulttype;		/* output type of coercion (an array type) */
-	int32		resulttypmod;	/* output typmod (also element typmod) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coerceformat;	/* how to display this node */
+	int32		resulttypmod pg_node_attr(jumble_ignore);	/* output typmod (also
+															 * element typmod) */
+	Oid			resultcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	CoercionForm coerceformat pg_node_attr(jumble_ignore);	/* how to display this
+															 * node */
 	int			location;		/* token location, or -1 if unknown */
 } ArrayCoerceExpr;
 
@@ -1070,7 +1147,8 @@ typedef struct ConvertRowtypeExpr
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type (always a composite type) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
-	CoercionForm convertformat; /* how to display this node */
+	CoercionForm convertformat pg_node_attr(jumble_ignore); /* how to display this
+															 * node */
 	int			location;		/* token location, or -1 if unknown */
 } ConvertRowtypeExpr;
 
@@ -1114,8 +1192,10 @@ typedef struct CollateExpr
 typedef struct CaseExpr
 {
 	Expr		xpr;
-	Oid			casetype;		/* type of expression result */
-	Oid			casecollid;		/* OID of collation, or InvalidOid if none */
+	Oid			casetype pg_node_attr(jumble_ignore);	/* type of expression
+														 * result */
+	Oid			casecollid pg_node_attr(jumble_ignore); /* OID of collation, or
+														 * InvalidOid if none */
 	Expr	   *arg;			/* implicit equality comparison argument */
 	List	   *args;			/* the arguments (list of WHEN clauses) */
 	Expr	   *defresult;		/* the default result (ELSE clause) */
@@ -1157,8 +1237,10 @@ typedef struct CaseTestExpr
 {
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
+	int32		typeMod pg_node_attr(jumble_ignore);	/* typemod for
+														 * substituted value */
+	Oid			collation pg_node_attr(jumble_ignore);	/* collation for the
+														 * substituted value */
 } CaseTestExpr;
 
 /*
@@ -1172,11 +1254,15 @@ typedef struct CaseTestExpr
 typedef struct ArrayExpr
 {
 	Expr		xpr;
-	Oid			array_typeid;	/* type of expression result */
-	Oid			array_collid;	/* OID of collation, or InvalidOid if none */
-	Oid			element_typeid; /* common type of array elements */
+	Oid			array_typeid pg_node_attr(jumble_ignore);	/* type of expression
+															 * result */
+	Oid			array_collid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	Oid			element_typeid pg_node_attr(jumble_ignore); /* common type of array
+															 * elements */
 	List	   *elements;		/* the array elements or sub-arrays */
-	bool		multidims;		/* true if elements are sub-arrays */
+	bool		multidims pg_node_attr(jumble_ignore);	/* true if elements are
+														 * sub-arrays */
 	int			location;		/* token location, or -1 if unknown */
 } ArrayExpr;
 
@@ -1205,7 +1291,8 @@ typedef struct RowExpr
 {
 	Expr		xpr;
 	List	   *args;			/* the fields */
-	Oid			row_typeid;		/* RECORDOID or a composite type's ID */
+	Oid			row_typeid pg_node_attr(jumble_ignore); /* RECORDOID or a
+														 * composite type's ID */
 
 	/*
 	 * row_typeid cannot be a domain over composite, only plain composite.  To
@@ -1219,8 +1306,10 @@ typedef struct RowExpr
 	 * We don't need to store a collation either.  The result type is
 	 * necessarily composite, and composite types never have a collation.
 	 */
-	CoercionForm row_format;	/* how to display this node */
-	List	   *colnames;		/* list of String, or NIL */
+	CoercionForm row_format pg_node_attr(jumble_ignore);	/* how to display this
+															 * node */
+	List	   *colnames pg_node_attr(jumble_ignore);	/* list of String, or
+														 * NIL */
 	int			location;		/* token location, or -1 if unknown */
 } RowExpr;
 
@@ -1253,9 +1342,14 @@ typedef struct RowCompareExpr
 {
 	Expr		xpr;
 	RowCompareType rctype;		/* LT LE GE or GT, never EQ or NE */
-	List	   *opnos;			/* OID list of pairwise comparison ops */
-	List	   *opfamilies;		/* OID list of containing operator families */
-	List	   *inputcollids;	/* OID list of collations for comparisons */
+	List	   *opnos pg_node_attr(jumble_ignore);	/* OID list of pairwise
+													 * comparison ops */
+	List	   *opfamilies pg_node_attr(jumble_ignore); /* OID list of
+														 * containing operator
+														 * families */
+	List	   *inputcollids pg_node_attr(jumble_ignore);	/* OID list of
+															 * collations for
+															 * comparisons */
 	List	   *largs;			/* the left-hand input arguments */
 	List	   *rargs;			/* the right-hand input arguments */
 } RowCompareExpr;
@@ -1266,8 +1360,10 @@ typedef struct RowCompareExpr
 typedef struct CoalesceExpr
 {
 	Expr		xpr;
-	Oid			coalescetype;	/* type of expression result */
-	Oid			coalescecollid; /* OID of collation, or InvalidOid if none */
+	Oid			coalescetype pg_node_attr(jumble_ignore);	/* type of expression
+															 * result */
+	Oid			coalescecollid pg_node_attr(jumble_ignore); /* OID of collation, or
+															 * InvalidOid if none */
 	List	   *args;			/* the arguments */
 	int			location;		/* token location, or -1 if unknown */
 } CoalesceExpr;
@@ -1284,9 +1380,12 @@ typedef enum MinMaxOp
 typedef struct MinMaxExpr
 {
 	Expr		xpr;
-	Oid			minmaxtype;		/* common type of arguments and result */
-	Oid			minmaxcollid;	/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
+	Oid			minmaxtype pg_node_attr(jumble_ignore); /* common type of
+														 * arguments and result */
+	Oid			minmaxcollid pg_node_attr(jumble_ignore);	/* OID of collation of
+															 * result */
+	Oid			inputcollid pg_node_attr(jumble_ignore);	/* OID of collation that
+															 * function should use */
 	MinMaxOp	op;				/* function to execute */
 	List	   *args;			/* the arguments */
 	int			location;		/* token location, or -1 if unknown */
@@ -1325,13 +1424,16 @@ typedef struct XmlExpr
 {
 	Expr		xpr;
 	XmlExprOp	op;				/* xml function ID */
-	char	   *name;			/* name in xml(NAME foo ...) syntaxes */
+	char	   *name pg_node_attr(jumble_ignore);	/* name in xml(NAME foo
+													 * ...) syntaxes */
 	List	   *named_args;		/* non-XML expressions for xml_attributes */
-	List	   *arg_names;		/* parallel list of String values */
+	List	   *arg_names pg_node_attr(jumble_ignore);	/* parallel list of
+														 * String values */
 	List	   *args;			/* list of expressions */
-	XmlOptionType xmloption;	/* DOCUMENT or CONTENT */
-	Oid			type;			/* target type/typmod for XMLSERIALIZE */
-	int32		typmod;
+	XmlOptionType xmloption pg_node_attr(jumble_ignore);	/* DOCUMENT or CONTENT */
+	Oid			type pg_node_attr(jumble_ignore);	/* target type/typmod for
+													 * XMLSERIALIZE */
+	int32		typmod pg_node_attr(jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } XmlExpr;
 
@@ -1364,7 +1466,9 @@ typedef struct NullTest
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	NullTestType nulltesttype;	/* IS NULL, IS NOT NULL */
-	bool		argisrow;		/* T to perform field-by-field null checks */
+	bool		argisrow pg_node_attr(jumble_ignore);	/* T to perform
+														 * field-by-field null
+														 * checks */
 	int			location;		/* token location, or -1 if unknown */
 } NullTest;
 
@@ -1404,9 +1508,12 @@ typedef struct CoerceToDomain
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* domain type ID (result type) */
-	int32		resulttypmod;	/* output typmod (currently always -1) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coercionformat;	/* how to display this node */
+	int32		resulttypmod pg_node_attr(jumble_ignore);	/* output typmod
+															 * (currently always -1) */
+	Oid			resultcollid pg_node_attr(jumble_ignore);	/* OID of collation, or
+															 * InvalidOid if none */
+	CoercionForm coercionformat pg_node_attr(jumble_ignore);	/* how to display this
+																 * node */
 	int			location;		/* token location, or -1 if unknown */
 } CoerceToDomain;
 
@@ -1418,13 +1525,17 @@ typedef struct CoerceToDomain
  * Note: the typeId/typeMod/collation will be set from the domain's base type,
  * not the domain itself.  This is because we shouldn't consider the value
  * to be a member of the domain if we haven't yet checked its constraints.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct CoerceToDomainValue
 {
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
+	int32		typeMod pg_node_attr(jumble_ignore);	/* typemod for
+														 * substituted value */
+	Oid			collation pg_node_attr(jumble_ignore);	/* collation for the
+														 * substituted value */
 	int			location;		/* token location, or -1 if unknown */
 } CoerceToDomainValue;
 
@@ -1434,13 +1545,17 @@ typedef struct CoerceToDomainValue
  * This is not an executable expression: it must be replaced by the actual
  * column default expression during rewriting.  But it is convenient to
  * treat it as an expression node during parsing and rewriting.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct SetToDefault
 {
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
+	int32		typeMod pg_node_attr(jumble_ignore);	/* typemod for
+														 * substituted value */
+	Oid			collation pg_node_attr(jumble_ignore);	/* collation for the
+														 * substituted value */
 	int			location;		/* token location, or -1 if unknown */
 } SetToDefault;
 
@@ -1554,13 +1669,16 @@ typedef struct TargetEntry
 	Expr		xpr;
 	Expr	   *expr;			/* expression to evaluate */
 	AttrNumber	resno;			/* attribute number (see notes above) */
-	char	   *resname;		/* name of the column (could be NULL) */
+	char	   *resname pg_node_attr(jumble_ignore);	/* name of the column
+														 * (could be NULL) */
 	Index		ressortgroupref;	/* nonzero if referenced by a sort/group
 									 * clause */
-	Oid			resorigtbl;		/* OID of column's source table */
-	AttrNumber	resorigcol;		/* column's number in source table */
-	bool		resjunk;		/* set to true to eliminate the attribute from
-								 * final target list */
+	Oid			resorigtbl pg_node_attr(jumble_ignore); /* OID of column's
+														 * source table */
+	AttrNumber	resorigcol pg_node_attr(jumble_ignore); /* column's number in
+														 * source table */
+	/* set to true to eliminate the attribute from final target list */
+	bool		resjunk pg_node_attr(jumble_ignore);
 } TargetEntry;
 
 
@@ -1642,10 +1760,13 @@ typedef struct JoinExpr
 	bool		isNatural;		/* Natural join? Will need to shape table */
 	Node	   *larg;			/* left subtree */
 	Node	   *rarg;			/* right subtree */
-	List	   *usingClause;	/* USING clause, if any (list of String) */
-	Alias	   *join_using_alias;	/* alias attached to USING clause, if any */
+	List	   *usingClause pg_node_attr(jumble_ignore);	/* USING clause, if any
+															 * (list of String) */
+	Alias	   *join_using_alias pg_node_attr(jumble_ignore);	/* alias attached to
+																 * USING clause, if any */
 	Node	   *quals;			/* qualifiers on join, if any */
-	Alias	   *alias;			/* user-written alias clause, if any */
+	Alias	   *alias pg_node_attr(jumble_ignore);	/* user-written alias
+													 * clause, if any */
 	int			rtindex;		/* RT index assigned for join, or 0 */
 } JoinExpr;
 
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 7c594be583..cd5731a2ca 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -19,6 +19,7 @@ OBJS = \
 	copyfuncs.o \
 	equalfuncs.o \
 	extensible.o \
+	jumblefuncs.o \
 	list.o \
 	makefuncs.o \
 	multibitmapset.o \
diff --git a/src/backend/nodes/README b/src/backend/nodes/README
index 489a67eb89..e97a918e95 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -47,6 +47,7 @@ FILES IN THIS DIRECTORY (src/backend/nodes/)
     General-purpose node manipulation functions:
 	copyfuncs.c	- copy a node tree (*)
 	equalfuncs.c	- compare two node trees (*)
+	jumblefuncs.c   - compute a node tree for query jumbling (*)
 	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
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index b3c1ead496..89357c1ce6 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -121,6 +121,8 @@ my %node_type_info;
 my @no_copy;
 # node types we don't want equal support for
 my @no_equal;
+# node types we don't want jumble support for
+my @no_jumble;
 # node types we don't want read support for
 my @no_read;
 # node types we don't want read/write support for
@@ -155,12 +157,13 @@ 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);
-# Lists are specially treated in all four support files, too.
+# Lists are specially treated in all five support files, too.
 # (Ideally we'd mark List as "special copy/equal" not "no copy/equal".
 # But until there's other use-cases for that, just hot-wire the tests
 # that would need to distinguish.)
 push @no_copy,            qw(List);
 push @no_equal,           qw(List);
+push @no_jumble,          qw(List);
 push @special_read_write, qw(List);
 
 # Nodes with custom copy/equal implementations are skipped from
@@ -332,6 +335,10 @@ foreach my $infile (@ARGV)
 							push @no_copy,  $in_struct;
 							push @no_equal, $in_struct;
 						}
+						elsif ($attr eq 'no_jumble')
+						{
+							push @no_jumble, $in_struct;
+						}
 						elsif ($attr eq 'no_read')
 						{
 							push @no_read, $in_struct;
@@ -457,6 +464,8 @@ foreach my $infile (@ARGV)
 								equal_as_scalar
 								equal_ignore
 								equal_ignore_if_zero
+								jumble_ignore
+								jumble_location
 								read_write_ignore
 								write_only_relids
 								write_only_nondefault_pathtarget
@@ -1225,6 +1234,100 @@ close $ofs;
 close $rfs;
 
 
+# jumblefuncs.c
+
+push @output_files, 'jumblefuncs.funcs.c';
+open my $jff, '>', "$output_path/jumblefuncs.funcs.c$tmpext" or die $!;
+push @output_files, 'jumblefuncs.switch.c';
+open my $jfs, '>', "$output_path/jumblefuncs.switch.c$tmpext" or die $!;
+
+printf $jff $header_comment, 'jumblefuncs.funcs.c';
+printf $jfs $header_comment, 'jumblefuncs.switch.c';
+
+print $jff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @nodetag_only;
+	my $struct_no_jumble = (elem $n, @no_jumble);
+
+	print $jfs "\t\t\tcase T_${n}:\n"
+	  . "\t\t\t\t_jumble${n}(jstate, expr);\n"
+	  . "\t\t\t\tbreak;\n"
+	  unless $struct_no_jumble;
+
+	print $jff "
+static void
+_jumble${n}(JumbleState *jstate, Node *node)
+{
+\t${n} *expr = (${n} *) node;\n
+" unless $struct_no_jumble;
+
+	# 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 $jumble_ignore   = $struct_no_jumble;
+		my $jumble_location = 0;
+
+		# extract per-field attributes
+		foreach my $a (@a)
+		{
+			if ($a eq 'jumble_ignore')
+			{
+				$jumble_ignore = 1;
+			}
+			elsif ($a eq 'jumble_location')
+			{
+				$jumble_location = 1;
+			}
+		}
+
+		# node type
+		if (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+			and elem $1, @node_types)
+		{
+			print $jff "\tJUMBLE_NODE($f);\n"
+			  unless $jumble_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			# Track the node's location only if directly requested.
+			if ($jumble_location)
+			{
+				print $jff "\tJUMBLE_LOCATION($f);\n"
+				  unless $jumble_ignore;
+			}
+		}
+		elsif ($t eq 'char*')
+		{
+			print $jff "\tJUMBLE_STRING($f);\n"
+			  unless $jumble_ignore;
+		}
+		else
+		{
+			print $jff "\tJUMBLE_FIELD($f);\n"
+			  unless $jumble_ignore;
+		}
+	}
+
+	# Some nodes have no attributes like CheckPointStmt,
+	# so tweak things for empty contents.
+	if (scalar(@{ $node_type_info{$n}->{fields} }) == 0)
+	{
+		print $jff "\t(void) expr;\n"
+		  unless $struct_no_jumble;
+	}
+
+	print $jff "}
+" unless $struct_no_jumble;
+}
+
+close $jff;
+close $jfs;
+
 # now rename the temporary files to their final names
 foreach my $file (@output_files)
 {
diff --git a/src/backend/nodes/jumblefuncs.c b/src/backend/nodes/jumblefuncs.c
new file mode 100644
index 0000000000..b65e022d17
--- /dev/null
+++ b/src/backend/nodes/jumblefuncs.c
@@ -0,0 +1,358 @@
+/*-------------------------------------------------------------------------
+ *
+ * jumblefuncs.c
+ *	 Query normalization and fingerprinting.
+ *
+ * Normalization is a process whereby similar queries, typically differing only
+ * in their constants (though the exact rules are somewhat more subtle than
+ * that) are recognized as equivalent, and are tracked as a single entry.  This
+ * is particularly useful for non-prepared queries.
+ *
+ * Normalization is implemented by fingerprinting queries, selectively
+ * serializing those fields of each query tree's nodes that are judged to be
+ * essential to the query.  This is referred to as a query jumble.  This is
+ * distinct from a regular serialization in that various extraneous
+ * information is ignored as irrelevant or not essential to the query, such
+ * as the collations of Vars and, most notably, the values of constants.
+ *
+ * This jumble is acquired at the end of parse analysis of each query, and
+ * a 64-bit hash of it is stored into the query's Query.queryId field.
+ * The server then copies this value around, making it available in plan
+ * tree(s) generated from the query.  The executor can then use this value
+ * to blame query costs on the proper queryId.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/nodes/jumblefuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "common/hashfn.h"
+#include "miscadmin.h"
+#include "parser/scansup.h"
+#include "utils/queryjumble.h"
+
+#define JUMBLE_SIZE				1024	/* query serialization buffer size */
+
+/* GUC parameters */
+int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
+
+/* True when compute_query_id is ON, or AUTO and a module requests them */
+bool		query_id_enabled = false;
+
+static void AppendJumble(JumbleState *jstate,
+						 const unsigned char *item, Size size);
+static void RecordConstLocation(JumbleState *jstate, int location);
+static void _jumbleNode(JumbleState *jstate, Node *node);
+static void _jumbleList(JumbleState *jstate, Node *node);
+static void _jumbleRangeTblEntry(JumbleState *jstate, Node *node);
+
+/*
+ * Given a possibly multi-statement source string, confine our attention to the
+ * relevant part of the string.
+ */
+const char *
+CleanQuerytext(const char *query, int *location, int *len)
+{
+	int			query_location = *location;
+	int			query_len = *len;
+
+	/* First apply starting offset, unless it's -1 (unknown). */
+	if (query_location >= 0)
+	{
+		Assert(query_location <= strlen(query));
+		query += query_location;
+		/* Length of 0 (or -1) means "rest of string" */
+		if (query_len <= 0)
+			query_len = strlen(query);
+		else
+			Assert(query_len <= strlen(query));
+	}
+	else
+	{
+		/* If query location is unknown, distrust query_len as well */
+		query_location = 0;
+		query_len = strlen(query);
+	}
+
+	/*
+	 * Discard leading and trailing whitespace, too.  Use scanner_isspace()
+	 * not libc's isspace(), because we want to match the lexer's behavior.
+	 */
+	while (query_len > 0 && scanner_isspace(query[0]))
+		query++, query_location++, query_len--;
+	while (query_len > 0 && scanner_isspace(query[query_len - 1]))
+		query_len--;
+
+	*location = query_location;
+	*len = query_len;
+
+	return query;
+}
+
+JumbleState *
+JumbleQuery(Query *query, const char *querytext)
+{
+	JumbleState *jstate = NULL;
+
+	Assert(IsQueryIdEnabled());
+
+	jstate = (JumbleState *) palloc(sizeof(JumbleState));
+
+	/* Set up workspace for query jumbling */
+	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+	jstate->jumble_len = 0;
+	jstate->clocations_buf_size = 32;
+	jstate->clocations = (LocationLen *)
+		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+	jstate->clocations_count = 0;
+	jstate->highest_extern_param_id = 0;
+
+	/* Compute query ID and mark the Query node with it */
+	_jumbleNode(jstate, (Node *) query);
+	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+													  jstate->jumble_len,
+													  0));
+
+	/*
+	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+	 * prevent confusion with the utility-statement case.
+	 */
+	if (query->queryId == UINT64CONST(0))
+		query->queryId = UINT64CONST(1);
+
+	return jstate;
+}
+
+/*
+ * Enables query identifier computation.
+ *
+ * Third-party plugins can use this function to inform core that they require
+ * a query identifier to be computed.
+ */
+void
+EnableQueryId(void)
+{
+	if (compute_query_id != COMPUTE_QUERY_ID_OFF)
+		query_id_enabled = true;
+}
+
+/*
+ * AppendJumble: Append a value that is substantive in a given query to
+ * the current jumble.
+ */
+static void
+AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
+{
+	unsigned char *jumble = jstate->jumble;
+	Size		jumble_len = jstate->jumble_len;
+
+	/*
+	 * Whenever the jumble buffer is full, we hash the current contents and
+	 * reset the buffer to contain just that hash value, thus relying on the
+	 * hash to summarize everything so far.
+	 */
+	while (size > 0)
+	{
+		Size		part_size;
+
+		if (jumble_len >= JUMBLE_SIZE)
+		{
+			uint64		start_hash;
+
+			start_hash = DatumGetUInt64(hash_any_extended(jumble,
+														  JUMBLE_SIZE, 0));
+			memcpy(jumble, &start_hash, sizeof(start_hash));
+			jumble_len = sizeof(start_hash);
+		}
+		part_size = Min(size, JUMBLE_SIZE - jumble_len);
+		memcpy(jumble + jumble_len, item, part_size);
+		jumble_len += part_size;
+		item += part_size;
+		size -= part_size;
+	}
+	jstate->jumble_len = jumble_len;
+}
+
+/*
+ * Record location of constant within query string of query tree
+ * that is currently being walked.
+ */
+static void
+RecordConstLocation(JumbleState *jstate, int location)
+{
+	/* -1 indicates unknown or undefined location */
+	if (location >= 0)
+	{
+		/* enlarge array if needed */
+		if (jstate->clocations_count >= jstate->clocations_buf_size)
+		{
+			jstate->clocations_buf_size *= 2;
+			jstate->clocations = (LocationLen *)
+				repalloc(jstate->clocations,
+						 jstate->clocations_buf_size *
+						 sizeof(LocationLen));
+		}
+		jstate->clocations[jstate->clocations_count].location = location;
+		/* initialize lengths to -1 to simplify third-party module usage */
+		jstate->clocations[jstate->clocations_count].length = -1;
+		jstate->clocations_count++;
+	}
+}
+
+#define JUMBLE_NODE(item) \
+	_jumbleNode(jstate, (Node *) expr->item)
+#define JUMBLE_LOCATION(location) \
+	RecordConstLocation(jstate, expr->location)
+#define JUMBLE_FIELD(item) \
+	AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item))
+#define JUMBLE_FIELD_SINGLE(item) \
+	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
+#define JUMBLE_STRING(str) \
+do { \
+	if (expr->str) \
+		AppendJumble(jstate, (const unsigned char *) (expr->str), strlen(expr->str) + 1); \
+} while(0)
+
+#include "jumblefuncs.funcs.c"
+
+static void
+_jumbleNode(JumbleState *jstate, Node *node)
+{
+	Node	   *expr = node;
+
+	if (expr == NULL)
+		return;
+
+	/* Guard against stack overflow due to overly complex expressions */
+	check_stack_depth();
+
+	/*
+	 * We always emit the node's NodeTag, then any additional fields that are
+	 * considered significant, and then we recurse to any child nodes.
+	 */
+	JUMBLE_FIELD(type);
+
+	switch (nodeTag(expr))
+	{
+#include "jumblefuncs.switch.c"
+
+		case T_List:
+		case T_IntList:
+		case T_OidList:
+		case T_XidList:
+			_jumbleList(jstate, expr);
+			break;
+
+		case T_RangeTblEntry:
+			_jumbleRangeTblEntry(jstate, expr);
+			break;
+
+		default:
+			/* Only a warning, since we can stumble along anyway */
+			elog(WARNING, "unrecognized node type: %d",
+				 (int) nodeTag(expr));
+			break;
+	}
+
+	/* Special cases */
+	switch (nodeTag(expr))
+	{
+		case T_Param:
+			{
+				Param	   *p = (Param *) node;
+
+				/* Also, track the highest external Param id */
+				if (p->paramkind == PARAM_EXTERN &&
+					p->paramid > jstate->highest_extern_param_id)
+					jstate->highest_extern_param_id = p->paramid;
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+_jumbleList(JumbleState *jstate, Node *node)
+{
+	List	   *expr = (List *) node;
+	ListCell   *l;
+
+	switch (expr->type)
+	{
+		case T_List:
+			foreach(l, expr)
+				_jumbleNode(jstate, lfirst(l));
+			break;
+		case T_IntList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_int(l));
+			break;
+		case T_OidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_oid(l));
+			break;
+		case T_XidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_xid(l));
+			break;
+		default:
+			elog(ERROR, "unrecognized list node type: %d",
+				 (int) expr->type);
+			return;
+	}
+}
+
+static void
+_jumbleRangeTblEntry(JumbleState *jstate, Node *node)
+{
+	RangeTblEntry *expr = (RangeTblEntry *) node;
+
+	JUMBLE_FIELD(rtekind);
+	switch (expr->rtekind)
+	{
+		case RTE_RELATION:
+			JUMBLE_FIELD(relid);
+			JUMBLE_NODE(tablesample);
+			JUMBLE_FIELD(inh);
+			break;
+		case RTE_SUBQUERY:
+			JUMBLE_NODE(subquery);
+			break;
+		case RTE_JOIN:
+			JUMBLE_FIELD(jointype);
+			break;
+		case RTE_FUNCTION:
+			JUMBLE_NODE(functions);
+			break;
+		case RTE_TABLEFUNC:
+			JUMBLE_NODE(tablefunc);
+			break;
+		case RTE_VALUES:
+			JUMBLE_NODE(values_lists);
+			break;
+		case RTE_CTE:
+
+			/*
+			 * Depending on the CTE name here isn't ideal, but it's the only
+			 * info we have to identify the referenced WITH item.
+			 */
+			JUMBLE_STRING(ctename);
+			JUMBLE_FIELD(ctelevelsup);
+			break;
+		case RTE_NAMEDTUPLESTORE:
+			JUMBLE_STRING(enrname);
+			break;
+		case RTE_RESULT:
+			break;
+		default:
+			elog(ERROR, "unrecognized RTE kind: %d", (int) expr->rtekind);
+			break;
+	}
+}
diff --git a/src/backend/nodes/meson.build b/src/backend/nodes/meson.build
index 2ff7dbac1d..58d774478d 100644
--- a/src/backend/nodes/meson.build
+++ b/src/backend/nodes/meson.build
@@ -20,6 +20,7 @@ backend_sources += files(
 nodefunc_sources = files(
   'copyfuncs.c',
   'equalfuncs.c',
+  'jumblefuncs.c',
   'outfuncs.c',
   'readfuncs.c',
 )
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index b9ee4eb48a..2910032930 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -26,7 +26,6 @@ OBJS = \
 	pg_rusage.o \
 	ps_status.o \
 	queryenvironment.o \
-	queryjumble.o \
 	rls.o \
 	sampling.o \
 	superuser.o \
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index e3e99ec5cb..f719c97c05 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -11,7 +11,6 @@ backend_sources += files(
   'pg_rusage.c',
   'ps_status.c',
   'queryenvironment.c',
-  'queryjumble.c',
   'rls.c',
   'sampling.c',
   'superuser.c',
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
deleted file mode 100644
index 328995a7dc..0000000000
--- a/src/backend/utils/misc/queryjumble.c
+++ /dev/null
@@ -1,861 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * queryjumble.c
- *	 Query normalization and fingerprinting.
- *
- * Normalization is a process whereby similar queries, typically differing only
- * in their constants (though the exact rules are somewhat more subtle than
- * that) are recognized as equivalent, and are tracked as a single entry.  This
- * is particularly useful for non-prepared queries.
- *
- * Normalization is implemented by fingerprinting queries, selectively
- * serializing those fields of each query tree's nodes that are judged to be
- * essential to the query.  This is referred to as a query jumble.  This is
- * distinct from a regular serialization in that various extraneous
- * information is ignored as irrelevant or not essential to the query, such
- * as the collations of Vars and, most notably, the values of constants.
- *
- * This jumble is acquired at the end of parse analysis of each query, and
- * a 64-bit hash of it is stored into the query's Query.queryId field.
- * The server then copies this value around, making it available in plan
- * tree(s) generated from the query.  The executor can then use this value
- * to blame query costs on the proper queryId.
- *
- * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- *	  src/backend/utils/misc/queryjumble.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "common/hashfn.h"
-#include "miscadmin.h"
-#include "parser/scansup.h"
-#include "utils/queryjumble.h"
-
-#define JUMBLE_SIZE				1024	/* query serialization buffer size */
-
-/* GUC parameters */
-int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
-
-/* True when compute_query_id is ON, or AUTO and a module requests them */
-bool		query_id_enabled = false;
-
-static uint64 compute_utility_query_id(const char *query_text,
-									   int query_location, int query_len);
-static void AppendJumble(JumbleState *jstate,
-						 const unsigned char *item, Size size);
-static void JumbleQueryInternal(JumbleState *jstate, Query *query);
-static void JumbleRangeTable(JumbleState *jstate, List *rtable);
-static void JumbleRowMarks(JumbleState *jstate, List *rowMarks);
-static void JumbleExpr(JumbleState *jstate, Node *node);
-static void RecordConstLocation(JumbleState *jstate, int location);
-
-/*
- * Given a possibly multi-statement source string, confine our attention to the
- * relevant part of the string.
- */
-const char *
-CleanQuerytext(const char *query, int *location, int *len)
-{
-	int			query_location = *location;
-	int			query_len = *len;
-
-	/* First apply starting offset, unless it's -1 (unknown). */
-	if (query_location >= 0)
-	{
-		Assert(query_location <= strlen(query));
-		query += query_location;
-		/* Length of 0 (or -1) means "rest of string" */
-		if (query_len <= 0)
-			query_len = strlen(query);
-		else
-			Assert(query_len <= strlen(query));
-	}
-	else
-	{
-		/* If query location is unknown, distrust query_len as well */
-		query_location = 0;
-		query_len = strlen(query);
-	}
-
-	/*
-	 * Discard leading and trailing whitespace, too.  Use scanner_isspace()
-	 * not libc's isspace(), because we want to match the lexer's behavior.
-	 */
-	while (query_len > 0 && scanner_isspace(query[0]))
-		query++, query_location++, query_len--;
-	while (query_len > 0 && scanner_isspace(query[query_len - 1]))
-		query_len--;
-
-	*location = query_location;
-	*len = query_len;
-
-	return query;
-}
-
-JumbleState *
-JumbleQuery(Query *query, const char *querytext)
-{
-	JumbleState *jstate = NULL;
-
-	Assert(IsQueryIdEnabled());
-
-	if (query->utilityStmt)
-	{
-		query->queryId = compute_utility_query_id(querytext,
-												  query->stmt_location,
-												  query->stmt_len);
-	}
-	else
-	{
-		jstate = (JumbleState *) palloc(sizeof(JumbleState));
-
-		/* Set up workspace for query jumbling */
-		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-		jstate->jumble_len = 0;
-		jstate->clocations_buf_size = 32;
-		jstate->clocations = (LocationLen *)
-			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-		jstate->clocations_count = 0;
-		jstate->highest_extern_param_id = 0;
-
-		/* Compute query ID and mark the Query node with it */
-		JumbleQueryInternal(jstate, query);
-		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-														  jstate->jumble_len,
-														  0));
-
-		/*
-		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-		 * prevent confusion with the utility-statement case.
-		 */
-		if (query->queryId == UINT64CONST(0))
-			query->queryId = UINT64CONST(1);
-	}
-
-	return jstate;
-}
-
-/*
- * Enables query identifier computation.
- *
- * Third-party plugins can use this function to inform core that they require
- * a query identifier to be computed.
- */
-void
-EnableQueryId(void)
-{
-	if (compute_query_id != COMPUTE_QUERY_ID_OFF)
-		query_id_enabled = true;
-}
-
-/*
- * Compute a query identifier for the given utility query string.
- */
-static uint64
-compute_utility_query_id(const char *query_text, int query_location, int query_len)
-{
-	uint64		queryId;
-	const char *sql;
-
-	/*
-	 * Confine our attention to the relevant part of the string, if the query
-	 * is a portion of a multi-statement source string.
-	 */
-	sql = CleanQuerytext(query_text, &query_location, &query_len);
-
-	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
-											   query_len, 0));
-
-	/*
-	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
-	 * 2 instead, queryID 1 is already in use for normal statements.
-	 */
-	if (queryId == UINT64CONST(0))
-		queryId = UINT64CONST(2);
-
-	return queryId;
-}
-
-/*
- * AppendJumble: Append a value that is substantive in a given query to
- * the current jumble.
- */
-static void
-AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
-{
-	unsigned char *jumble = jstate->jumble;
-	Size		jumble_len = jstate->jumble_len;
-
-	/*
-	 * Whenever the jumble buffer is full, we hash the current contents and
-	 * reset the buffer to contain just that hash value, thus relying on the
-	 * hash to summarize everything so far.
-	 */
-	while (size > 0)
-	{
-		Size		part_size;
-
-		if (jumble_len >= JUMBLE_SIZE)
-		{
-			uint64		start_hash;
-
-			start_hash = DatumGetUInt64(hash_any_extended(jumble,
-														  JUMBLE_SIZE, 0));
-			memcpy(jumble, &start_hash, sizeof(start_hash));
-			jumble_len = sizeof(start_hash);
-		}
-		part_size = Min(size, JUMBLE_SIZE - jumble_len);
-		memcpy(jumble + jumble_len, item, part_size);
-		jumble_len += part_size;
-		item += part_size;
-		size -= part_size;
-	}
-	jstate->jumble_len = jumble_len;
-}
-
-/*
- * Wrappers around AppendJumble to encapsulate details of serialization
- * of individual local variable elements.
- */
-#define APP_JUMB(item) \
-	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
-#define APP_JUMB_STRING(str) \
-	AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1)
-
-/*
- * JumbleQueryInternal: Selectively serialize the query tree, appending
- * significant data to the "query jumble" while ignoring nonsignificant data.
- *
- * Rule of thumb for what to include is that we should ignore anything not
- * semantically significant (such as alias names) as well as anything that can
- * be deduced from child nodes (else we'd just be double-hashing that piece
- * of information).
- */
-static void
-JumbleQueryInternal(JumbleState *jstate, Query *query)
-{
-	Assert(IsA(query, Query));
-	Assert(query->utilityStmt == NULL);
-
-	APP_JUMB(query->commandType);
-	/* resultRelation is usually predictable from commandType */
-	JumbleExpr(jstate, (Node *) query->cteList);
-	JumbleRangeTable(jstate, query->rtable);
-	JumbleExpr(jstate, (Node *) query->jointree);
-	JumbleExpr(jstate, (Node *) query->mergeActionList);
-	JumbleExpr(jstate, (Node *) query->targetList);
-	JumbleExpr(jstate, (Node *) query->onConflict);
-	JumbleExpr(jstate, (Node *) query->returningList);
-	JumbleExpr(jstate, (Node *) query->groupClause);
-	APP_JUMB(query->groupDistinct);
-	JumbleExpr(jstate, (Node *) query->groupingSets);
-	JumbleExpr(jstate, query->havingQual);
-	JumbleExpr(jstate, (Node *) query->windowClause);
-	JumbleExpr(jstate, (Node *) query->distinctClause);
-	JumbleExpr(jstate, (Node *) query->sortClause);
-	JumbleExpr(jstate, query->limitOffset);
-	JumbleExpr(jstate, query->limitCount);
-	APP_JUMB(query->limitOption);
-	JumbleRowMarks(jstate, query->rowMarks);
-	JumbleExpr(jstate, query->setOperations);
-}
-
-/*
- * Jumble a range table
- */
-static void
-JumbleRangeTable(JumbleState *jstate, List *rtable)
-{
-	ListCell   *lc;
-
-	foreach(lc, rtable)
-	{
-		RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-		APP_JUMB(rte->rtekind);
-		switch (rte->rtekind)
-		{
-			case RTE_RELATION:
-				APP_JUMB(rte->relid);
-				JumbleExpr(jstate, (Node *) rte->tablesample);
-				APP_JUMB(rte->inh);
-				break;
-			case RTE_SUBQUERY:
-				JumbleQueryInternal(jstate, rte->subquery);
-				break;
-			case RTE_JOIN:
-				APP_JUMB(rte->jointype);
-				break;
-			case RTE_FUNCTION:
-				JumbleExpr(jstate, (Node *) rte->functions);
-				break;
-			case RTE_TABLEFUNC:
-				JumbleExpr(jstate, (Node *) rte->tablefunc);
-				break;
-			case RTE_VALUES:
-				JumbleExpr(jstate, (Node *) rte->values_lists);
-				break;
-			case RTE_CTE:
-
-				/*
-				 * Depending on the CTE name here isn't ideal, but it's the
-				 * only info we have to identify the referenced WITH item.
-				 */
-				APP_JUMB_STRING(rte->ctename);
-				APP_JUMB(rte->ctelevelsup);
-				break;
-			case RTE_NAMEDTUPLESTORE:
-				APP_JUMB_STRING(rte->enrname);
-				break;
-			case RTE_RESULT:
-				break;
-			default:
-				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
-				break;
-		}
-	}
-}
-
-/*
- * Jumble a rowMarks list
- */
-static void
-JumbleRowMarks(JumbleState *jstate, List *rowMarks)
-{
-	ListCell   *lc;
-
-	foreach(lc, rowMarks)
-	{
-		RowMarkClause *rowmark = lfirst_node(RowMarkClause, lc);
-
-		if (!rowmark->pushedDown)
-		{
-			APP_JUMB(rowmark->rti);
-			APP_JUMB(rowmark->strength);
-			APP_JUMB(rowmark->waitPolicy);
-		}
-	}
-}
-
-/*
- * Jumble an expression tree
- *
- * In general this function should handle all the same node types that
- * expression_tree_walker() does, and therefore it's coded to be as parallel
- * to that function as possible.  However, since we are only invoked on
- * queries immediately post-parse-analysis, we need not handle node types
- * that only appear in planning.
- *
- * Note: the reason we don't simply use expression_tree_walker() is that the
- * point of that function is to support tree walkers that don't care about
- * most tree node types, but here we care about all types.  We should complain
- * about any unrecognized node type.
- */
-static void
-JumbleExpr(JumbleState *jstate, Node *node)
-{
-	ListCell   *temp;
-
-	if (node == NULL)
-		return;
-
-	/* Guard against stack overflow due to overly complex expressions */
-	check_stack_depth();
-
-	/*
-	 * We always emit the node's NodeTag, then any additional fields that are
-	 * considered significant, and then we recurse to any child nodes.
-	 */
-	APP_JUMB(node->type);
-
-	switch (nodeTag(node))
-	{
-		case T_Var:
-			{
-				Var		   *var = (Var *) node;
-
-				APP_JUMB(var->varno);
-				APP_JUMB(var->varattno);
-				APP_JUMB(var->varlevelsup);
-			}
-			break;
-		case T_Const:
-			{
-				Const	   *c = (Const *) node;
-
-				/* We jumble only the constant's type, not its value */
-				APP_JUMB(c->consttype);
-				/* Also, record its parse location for query normalization */
-				RecordConstLocation(jstate, c->location);
-			}
-			break;
-		case T_Param:
-			{
-				Param	   *p = (Param *) node;
-
-				APP_JUMB(p->paramkind);
-				APP_JUMB(p->paramid);
-				APP_JUMB(p->paramtype);
-				/* Also, track the highest external Param id */
-				if (p->paramkind == PARAM_EXTERN &&
-					p->paramid > jstate->highest_extern_param_id)
-					jstate->highest_extern_param_id = p->paramid;
-			}
-			break;
-		case T_Aggref:
-			{
-				Aggref	   *expr = (Aggref *) node;
-
-				APP_JUMB(expr->aggfnoid);
-				JumbleExpr(jstate, (Node *) expr->aggdirectargs);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggorder);
-				JumbleExpr(jstate, (Node *) expr->aggdistinct);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_GroupingFunc:
-			{
-				GroupingFunc *grpnode = (GroupingFunc *) node;
-
-				JumbleExpr(jstate, (Node *) grpnode->refs);
-				APP_JUMB(grpnode->agglevelsup);
-			}
-			break;
-		case T_WindowFunc:
-			{
-				WindowFunc *expr = (WindowFunc *) node;
-
-				APP_JUMB(expr->winfnoid);
-				APP_JUMB(expr->winref);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_SubscriptingRef:
-			{
-				SubscriptingRef *sbsref = (SubscriptingRef *) node;
-
-				JumbleExpr(jstate, (Node *) sbsref->refupperindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refassgnexpr);
-			}
-			break;
-		case T_FuncExpr:
-			{
-				FuncExpr   *expr = (FuncExpr *) node;
-
-				APP_JUMB(expr->funcid);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_NamedArgExpr:
-			{
-				NamedArgExpr *nae = (NamedArgExpr *) node;
-
-				APP_JUMB(nae->argnumber);
-				JumbleExpr(jstate, (Node *) nae->arg);
-			}
-			break;
-		case T_OpExpr:
-		case T_DistinctExpr:	/* struct-equivalent to OpExpr */
-		case T_NullIfExpr:		/* struct-equivalent to OpExpr */
-			{
-				OpExpr	   *expr = (OpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_ScalarArrayOpExpr:
-			{
-				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				APP_JUMB(expr->useOr);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_BoolExpr:
-			{
-				BoolExpr   *expr = (BoolExpr *) node;
-
-				APP_JUMB(expr->boolop);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_SubLink:
-			{
-				SubLink    *sublink = (SubLink *) node;
-
-				APP_JUMB(sublink->subLinkType);
-				APP_JUMB(sublink->subLinkId);
-				JumbleExpr(jstate, (Node *) sublink->testexpr);
-				JumbleQueryInternal(jstate, castNode(Query, sublink->subselect));
-			}
-			break;
-		case T_FieldSelect:
-			{
-				FieldSelect *fs = (FieldSelect *) node;
-
-				APP_JUMB(fs->fieldnum);
-				JumbleExpr(jstate, (Node *) fs->arg);
-			}
-			break;
-		case T_FieldStore:
-			{
-				FieldStore *fstore = (FieldStore *) node;
-
-				JumbleExpr(jstate, (Node *) fstore->arg);
-				JumbleExpr(jstate, (Node *) fstore->newvals);
-			}
-			break;
-		case T_RelabelType:
-			{
-				RelabelType *rt = (RelabelType *) node;
-
-				APP_JUMB(rt->resulttype);
-				JumbleExpr(jstate, (Node *) rt->arg);
-			}
-			break;
-		case T_CoerceViaIO:
-			{
-				CoerceViaIO *cio = (CoerceViaIO *) node;
-
-				APP_JUMB(cio->resulttype);
-				JumbleExpr(jstate, (Node *) cio->arg);
-			}
-			break;
-		case T_ArrayCoerceExpr:
-			{
-				ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node;
-
-				APP_JUMB(acexpr->resulttype);
-				JumbleExpr(jstate, (Node *) acexpr->arg);
-				JumbleExpr(jstate, (Node *) acexpr->elemexpr);
-			}
-			break;
-		case T_ConvertRowtypeExpr:
-			{
-				ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node;
-
-				APP_JUMB(crexpr->resulttype);
-				JumbleExpr(jstate, (Node *) crexpr->arg);
-			}
-			break;
-		case T_CollateExpr:
-			{
-				CollateExpr *ce = (CollateExpr *) node;
-
-				APP_JUMB(ce->collOid);
-				JumbleExpr(jstate, (Node *) ce->arg);
-			}
-			break;
-		case T_CaseExpr:
-			{
-				CaseExpr   *caseexpr = (CaseExpr *) node;
-
-				JumbleExpr(jstate, (Node *) caseexpr->arg);
-				foreach(temp, caseexpr->args)
-				{
-					CaseWhen   *when = lfirst_node(CaseWhen, temp);
-
-					JumbleExpr(jstate, (Node *) when->expr);
-					JumbleExpr(jstate, (Node *) when->result);
-				}
-				JumbleExpr(jstate, (Node *) caseexpr->defresult);
-			}
-			break;
-		case T_CaseTestExpr:
-			{
-				CaseTestExpr *ct = (CaseTestExpr *) node;
-
-				APP_JUMB(ct->typeId);
-			}
-			break;
-		case T_ArrayExpr:
-			JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements);
-			break;
-		case T_RowExpr:
-			JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args);
-			break;
-		case T_RowCompareExpr:
-			{
-				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-
-				APP_JUMB(rcexpr->rctype);
-				JumbleExpr(jstate, (Node *) rcexpr->largs);
-				JumbleExpr(jstate, (Node *) rcexpr->rargs);
-			}
-			break;
-		case T_CoalesceExpr:
-			JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args);
-			break;
-		case T_MinMaxExpr:
-			{
-				MinMaxExpr *mmexpr = (MinMaxExpr *) node;
-
-				APP_JUMB(mmexpr->op);
-				JumbleExpr(jstate, (Node *) mmexpr->args);
-			}
-			break;
-		case T_XmlExpr:
-			{
-				XmlExpr    *xexpr = (XmlExpr *) node;
-
-				APP_JUMB(xexpr->op);
-				JumbleExpr(jstate, (Node *) xexpr->named_args);
-				JumbleExpr(jstate, (Node *) xexpr->args);
-			}
-			break;
-		case T_NullTest:
-			{
-				NullTest   *nt = (NullTest *) node;
-
-				APP_JUMB(nt->nulltesttype);
-				JumbleExpr(jstate, (Node *) nt->arg);
-			}
-			break;
-		case T_BooleanTest:
-			{
-				BooleanTest *bt = (BooleanTest *) node;
-
-				APP_JUMB(bt->booltesttype);
-				JumbleExpr(jstate, (Node *) bt->arg);
-			}
-			break;
-		case T_CoerceToDomain:
-			{
-				CoerceToDomain *cd = (CoerceToDomain *) node;
-
-				APP_JUMB(cd->resulttype);
-				JumbleExpr(jstate, (Node *) cd->arg);
-			}
-			break;
-		case T_CoerceToDomainValue:
-			{
-				CoerceToDomainValue *cdv = (CoerceToDomainValue *) node;
-
-				APP_JUMB(cdv->typeId);
-			}
-			break;
-		case T_SetToDefault:
-			{
-				SetToDefault *sd = (SetToDefault *) node;
-
-				APP_JUMB(sd->typeId);
-			}
-			break;
-		case T_CurrentOfExpr:
-			{
-				CurrentOfExpr *ce = (CurrentOfExpr *) node;
-
-				APP_JUMB(ce->cvarno);
-				if (ce->cursor_name)
-					APP_JUMB_STRING(ce->cursor_name);
-				APP_JUMB(ce->cursor_param);
-			}
-			break;
-		case T_NextValueExpr:
-			{
-				NextValueExpr *nve = (NextValueExpr *) node;
-
-				APP_JUMB(nve->seqid);
-				APP_JUMB(nve->typeId);
-			}
-			break;
-		case T_InferenceElem:
-			{
-				InferenceElem *ie = (InferenceElem *) node;
-
-				APP_JUMB(ie->infercollid);
-				APP_JUMB(ie->inferopclass);
-				JumbleExpr(jstate, ie->expr);
-			}
-			break;
-		case T_TargetEntry:
-			{
-				TargetEntry *tle = (TargetEntry *) node;
-
-				APP_JUMB(tle->resno);
-				APP_JUMB(tle->ressortgroupref);
-				JumbleExpr(jstate, (Node *) tle->expr);
-			}
-			break;
-		case T_RangeTblRef:
-			{
-				RangeTblRef *rtr = (RangeTblRef *) node;
-
-				APP_JUMB(rtr->rtindex);
-			}
-			break;
-		case T_JoinExpr:
-			{
-				JoinExpr   *join = (JoinExpr *) node;
-
-				APP_JUMB(join->jointype);
-				APP_JUMB(join->isNatural);
-				APP_JUMB(join->rtindex);
-				JumbleExpr(jstate, join->larg);
-				JumbleExpr(jstate, join->rarg);
-				JumbleExpr(jstate, join->quals);
-			}
-			break;
-		case T_FromExpr:
-			{
-				FromExpr   *from = (FromExpr *) node;
-
-				JumbleExpr(jstate, (Node *) from->fromlist);
-				JumbleExpr(jstate, from->quals);
-			}
-			break;
-		case T_OnConflictExpr:
-			{
-				OnConflictExpr *conf = (OnConflictExpr *) node;
-
-				APP_JUMB(conf->action);
-				JumbleExpr(jstate, (Node *) conf->arbiterElems);
-				JumbleExpr(jstate, conf->arbiterWhere);
-				JumbleExpr(jstate, (Node *) conf->onConflictSet);
-				JumbleExpr(jstate, conf->onConflictWhere);
-				APP_JUMB(conf->constraint);
-				APP_JUMB(conf->exclRelIndex);
-				JumbleExpr(jstate, (Node *) conf->exclRelTlist);
-			}
-			break;
-		case T_MergeAction:
-			{
-				MergeAction *mergeaction = (MergeAction *) node;
-
-				APP_JUMB(mergeaction->matched);
-				APP_JUMB(mergeaction->commandType);
-				JumbleExpr(jstate, mergeaction->qual);
-				JumbleExpr(jstate, (Node *) mergeaction->targetList);
-			}
-			break;
-		case T_List:
-			foreach(temp, (List *) node)
-			{
-				JumbleExpr(jstate, (Node *) lfirst(temp));
-			}
-			break;
-		case T_IntList:
-			foreach(temp, (List *) node)
-			{
-				APP_JUMB(lfirst_int(temp));
-			}
-			break;
-		case T_SortGroupClause:
-			{
-				SortGroupClause *sgc = (SortGroupClause *) node;
-
-				APP_JUMB(sgc->tleSortGroupRef);
-				APP_JUMB(sgc->eqop);
-				APP_JUMB(sgc->sortop);
-				APP_JUMB(sgc->nulls_first);
-			}
-			break;
-		case T_GroupingSet:
-			{
-				GroupingSet *gsnode = (GroupingSet *) node;
-
-				JumbleExpr(jstate, (Node *) gsnode->content);
-			}
-			break;
-		case T_WindowClause:
-			{
-				WindowClause *wc = (WindowClause *) node;
-
-				APP_JUMB(wc->winref);
-				APP_JUMB(wc->frameOptions);
-				JumbleExpr(jstate, (Node *) wc->partitionClause);
-				JumbleExpr(jstate, (Node *) wc->orderClause);
-				JumbleExpr(jstate, wc->startOffset);
-				JumbleExpr(jstate, wc->endOffset);
-			}
-			break;
-		case T_CommonTableExpr:
-			{
-				CommonTableExpr *cte = (CommonTableExpr *) node;
-
-				/* we store the string name because RTE_CTE RTEs need it */
-				APP_JUMB_STRING(cte->ctename);
-				APP_JUMB(cte->ctematerialized);
-				JumbleQueryInternal(jstate, castNode(Query, cte->ctequery));
-			}
-			break;
-		case T_SetOperationStmt:
-			{
-				SetOperationStmt *setop = (SetOperationStmt *) node;
-
-				APP_JUMB(setop->op);
-				APP_JUMB(setop->all);
-				JumbleExpr(jstate, setop->larg);
-				JumbleExpr(jstate, setop->rarg);
-			}
-			break;
-		case T_RangeTblFunction:
-			{
-				RangeTblFunction *rtfunc = (RangeTblFunction *) node;
-
-				JumbleExpr(jstate, rtfunc->funcexpr);
-			}
-			break;
-		case T_TableFunc:
-			{
-				TableFunc  *tablefunc = (TableFunc *) node;
-
-				JumbleExpr(jstate, tablefunc->docexpr);
-				JumbleExpr(jstate, tablefunc->rowexpr);
-				JumbleExpr(jstate, (Node *) tablefunc->colexprs);
-			}
-			break;
-		case T_TableSampleClause:
-			{
-				TableSampleClause *tsc = (TableSampleClause *) node;
-
-				APP_JUMB(tsc->tsmhandler);
-				JumbleExpr(jstate, (Node *) tsc->args);
-				JumbleExpr(jstate, (Node *) tsc->repeatable);
-			}
-			break;
-		default:
-			/* Only a warning, since we can stumble along anyway */
-			elog(WARNING, "unrecognized node type: %d",
-				 (int) nodeTag(node));
-			break;
-	}
-}
-
-/*
- * Record location of constant within query string of query tree
- * that is currently being walked.
- */
-static void
-RecordConstLocation(JumbleState *jstate, int location)
-{
-	/* -1 indicates unknown or undefined location */
-	if (location >= 0)
-	{
-		/* enlarge array if needed */
-		if (jstate->clocations_count >= jstate->clocations_buf_size)
-		{
-			jstate->clocations_buf_size *= 2;
-			jstate->clocations = (LocationLen *)
-				repalloc(jstate->clocations,
-						 jstate->clocations_buf_size *
-						 sizeof(LocationLen));
-		}
-		jstate->clocations[jstate->clocations_count].location = location;
-		/* initialize lengths to -1 to simplify third-party module usage */
-		jstate->clocations[jstate->clocations_count].length = -1;
-		jstate->clocations_count++;
-	}
-}
-- 
2.39.0

#6Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Michael Paquier (#5)
Re: Generating code for query jumbling through gen_node_support.pl

On 13.01.23 08:54, Michael Paquier wrote:

Using a "jumble_ignore" flag is equally invasive to using an
"jumble_include" flag for each field, I am afraid, as the number of
fields in the nodes included in jumbles is pretty equivalent to the
number of fields ignored. I tend to prefer the approach of ignoring
things though, which is more consistent with the practive for node
read, write and copy.

I concur that jumble_ignore is better. I suppose you placed the
jumble_ignore markers to maintain parity with the existing code, but I
think that some the markers are actually wrong and are just errors of
omission in the existing code (such as Query.override, to pick a random
example). The way you have structured this would allow us to find and
analyze such errors better.

Anyway, when it comes to the location, another thing that can be
considered here would be to require a node-level flag for the nodes on
which we want to track the location.  This overlaps a bit with the
fields satisfying "($t eq 'int' && $f =~ 'location$')", but it removes
most of the code changes like this one as at the end we only care
about the location for Const nodes:
-   int         location;       /* token location, or -1 if unknown */
+   int         location pg_node_attr(jumble_ignore);   /* token location, or -1
+                                                        * if unknown */

I have taken this approach in v2 of the patch, shaving ~100 lines of
more code as there is no need to mark all these location fields with
"jumble_ignore" anymore, except for Const, of course.

I don't understand why you chose that when we already have an
established way. This would just make the jumble annotations
inconsistent with the other annotations.

I also have two suggestions to make this patch more palatable:

1. Make a separate patch to reformat long comments, like
835d476fd21bcfb60b055941dee8c3d9559af14c.

2. Make a separate patch to move the jumble support to
src/backend/nodes/jumblefuncs.c. (It was probably a mistake that it
wasn't there to begin with, and some of the errors of omission alluded
to above are probably caused by it having been hidden away in the wrong
place.)

#7Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#6)
Re: Generating code for query jumbling through gen_node_support.pl

On Mon, Jan 16, 2023 at 03:13:35PM +0100, Peter Eisentraut wrote:

On 13.01.23 08:54, Michael Paquier wrote:

Using a "jumble_ignore" flag is equally invasive to using an
"jumble_include" flag for each field, I am afraid, as the number of
fields in the nodes included in jumbles is pretty equivalent to the
number of fields ignored. I tend to prefer the approach of ignoring
things though, which is more consistent with the practive for node
read, write and copy.

I concur that jumble_ignore is better. I suppose you placed the
jumble_ignore markers to maintain parity with the existing code, but I think
that some the markers are actually wrong and are just errors of omission in
the existing code (such as Query.override, to pick a random example). The
way you have structured this would allow us to find and analyze such errors
better.

Thanks. Yes, I have made an effort of going down to maintain an exact
compatibility with the existing code for now. My take is that
removing or adding more things into the jumble deserves its own
discussion. I think that's a bit better once this code is automated
with the nodes, now it would not be difficult either to adjust HEAD
depending on that. Only the analysis effort is different.

Anyway, when it comes to the location, another thing that can be
considered here would be to require a node-level flag for the nodes on
which we want to track the location.  This overlaps a bit with the
fields satisfying "($t eq 'int' && $f =~ 'location$')", but it removes
most of the code changes like this one as at the end we only care
about the location for Const nodes:
-   int         location;       /* token location, or -1 if unknown */
+   int         location pg_node_attr(jumble_ignore);   /* token location, or -1
+                                                        * if unknown */

I have taken this approach in v2 of the patch, shaving ~100 lines of
more code as there is no need to mark all these location fields with
"jumble_ignore" anymore, except for Const, of course.

I don't understand why you chose that when we already have an established
way. This would just make the jumble annotations inconsistent with the
other annotations.

Because we don't want to track the location of all the nodes! If we
do that, pg_stat_statements would begin to parameterize a lot more
things, from column aliases to full contents of IN or WITH clauses.
The root point is that we only want to track the jumble location for
Const nodes now. In order to do that, there are two approaches:
- Mark all the locations with jumble_ignore: more invasive, but
it requires only one per-field attribute with "jumble_ignore". This
is what v1 did.
- Mark only the fields where we want to track the location with a
second node type, like "jumble_location". We could restrict that
depending on the field type or its name on top of checking the field
attribute, whatever. This is what v2 did.

Which approach do you prefer? Marking all the locations we don't want
with jumble_ignore, or introduce a second attribute with
jumble_location. I'd tend to prefer the approach of minimizing the
number of node and field attributes, FWIW. Now you have worked on
this area previously, so your view may be more adapted than my
thinking process

The long-term perspective is that I'd like to extend the tracking of
the locations to more DDL nodes, like parameters of SET statements or
parts of CALL statements. Not to mention that this makes the work of
forks easier. This is a separate discussion.

I also have two suggestions to make this patch more palatable:

1. Make a separate patch to reformat long comments, like
835d476fd21bcfb60b055941dee8c3d9559af14c.

2. Make a separate patch to move the jumble support to
src/backend/nodes/jumblefuncs.c. (It was probably a mistake that it wasn't
there to begin with, and some of the errors of omission alluded to above are
probably caused by it having been hidden away in the wrong place.)

Both suggestions make sense. I'll shape the next series of the patch
to do something among these lines.

My question about the location tracking greatly influences the first
patch (comment reformating) and third patch (automated generation of
the code) of the series, so do you have a preference about that?
--
Michael

#8Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Michael Paquier (#7)
Re: Generating code for query jumbling through gen_node_support.pl

On 17.01.23 04:48, Michael Paquier wrote:

Anyway, when it comes to the location, another thing that can be
considered here would be to require a node-level flag for the nodes on
which we want to track the location.  This overlaps a bit with the
fields satisfying "($t eq 'int' && $f =~ 'location$')", but it removes
most of the code changes like this one as at the end we only care
about the location for Const nodes:
-   int         location;       /* token location, or -1 if unknown */
+   int         location pg_node_attr(jumble_ignore);   /* token location, or -1
+                                                        * if unknown */

I have taken this approach in v2 of the patch, shaving ~100 lines of
more code as there is no need to mark all these location fields with
"jumble_ignore" anymore, except for Const, of course.

I don't understand why you chose that when we already have an established
way. This would just make the jumble annotations inconsistent with the
other annotations.

Because we don't want to track the location of all the nodes! If we
do that, pg_stat_statements would begin to parameterize a lot more
things, from column aliases to full contents of IN or WITH clauses.
The root point is that we only want to track the jumble location for
Const nodes now. In order to do that, there are two approaches:
- Mark all the locations with jumble_ignore: more invasive, but
it requires only one per-field attribute with "jumble_ignore". This
is what v1 did.

Ok, I understand now, and I agree with this approach over the opposite.
I was confused because the snippet you showed above used
"jumble_ignore", but your patch is correct as it uses "jumble_location".

That said, the term "jumble" is really weird, because in the sense that
we are using it here it means, approximately, "to mix together", "to
unify". So what we are doing with the Const nodes is really to *not*
jumble the location, but for all other node types we are jumbling the
location. At least that is my understanding.

#9Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#8)
Re: Generating code for query jumbling through gen_node_support.pl

On Tue, Jan 17, 2023 at 08:43:44AM +0100, Peter Eisentraut wrote:

Ok, I understand now, and I agree with this approach over the opposite. I
was confused because the snippet you showed above used "jumble_ignore", but
your patch is correct as it uses "jumble_location".

Okay. I'll refresh the patch set so as we have only "jumble_ignore",
then, like v1, with preparatory patches for what you mentioned and
anything that comes into mind.

That said, the term "jumble" is really weird, because in the sense that we
are using it here it means, approximately, "to mix together", "to unify".
So what we are doing with the Const nodes is really to *not* jumble the
location, but for all other node types we are jumbling the location. At
least that is my understanding.

I am quite familiar with this term, FWIW. That's what we've inherited
from the days where this has been introduced in pg_stat_statements.
--
Michael

#10Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#9)
3 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Tue, Jan 17, 2023 at 04:52:28PM +0900, Michael Paquier wrote:

On Tue, Jan 17, 2023 at 08:43:44AM +0100, Peter Eisentraut wrote:

Ok, I understand now, and I agree with this approach over the opposite. I
was confused because the snippet you showed above used "jumble_ignore", but
your patch is correct as it uses "jumble_location".

Okay. I'll refresh the patch set so as we have only "jumble_ignore",
then, like v1, with preparatory patches for what you mentioned and
anything that comes into mind.

This is done as of the patch series v3 attached:
- 0001 reformats all the comments of the nodes.
- 0002 moves the current files for query jumble as of queryjumble.c ->
queryjumblefuncs.c and utils/queryjumble.h -> nodes/queryjumble.h.
- 0003 is the core feature, where I have done a second pass over the
nodes to make sure that things map with HEAD, incorporating the extra
docs coming from v2, adding a bit more.

That said, the term "jumble" is really weird, because in the sense that we
are using it here it means, approximately, "to mix together", "to unify".
So what we are doing with the Const nodes is really to *not* jumble the
location, but for all other node types we are jumbling the location. At
least that is my understanding.

I am quite familiar with this term, FWIW. That's what we've inherited
from the days where this has been introduced in pg_stat_statements.

I have renamed the node attributes to query_jumble_ignore and
no_query_jumble at the end, after considering Peter's point that only
"jumble" could be fuzzy here. The file names are changed in
consequence.

While doing all that, I have done some micro-benchmarking of
JumbleQuery(), making it loop 50M times on my laptop each time a query
ID is computed (hideous hack with a loop in queryjumble.c):
- For non-utility queries, aka queries that go through
JumbleQueryInternal(), I am measuring a repeatable ~10% improvement
with the generated code over HEAD, which is kind of nice. I have
tested a few DMLs and simple SELECTs, still it looks like a trend.
- For utility queries, the new code is competing against
hash_any_extended() with the query string, which is going to be hard
to beat.. I am measuring what looks like a 5 times slowdown, at
least, and more depending on the depth of the query tree. That's not
surprising. On a 50M loop, this comes down to compare a computation
of 100ns to 5ns for a 20-time slowdown for example, still this could
justify the addition of a GUC to control whether utility queries have
their query ID compiled depending on their nodes or their string
hash, as this could become noticeable in OLTP workloads with loads
of short statements and BEGIN/COMMIT queries?

Thoughts or comments?
--
Michael

Attachments:

v3-0001-Rework-format-of-comments-for-nodes.patchtext/x-diff; charset=us-asciiDownload
From 43a5bdffdb9dbe3ecb40e9bf8a5f0e9f12ceff32 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 18 Jan 2023 14:26:12 +0900
Subject: [PATCH v3 1/3] Rework format of comments for nodes

This is similar to 835d476, except that this one is to add node
properties related to query jumbling.
---
 src/include/nodes/parsenodes.h | 261 ++++++++++++------
 src/include/nodes/plannodes.h  |   3 +-
 src/include/nodes/primnodes.h  | 469 ++++++++++++++++++++++-----------
 3 files changed, 487 insertions(+), 246 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cfeca96d53..eec51e3ee2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -123,7 +123,8 @@ typedef struct Query
 
 	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
 
-	QuerySource querySource;	/* where did I come from? */
+	/* where did I come from? */
+	QuerySource querySource;
 
 	/*
 	 * query identifier (can be set by plugins); ignored for equal, as it
@@ -131,39 +132,58 @@ typedef struct Query
 	 */
 	uint64		queryId pg_node_attr(equal_ignore, read_write_ignore, read_as(0));
 
-	bool		canSetTag;		/* do I set the command result tag? */
+	/* do I set the command result tag? */
+	bool		canSetTag;
 
 	Node	   *utilityStmt;	/* non-null if commandType == CMD_UTILITY */
 
-	int			resultRelation; /* rtable index of target relation for
-								 * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
+	/*
+	 * rtable index of target relation for INSERT/UPDATE/DELETE/MERGE; 0 for
+	 * SELECT.
+	 */
+	int			resultRelation;
 
-	bool		hasAggs;		/* has aggregates in tlist or havingQual */
-	bool		hasWindowFuncs; /* has window functions in tlist */
-	bool		hasTargetSRFs;	/* has set-returning functions in tlist */
-	bool		hasSubLinks;	/* has subquery SubLink */
-	bool		hasDistinctOn;	/* distinctClause is from DISTINCT ON */
-	bool		hasRecursive;	/* WITH RECURSIVE was specified */
-	bool		hasModifyingCTE;	/* has INSERT/UPDATE/DELETE in WITH */
-	bool		hasForUpdate;	/* FOR [KEY] UPDATE/SHARE was specified */
-	bool		hasRowSecurity; /* rewriter has applied some RLS policy */
-
-	bool		isReturn;		/* is a RETURN statement */
+	/* has aggregates in tlist or havingQual */
+	bool		hasAggs;
+	/* has window functions in tlist */
+	bool		hasWindowFuncs;
+	/* has set-returning functions in tlist */
+	bool		hasTargetSRFs;
+	/* has subquery SubLink */
+	bool		hasSubLinks;
+	/* distinctClause is from DISTINCT ON */
+	bool		hasDistinctOn;
+	/* WITH RECURSIVE was specified */
+	bool		hasRecursive;
+	/* has INSERT/UPDATE/DELETE in WITH */
+	bool		hasModifyingCTE;
+	/* FOR [KEY] UPDATE/SHARE was specified */
+	bool		hasForUpdate;
+	/* rewriter has applied some RLS policy */
+	bool		hasRowSecurity;
+	/* is a RETURN statement */
+	bool		isReturn;
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
-	List	   *rteperminfos;	/* list of RTEPermissionInfo nodes for the
-								 * rtable entries having perminfoindex > 0 */
+
+	/*
+	 * list of RTEPermissionInfo nodes for the rtable entries having
+	 * perminfoindex > 0
+	 */
+	List	   *rteperminfos;
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
 	List	   *mergeActionList;	/* list of actions for MERGE (only) */
-	bool		mergeUseOuterJoin;	/* whether to use outer join */
+	/* whether to use outer join */
+	bool		mergeUseOuterJoin;
 
 	List	   *targetList;		/* target list (of TargetEntry) */
 
-	OverridingKind override;	/* OVERRIDING clause */
+	/* OVERRIDING clause */
+	OverridingKind override;
 
 	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
 
@@ -191,11 +211,14 @@ typedef struct Query
 	Node	   *setOperations;	/* set-operation tree if this is top level of
 								 * a UNION/INTERSECT/EXCEPT query */
 
-	List	   *constraintDeps; /* a list of pg_constraint OIDs that the query
-								 * depends on to be semantically valid */
+	/*
+	 * A list of pg_constraint OIDs that the query depends on to be
+	 * semantically valid
+	 */
+	List	   *constraintDeps;
 
-	List	   *withCheckOptions;	/* a list of WithCheckOption's (added
-									 * during rewrite) */
+	/* a list of WithCheckOption's (added during rewrite) */
+	List	   *withCheckOptions;
 
 	/*
 	 * The following two fields identify the portion of the source text string
@@ -203,8 +226,10 @@ typedef struct Query
 	 * Queries, not in sub-queries.  When not set, they might both be zero, or
 	 * both be -1 meaning "unknown".
 	 */
-	int			stmt_location;	/* start location, or -1 if unknown */
-	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
+	/* start location, or -1 if unknown */
+	int			stmt_location;
+	/* length in bytes; 0 means "rest of string" */
+	int			stmt_len;
 } Query;
 
 
@@ -239,7 +264,8 @@ typedef struct TypeName
 	List	   *typmods;		/* type modifier expression(s) */
 	int32		typemod;		/* prespecified type modifier */
 	List	   *arrayBounds;	/* array bounds */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } TypeName;
 
 /*
@@ -259,7 +285,8 @@ typedef struct ColumnRef
 {
 	NodeTag		type;
 	List	   *fields;			/* field names (String nodes) or A_Star */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } ColumnRef;
 
 /*
@@ -269,7 +296,8 @@ typedef struct ParamRef
 {
 	NodeTag		type;
 	int			number;			/* the number of the parameter */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } ParamRef;
 
 /*
@@ -302,7 +330,8 @@ typedef struct A_Expr
 	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 */
+	/* token location, or -1 if unknown */
+	int			location;
 } A_Expr;
 
 /*
@@ -328,7 +357,8 @@ typedef struct A_Const
 	NodeTag		type;
 	union ValUnion val;
 	bool		isnull;			/* SQL NULL constant */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } A_Const;
 
 /*
@@ -339,7 +369,8 @@ typedef struct TypeCast
 	NodeTag		type;
 	Node	   *arg;			/* the expression being casted */
 	TypeName   *typeName;		/* the target type */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } TypeCast;
 
 /*
@@ -350,7 +381,8 @@ typedef struct CollateClause
 	NodeTag		type;
 	Node	   *arg;			/* input expression */
 	List	   *collname;		/* possibly-qualified collation name */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } CollateClause;
 
 /*
@@ -370,7 +402,8 @@ typedef struct RoleSpec
 	NodeTag		type;
 	RoleSpecType roletype;		/* Type of this rolespec */
 	char	   *rolename;		/* filled only for ROLESPEC_CSTRING */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } RoleSpec;
 
 /*
@@ -400,7 +433,8 @@ typedef struct FuncCall
 	bool		agg_distinct;	/* arguments were labeled DISTINCT */
 	bool		func_variadic;	/* last argument was labeled VARIADIC */
 	CoercionForm funcformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } FuncCall;
 
 /*
@@ -457,7 +491,8 @@ typedef struct A_ArrayExpr
 {
 	NodeTag		type;
 	List	   *elements;		/* array element expressions */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } A_ArrayExpr;
 
 /*
@@ -484,7 +519,8 @@ typedef struct ResTarget
 	char	   *name;			/* column name or NULL */
 	List	   *indirection;	/* subscripts, field names, and '*', or NIL */
 	Node	   *val;			/* the value expression to compute or assign */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } ResTarget;
 
 /*
@@ -514,7 +550,8 @@ typedef struct SortBy
 	SortByDir	sortby_dir;		/* ASC/DESC/USING/default */
 	SortByNulls sortby_nulls;	/* NULLS FIRST/LAST */
 	List	   *useOp;			/* name of op to use, if SORTBY_USING */
-	int			location;		/* operator location, or -1 if none/unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } SortBy;
 
 /*
@@ -535,7 +572,8 @@ typedef struct WindowDef
 	int			frameOptions;	/* frame_clause options, see below */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
-	int			location;		/* parse location, or -1 if none/unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } WindowDef;
 
 /*
@@ -625,7 +663,8 @@ typedef struct RangeTableFunc
 	List	   *namespaces;		/* list of namespaces as ResTarget */
 	List	   *columns;		/* list of RangeTableFuncCol */
 	Alias	   *alias;			/* table alias & optional column aliases */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } RangeTableFunc;
 
 /*
@@ -643,7 +682,8 @@ typedef struct RangeTableFuncCol
 	bool		is_not_null;	/* does it have NOT NULL? */
 	Node	   *colexpr;		/* column filter expression */
 	Node	   *coldefexpr;		/* column default value expression */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } RangeTableFuncCol;
 
 /*
@@ -663,7 +703,8 @@ typedef struct RangeTableSample
 	List	   *method;			/* sampling method name (possibly qualified) */
 	List	   *args;			/* argument(s) for sampling method */
 	Node	   *repeatable;		/* REPEATABLE expression, or NULL if none */
-	int			location;		/* method name location, or -1 if unknown */
+	/* method name location, or -1 if unknown */
+	int			location;
 } RangeTableSample;
 
 /*
@@ -706,7 +747,8 @@ typedef struct ColumnDef
 	Oid			collOid;		/* collation OID (InvalidOid if not set) */
 	List	   *constraints;	/* other constraints on column */
 	List	   *fdwoptions;		/* per-column FDW options */
-	int			location;		/* parse location, or -1 if none/unknown */
+	/* parse location, or -1 if unknown */
+	int			location;
 } ColumnDef;
 
 /*
@@ -780,7 +822,8 @@ typedef struct DefElem
 	Node	   *arg;			/* typically Integer, Float, String, or
 								 * TypeName */
 	DefElemAction defaction;	/* unspecified action, or SET/ADD/DROP */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } DefElem;
 
 /*
@@ -809,7 +852,8 @@ typedef struct XmlSerialize
 	XmlOptionType xmloption;	/* DOCUMENT or CONTENT */
 	Node	   *expr;
 	TypeName   *typeName;
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } XmlSerialize;
 
 /* Partitioning related definitions */
@@ -827,7 +871,8 @@ typedef struct PartitionElem
 	Node	   *expr;			/* expression to partition on, or NULL */
 	List	   *collation;		/* name of collation; NIL = default */
 	List	   *opclass;		/* name of desired opclass; NIL = default */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } PartitionElem;
 
 typedef enum PartitionStrategy
@@ -847,7 +892,8 @@ typedef struct PartitionSpec
 	NodeTag		type;
 	PartitionStrategy strategy;
 	List	   *partParams;		/* List of PartitionElems */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } PartitionSpec;
 
 /*
@@ -874,7 +920,8 @@ struct PartitionBoundSpec
 	List	   *lowerdatums;	/* List of PartitionRangeDatums */
 	List	   *upperdatums;	/* List of PartitionRangeDatums */
 
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 };
 
 /*
@@ -897,7 +944,8 @@ typedef struct PartitionRangeDatum
 	Node	   *value;			/* Const (or A_Const in raw tree), if kind is
 								 * PARTITION_RANGE_DATUM_VALUE, else NULL */
 
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } PartitionRangeDatum;
 
 /*
@@ -1223,14 +1271,21 @@ typedef struct RangeTblFunction
 	NodeTag		type;
 
 	Node	   *funcexpr;		/* expression tree for func call */
-	int			funccolcount;	/* number of columns it contributes to RTE */
+	/* number of columns it contributes to RTE */
+	int			funccolcount;
 	/* These fields record the contents of a column definition list, if any: */
-	List	   *funccolnames;	/* column names (list of String) */
-	List	   *funccoltypes;	/* OID list of column type OIDs */
-	List	   *funccoltypmods; /* integer list of column typmods */
-	List	   *funccolcollations;	/* OID list of column collation OIDs */
+	/* column names (list of String) */
+	List	   *funccolnames;
+	/* OID list of column type OIDs */
+	List	   *funccoltypes;
+	/* integer list of column typmods */
+	List	   *funccoltypmods;
+	/* OID list of column collation OIDs */
+	List	   *funccolcollations;
+
 	/* This is set during planning for use by the executor: */
-	Bitmapset  *funcparams;		/* PARAM_EXEC Param IDs affecting this func */
+	/* PARAM_EXEC Param IDs affecting this func */
+	Bitmapset  *funcparams;
 } RangeTblFunction;
 
 /*
@@ -1337,7 +1392,8 @@ typedef struct SortGroupClause
 	Oid			eqop;			/* the equality operator ('=' op) */
 	Oid			sortop;			/* the ordering operator ('<' op), or 0 */
 	bool		nulls_first;	/* do NULLs come before normal values? */
-	bool		hashable;		/* can eqop be implemented by hashing? */
+	/* can eqop be implemented by hashing? */
+	bool		hashable;
 } SortGroupClause;
 
 /*
@@ -1427,21 +1483,31 @@ typedef struct GroupingSet
 typedef struct WindowClause
 {
 	NodeTag		type;
-	char	   *name;			/* window name (NULL in an OVER clause) */
-	char	   *refname;		/* referenced window name, if any */
+	/* window name (NULL in an OVER clause) */
+	char	   *name;
+	/* referenced window name, if any */
+	char	   *refname;
 	List	   *partitionClause;	/* PARTITION BY list */
-	List	   *orderClause;	/* ORDER BY list */
+	/* ORDER BY list */
+	List	   *orderClause;
 	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 */
-	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? */
+	/* qual to help short-circuit execution */
+	List	   *runCondition;
+	/* 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;
 	Index		winref;			/* ID referenced by window functions */
-	bool		copiedOrder;	/* did we copy orderClause from refname? */
+	/* did we copy orderClause from refname? */
+	bool		copiedOrder;
 } WindowClause;
 
 /*
@@ -1477,7 +1543,8 @@ typedef struct WithClause
 	NodeTag		type;
 	List	   *ctes;			/* list of CommonTableExprs */
 	bool		recursive;		/* true = WITH RECURSIVE */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } WithClause;
 
 /*
@@ -1492,7 +1559,8 @@ typedef struct InferClause
 	List	   *indexElems;		/* IndexElems to infer unique index */
 	Node	   *whereClause;	/* qualification (partial-index predicate) */
 	char	   *conname;		/* Constraint name, or NULL if unnamed */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } InferClause;
 
 /*
@@ -1508,7 +1576,8 @@ typedef struct OnConflictClause
 	InferClause *infer;			/* Optional index inference clause */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	Node	   *whereClause;	/* qualifications */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } OnConflictClause;
 
 /*
@@ -1558,15 +1627,25 @@ typedef struct CommonTableExpr
 	Node	   *ctequery;		/* the CTE's subquery */
 	CTESearchClause *search_clause;
 	CTECycleClause *cycle_clause;
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 	/* These fields are set during parse analysis: */
-	bool		cterecursive;	/* is this CTE actually recursive? */
-	int			cterefcount;	/* number of RTEs referencing this CTE
-								 * (excluding internal self-references) */
-	List	   *ctecolnames;	/* list of output column names */
-	List	   *ctecoltypes;	/* OID list of output column type OIDs */
-	List	   *ctecoltypmods;	/* integer list of output column typmods */
-	List	   *ctecolcollations;	/* OID list of column collation OIDs */
+	/* is this CTE actually recursive? */
+	bool		cterecursive;
+
+	/*
+	 * Number of RTEs referencing this CTE (excluding internal
+	 * self-references)
+	 */
+	int			cterefcount;
+	/* list of output column names */
+	List	   *ctecolnames;
+	/* OID list of output column type OIDs */
+	List	   *ctecoltypes;
+	/* integer list of output column typmods */
+	List	   *ctecoltypmods;
+	/* OID list of column collation OIDs */
+	List	   *ctecolcollations;
 } CommonTableExpr;
 
 /* Convenience macro to get the output tlist of a CTE's query */
@@ -1603,10 +1682,12 @@ typedef struct MergeAction
 	NodeTag		type;
 	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
 	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
-	OverridingKind override;	/* OVERRIDING clause */
+	/* OVERRIDING clause */
+	OverridingKind override;
 	Node	   *qual;			/* transformed WHEN conditions */
 	List	   *targetList;		/* the target list (of TargetEntry) */
-	List	   *updateColnos;	/* target attribute numbers of an UPDATE */
+	/* target attribute numbers of an UPDATE */
+	List	   *updateColnos;
 } MergeAction;
 
 /*
@@ -1645,7 +1726,8 @@ typedef struct RawStmt
 {
 	NodeTag		type;
 	Node	   *stmt;			/* raw parse tree */
-	int			stmt_location;	/* start location, or -1 if unknown */
+	/* start location, or -1 if unknown */
+	int			stmt_location;
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } RawStmt;
 
@@ -1816,10 +1898,14 @@ typedef struct SetOperationStmt
 	/* Eventually add fields for CORRESPONDING spec here */
 
 	/* Fields derived during parse analysis: */
-	List	   *colTypes;		/* OID list of output column type OIDs */
-	List	   *colTypmods;		/* integer list of output column typmods */
-	List	   *colCollations;	/* OID list of output column collation OIDs */
-	List	   *groupClauses;	/* a list of SortGroupClause's */
+	/* OID list of output column type OIDs */
+	List	   *colTypes;
+	/* integer list of output column typmods */
+	List	   *colTypmods;
+	/* OID list of output column collation OIDs */
+	List	   *colCollations;
+	/* a list of SortGroupClause's */
+	List	   *groupClauses;
 	/* groupClauses is NIL if UNION ALL, but must be set otherwise */
 } SetOperationStmt;
 
@@ -1849,7 +1935,8 @@ typedef struct PLAssignStmt
 	List	   *indirection;	/* subscripts and field names, if any */
 	int			nnames;			/* number of names to use in ColumnRef */
 	SelectStmt *val;			/* the PL/pgSQL expression to assign */
-	int			location;		/* name's token location, or -1 if unknown */
+	/* name's token location, or -1 if unknown */
+	int			location;
 } PLAssignStmt;
 
 
@@ -2356,7 +2443,8 @@ typedef struct Constraint
 	char	   *conname;		/* Constraint name, or NULL if unnamed */
 	bool		deferrable;		/* DEFERRABLE? */
 	bool		initdeferred;	/* INITIALLY DEFERRED? */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 
 	/* Fields used for constraints with expressions (CHECK and DEFAULT): */
 	bool		is_no_inherit;	/* is constraint non-inheritable? */
@@ -3765,7 +3853,8 @@ typedef struct PublicationObjSpec
 	PublicationObjSpecType pubobjtype;	/* type of this publication object */
 	char	   *name;
 	PublicationTable *pubtable;
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } PublicationObjSpec;
 
 typedef struct CreatePublicationStmt
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index c1234fcf36..f33d7fe167 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -99,7 +99,8 @@ typedef struct PlannedStmt
 	Node	   *utilityStmt;	/* non-null if this is utility stmt */
 
 	/* statement location in source string (copied from Query) */
-	int			stmt_location;	/* start location, or -1 if unknown */
+	/* start location, or -1 if unknown */
+	int			stmt_location;
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } PlannedStmt;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 83e40e56d3..8d80c90ce7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -98,19 +98,32 @@ typedef struct RangeVar
 typedef struct TableFunc
 {
 	NodeTag		type;
-	List	   *ns_uris;		/* list of namespace URI expressions */
-	List	   *ns_names;		/* list of namespace names or NULL */
-	Node	   *docexpr;		/* input document expression */
-	Node	   *rowexpr;		/* row filter expression */
-	List	   *colnames;		/* column names (list of String) */
-	List	   *coltypes;		/* OID list of column type OIDs */
-	List	   *coltypmods;		/* integer list of column typmods */
-	List	   *colcollations;	/* OID list of column collation OIDs */
-	List	   *colexprs;		/* list of column filter expressions */
-	List	   *coldefexprs;	/* list of column default expressions */
-	Bitmapset  *notnulls;		/* nullability flag for each output column */
-	int			ordinalitycol;	/* counts from 0; -1 if none specified */
-	int			location;		/* token location, or -1 if unknown */
+	/* list of namespace URI expressions */
+	List	   *ns_uris;
+	/* list of namespace names or NULL */
+	List	   *ns_names;
+	/* input document expression */
+	Node	   *docexpr;
+	/* row filter expression */
+	Node	   *rowexpr;
+	/* column names (list of String) */
+	List	   *colnames;
+	/* OID list of column type OIDs */
+	List	   *coltypes;
+	/* integer list of column typmods */
+	List	   *coltypmods;
+	/* OID list of column collation OIDs */
+	List	   *colcollations;
+	/* list of column filter expressions */
+	List	   *colexprs;
+	/* list of column default expressions */
+	List	   *coldefexprs;
+	/* nullability flag for each output column */
+	Bitmapset  *notnulls;
+	/* counts from 0; -1 if none specified */
+	int			ordinalitycol;
+	/* token location, or -1 if unknown */
+	int			location;
 } TableFunc;
 
 /*
@@ -256,18 +269,27 @@ 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 */
-	Oid			constcollid;	/* OID of collation, or InvalidOid if none */
-	int			constlen;		/* typlen of the constant's datatype */
-	Datum		constvalue;		/* the constant's value */
-	bool		constisnull;	/* whether the constant is null (if true,
-								 * constvalue is undefined) */
-	bool		constbyval;		/* whether this datatype is passed by value.
-								 * If true, then all the information is stored
-								 * in the Datum. If false, then the Datum
-								 * contains a pointer to the information. */
-	int			location;		/* token location, or -1 if unknown */
+	/* pg_type OID of the constant's datatype */
+	Oid			consttype;
+	/* typmod value, if any */
+	int32		consttypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			constcollid;
+	/* typlen of the constant's datatype */
+	int			constlen;
+	/* the constant's value */
+	Datum		constvalue;
+	/* whether the constant is null (if true, constvalue is undefined) */
+	bool		constisnull;
+
+	/*
+	 * Whether this datatype is passed by value.  If true, then all the
+	 * information is stored in the Datum.  If false, then the Datum contains
+	 * a pointer to the information.
+	 */
+	bool		constbyval;
+	/* token location, or -1 if unknown */
+	int			location;
 } Const;
 
 /*
@@ -311,9 +333,12 @@ typedef struct Param
 	ParamKind	paramkind;		/* kind of parameter. See above */
 	int			paramid;		/* numeric ID for parameter */
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
-	int32		paramtypmod;	/* typmod value, if known */
-	Oid			paramcollid;	/* OID of collation, or InvalidOid if none */
-	int			location;		/* token location, or -1 if unknown */
+	/* typmod value, if known */
+	int32		paramtypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			paramcollid;
+	/* token location, or -1 if unknown */
+	int			location;
 } Param;
 
 /*
@@ -486,16 +511,26 @@ typedef struct GroupingFunc
 typedef struct WindowFunc
 {
 	Expr		xpr;
-	Oid			winfnoid;		/* pg_proc Oid of the function */
-	Oid			wintype;		/* type Oid of result of the window function */
-	Oid			wincollid;		/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
-	List	   *args;			/* arguments to the window function */
-	Expr	   *aggfilter;		/* FILTER expression, if any */
-	Index		winref;			/* index of associated WindowClause */
-	bool		winstar;		/* true if argument list was really '*' */
-	bool		winagg;			/* is function a simple aggregate? */
-	int			location;		/* token location, or -1 if unknown */
+	/* pg_proc Oid of the function */
+	Oid			winfnoid;
+	/* type Oid of result of the window function */
+	Oid			wintype;
+	/* OID of collation of result */
+	Oid			wincollid;
+	/* OID of collation that function should use */
+	Oid			inputcollid;
+	/* arguments to the window function */
+	List	   *args;
+	/* FILTER expression, if any */
+	Expr	   *aggfilter;
+	/* index of associated WindowClause */
+	Index		winref;
+	/* true if argument list was really '*' */
+	bool		winstar;
+	/* is function a simple aggregate? */
+	bool		winagg;
+	/* token location, or -1 if unknown */
+	int			location;
 } WindowFunc;
 
 /*
@@ -539,20 +574,28 @@ typedef struct WindowFunc
 typedef struct SubscriptingRef
 {
 	Expr		xpr;
-	Oid			refcontainertype;	/* type of the container proper */
-	Oid			refelemtype;	/* the container type's pg_type.typelem */
-	Oid			refrestype;		/* type of the SubscriptingRef's result */
-	int32		reftypmod;		/* typmod of the result */
-	Oid			refcollid;		/* collation of result, or InvalidOid if none */
-	List	   *refupperindexpr;	/* expressions that evaluate to upper
-									 * container indexes */
-	List	   *reflowerindexpr;	/* expressions that evaluate to lower
-									 * container indexes, or NIL for single
-									 * container element */
-	Expr	   *refexpr;		/* the expression that evaluates to a
-								 * container value */
-	Expr	   *refassgnexpr;	/* expression for the source value, or NULL if
-								 * fetch */
+	/* type of the container proper */
+	Oid			refcontainertype;
+	/* the container type's pg_type.typelem */
+	Oid			refelemtype;
+	/* type of the SubscriptingRef's result */
+	Oid			refrestype;
+	/* typmod of the result */
+	int32		reftypmod;
+	/* collation of result, or InvalidOid if none */
+	Oid			refcollid;
+	/* expressions that evaluate to upper container indexes */
+	List	   *refupperindexpr;
+
+	/*
+	 * expressions that evaluate to lower container indexes, or NIL for single
+	 * container element.
+	 */
+	List	   *reflowerindexpr;
+	/* the expression that evaluates to a container value */
+	Expr	   *refexpr;
+	/* expression for the source value, or NULL if fetch */
+	Expr	   *refassgnexpr;
 } SubscriptingRef;
 
 /*
@@ -595,16 +638,28 @@ typedef enum CoercionForm
 typedef struct FuncExpr
 {
 	Expr		xpr;
-	Oid			funcid;			/* PG_PROC OID of the function */
-	Oid			funcresulttype; /* PG_TYPE OID of result value */
-	bool		funcretset;		/* true if function returns set */
-	bool		funcvariadic;	/* true if variadic arguments have been
-								 * combined into an array last argument */
-	CoercionForm funcformat;	/* how to display this function call */
-	Oid			funccollid;		/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
-	List	   *args;			/* arguments to the function */
-	int			location;		/* token location, or -1 if unknown */
+	/* PG_PROC OID of the function */
+	Oid			funcid;
+	/* PG_TYPE OID of result value */
+	Oid			funcresulttype;
+	/* true if function returns set */
+	bool		funcretset;
+
+	/*
+	 * true if variadic arguments have been combined into an array last
+	 * argument
+	 */
+	bool		funcvariadic;
+	/* how to display this function call */
+	CoercionForm funcformat;
+	/* OID of collation of result */
+	Oid			funccollid;
+	/* OID of collation that function should use */
+	Oid			inputcollid;
+	/* arguments to the function */
+	List	   *args;
+	/* token location, or -1 if unknown */
+	int			location;
 } FuncExpr;
 
 /*
@@ -624,10 +679,14 @@ typedef struct FuncExpr
 typedef struct NamedArgExpr
 {
 	Expr		xpr;
-	Expr	   *arg;			/* the argument expression */
-	char	   *name;			/* the name */
-	int			argnumber;		/* argument's number in positional notation */
-	int			location;		/* argument name location, or -1 if unknown */
+	/* the argument expression */
+	Expr	   *arg;
+	/* the name */
+	char	   *name;
+	/* argument's number in positional notation */
+	int			argnumber;
+	/* argument name location, or -1 if unknown */
+	int			location;
 } NamedArgExpr;
 
 /*
@@ -765,7 +824,8 @@ typedef struct BoolExpr
 	Expr		xpr;
 	BoolExprType boolop;
 	List	   *args;			/* arguments to this expression */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } BoolExpr;
 
 /*
@@ -838,9 +898,12 @@ typedef struct SubLink
 	SubLinkType subLinkType;	/* see above */
 	int			subLinkId;		/* ID (1..n); 0 if not MULTIEXPR */
 	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
-	List	   *operName;		/* originally specified operator name */
-	Node	   *subselect;		/* subselect as Query* or raw parsetree */
-	int			location;		/* token location, or -1 if unknown */
+	/* originally specified operator name */
+	List	   *operName;
+	/* subselect as Query* or raw parsetree */
+	Node	   *subselect;
+	/* token location, or -1 if unknown */
+	int			location;
 } SubLink;
 
 /*
@@ -948,10 +1011,12 @@ typedef struct FieldSelect
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	AttrNumber	fieldnum;		/* attribute number of field to extract */
-	Oid			resulttype;		/* type of the field (result type of this
-								 * node) */
-	int32		resulttypmod;	/* output typmod (usually -1) */
-	Oid			resultcollid;	/* OID of collation of the field */
+	/* type of the field (result type of this node) */
+	Oid			resulttype;
+	/* output typmod (usually -1) */
+	int32		resulttypmod;
+	/* OID of collation of the field */
+	Oid			resultcollid;
 } FieldSelect;
 
 /* ----------------
@@ -977,8 +1042,10 @@ typedef struct FieldStore
 	Expr		xpr;
 	Expr	   *arg;			/* input tuple value */
 	List	   *newvals;		/* new value(s) for field(s) */
-	List	   *fieldnums;		/* integer list of field attnums */
-	Oid			resulttype;		/* type of result (same as type of arg) */
+	/* integer list of field attnums */
+	List	   *fieldnums;
+	/* type of result (same as type of arg) */
+	Oid			resulttype;
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 } FieldStore;
 
@@ -1000,10 +1067,14 @@ typedef struct RelabelType
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion expression */
-	int32		resulttypmod;	/* output typmod (usually -1) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm relabelformat; /* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* output typmod (usually -1) */
+	int32		resulttypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			resultcollid;
+	/* how to display this node */
+	CoercionForm relabelformat;
+	/* token location, or -1 if unknown */
+	int			location;
 } RelabelType;
 
 /* ----------------
@@ -1021,9 +1092,12 @@ typedef struct CoerceViaIO
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion */
 	/* output typmod is not stored, but is presumed -1 */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coerceformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* OID of collation, or InvalidOid if none */
+	Oid			resultcollid;
+	/* how to display this node */
+	CoercionForm coerceformat;
+	/* token location, or -1 if unknown */
+	int			location;
 } CoerceViaIO;
 
 /* ----------------
@@ -1045,10 +1119,14 @@ typedef struct ArrayCoerceExpr
 	Expr	   *arg;			/* input expression (yields an array) */
 	Expr	   *elemexpr;		/* expression representing per-element work */
 	Oid			resulttype;		/* output type of coercion (an array type) */
-	int32		resulttypmod;	/* output typmod (also element typmod) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coerceformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* output typmod (also element typmod) */
+	int32		resulttypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			resultcollid;
+	/* how to display this node */
+	CoercionForm coerceformat;
+	/* token location, or -1 if unknown */
+	int			location;
 } ArrayCoerceExpr;
 
 /* ----------------
@@ -1070,8 +1148,10 @@ typedef struct ConvertRowtypeExpr
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type (always a composite type) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
-	CoercionForm convertformat; /* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* how to display this node */
+	CoercionForm convertformat;
+	/* token location, or -1 if unknown */
+	int			location;
 } ConvertRowtypeExpr;
 
 /*----------
@@ -1086,7 +1166,8 @@ typedef struct CollateExpr
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			collOid;		/* collation's OID */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } CollateExpr;
 
 /*----------
@@ -1114,12 +1195,15 @@ typedef struct CollateExpr
 typedef struct CaseExpr
 {
 	Expr		xpr;
-	Oid			casetype;		/* type of expression result */
-	Oid			casecollid;		/* OID of collation, or InvalidOid if none */
+	/* type of expression result */
+	Oid			casetype;
+	/* OID of collation, or InvalidOid if none */
+	Oid			casecollid;
 	Expr	   *arg;			/* implicit equality comparison argument */
 	List	   *args;			/* the arguments (list of WHEN clauses) */
 	Expr	   *defresult;		/* the default result (ELSE clause) */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } CaseExpr;
 
 /*
@@ -1130,7 +1214,8 @@ typedef struct CaseWhen
 	Expr		xpr;
 	Expr	   *expr;			/* condition expression */
 	Expr	   *result;			/* substitution result */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } CaseWhen;
 
 /*
@@ -1157,8 +1242,10 @@ typedef struct CaseTestExpr
 {
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
+	/* typemod for substituted value */
+	int32		typeMod;
+	/* collation for the substituted value */
+	Oid			collation;
 } CaseTestExpr;
 
 /*
@@ -1172,12 +1259,18 @@ typedef struct CaseTestExpr
 typedef struct ArrayExpr
 {
 	Expr		xpr;
-	Oid			array_typeid;	/* type of expression result */
-	Oid			array_collid;	/* OID of collation, or InvalidOid if none */
-	Oid			element_typeid; /* common type of array elements */
-	List	   *elements;		/* the array elements or sub-arrays */
-	bool		multidims;		/* true if elements are sub-arrays */
-	int			location;		/* token location, or -1 if unknown */
+	/* type of expression result */
+	Oid			array_typeid;
+	/* OID of collation, or InvalidOid if none */
+	Oid			array_collid;
+	/* common type of array elements */
+	Oid			element_typeid;
+	/* the array elements or sub-arrays */
+	List	   *elements;
+	/* true if elements are sub-arrays */
+	bool		multidims;
+	/* token location, or -1 if unknown */
+	int			location;
 } ArrayExpr;
 
 /*
@@ -1205,7 +1298,9 @@ typedef struct RowExpr
 {
 	Expr		xpr;
 	List	   *args;			/* the fields */
-	Oid			row_typeid;		/* RECORDOID or a composite type's ID */
+
+	/* RECORDOID or a composite type's ID */
+	Oid			row_typeid;
 
 	/*
 	 * row_typeid cannot be a domain over composite, only plain composite.  To
@@ -1219,9 +1314,15 @@ typedef struct RowExpr
 	 * We don't need to store a collation either.  The result type is
 	 * necessarily composite, and composite types never have a collation.
 	 */
-	CoercionForm row_format;	/* how to display this node */
-	List	   *colnames;		/* list of String, or NIL */
-	int			location;		/* token location, or -1 if unknown */
+
+	/* how to display this node */
+	CoercionForm row_format;
+
+	/* list of String, or NIL */
+	List	   *colnames;
+
+	/* token location, or -1 if unknown */
+	int			location;
 } RowExpr;
 
 /*
@@ -1252,12 +1353,19 @@ typedef enum RowCompareType
 typedef struct RowCompareExpr
 {
 	Expr		xpr;
-	RowCompareType rctype;		/* LT LE GE or GT, never EQ or NE */
-	List	   *opnos;			/* OID list of pairwise comparison ops */
-	List	   *opfamilies;		/* OID list of containing operator families */
-	List	   *inputcollids;	/* OID list of collations for comparisons */
-	List	   *largs;			/* the left-hand input arguments */
-	List	   *rargs;			/* the right-hand input arguments */
+
+	/* LT LE GE or GT, never EQ or NE */
+	RowCompareType rctype;
+	/* OID list of pairwise comparison ops */
+	List	   *opnos;
+	/* OID list of containing operator families */
+	List	   *opfamilies;
+	/* OID list of collations for comparisons */
+	List	   *inputcollids;
+	/* the left-hand input arguments */
+	List	   *largs;
+	/* the right-hand input arguments */
+	List	   *rargs;
 } RowCompareExpr;
 
 /*
@@ -1266,10 +1374,14 @@ typedef struct RowCompareExpr
 typedef struct CoalesceExpr
 {
 	Expr		xpr;
-	Oid			coalescetype;	/* type of expression result */
-	Oid			coalescecollid; /* OID of collation, or InvalidOid if none */
-	List	   *args;			/* the arguments */
-	int			location;		/* token location, or -1 if unknown */
+	/* type of expression result */
+	Oid			coalescetype;
+	/* OID of collation, or InvalidOid if none */
+	Oid			coalescecollid;
+	/* the arguments */
+	List	   *args;
+	/* token location, or -1 if unknown */
+	int			location;
 } CoalesceExpr;
 
 /*
@@ -1284,12 +1396,18 @@ typedef enum MinMaxOp
 typedef struct MinMaxExpr
 {
 	Expr		xpr;
-	Oid			minmaxtype;		/* common type of arguments and result */
-	Oid			minmaxcollid;	/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
-	MinMaxOp	op;				/* function to execute */
-	List	   *args;			/* the arguments */
-	int			location;		/* token location, or -1 if unknown */
+	/* common type of arguments and result */
+	Oid			minmaxtype;
+	/* OID of collation of result */
+	Oid			minmaxcollid;
+	/* OID of collation that function should use */
+	Oid			inputcollid;
+	/* function to execute */
+	MinMaxOp	op;
+	/* the arguments */
+	List	   *args;
+	/* token location, or -1 if unknown */
+	int			location;
 } MinMaxExpr;
 
 /*
@@ -1324,15 +1442,23 @@ typedef enum XmlOptionType
 typedef struct XmlExpr
 {
 	Expr		xpr;
-	XmlExprOp	op;				/* xml function ID */
-	char	   *name;			/* name in xml(NAME foo ...) syntaxes */
-	List	   *named_args;		/* non-XML expressions for xml_attributes */
-	List	   *arg_names;		/* parallel list of String values */
-	List	   *args;			/* list of expressions */
-	XmlOptionType xmloption;	/* DOCUMENT or CONTENT */
-	Oid			type;			/* target type/typmod for XMLSERIALIZE */
+	/* xml function ID */
+	XmlExprOp	op;
+	/* name in xml(NAME foo ...) syntaxes */
+	char	   *name;
+	/* non-XML expressions for xml_attributes */
+	List	   *named_args;
+	/* parallel list of String values */
+	List	   *arg_names;
+	/* list of expressions */
+	List	   *args;
+	/* DOCUMENT or CONTENT */
+	XmlOptionType xmloption;
+	/* target type/typmod for XMLSERIALIZE */
+	Oid			type;
 	int32		typmod;
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } XmlExpr;
 
 /* ----------------
@@ -1364,8 +1490,10 @@ typedef struct NullTest
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	NullTestType nulltesttype;	/* IS NULL, IS NOT NULL */
-	bool		argisrow;		/* T to perform field-by-field null checks */
-	int			location;		/* token location, or -1 if unknown */
+	/* T to perform field-by-field null checks */
+	bool		argisrow;
+	/* token location, or -1 if unknown */
+	int			location;
 } NullTest;
 
 /*
@@ -1387,7 +1515,8 @@ typedef struct BooleanTest
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	BoolTestType booltesttype;	/* test type */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } BooleanTest;
 
 /*
@@ -1404,10 +1533,14 @@ typedef struct CoerceToDomain
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* domain type ID (result type) */
-	int32		resulttypmod;	/* output typmod (currently always -1) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coercionformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* output typmod (currently always -1) */
+	int32		resulttypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			resultcollid;
+	/* how to display this node */
+	CoercionForm coercionformat;
+	/* token location, or -1 if unknown */
+	int			location;
 } CoerceToDomain;
 
 /*
@@ -1422,10 +1555,14 @@ typedef struct CoerceToDomain
 typedef struct CoerceToDomainValue
 {
 	Expr		xpr;
-	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
-	int			location;		/* token location, or -1 if unknown */
+	/* type for substituted value */
+	Oid			typeId;
+	/* typemod for substituted value */
+	int32		typeMod;
+	/* collation for the substituted value */
+	Oid			collation;
+	/* token location, or -1 if unknown */
+	int			location;
 } CoerceToDomainValue;
 
 /*
@@ -1438,10 +1575,14 @@ typedef struct CoerceToDomainValue
 typedef struct SetToDefault
 {
 	Expr		xpr;
-	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
-	int			location;		/* token location, or -1 if unknown */
+	/* type for substituted value */
+	Oid			typeId;
+	/* typemod for substituted value */
+	int32		typeMod;
+	/* collation for the substituted value */
+	Oid			collation;
+	/* token location, or -1 if unknown */
+	int			location;
 } SetToDefault;
 
 /*
@@ -1552,15 +1693,20 @@ typedef struct InferenceElem
 typedef struct TargetEntry
 {
 	Expr		xpr;
-	Expr	   *expr;			/* expression to evaluate */
-	AttrNumber	resno;			/* attribute number (see notes above) */
-	char	   *resname;		/* name of the column (could be NULL) */
-	Index		ressortgroupref;	/* nonzero if referenced by a sort/group
-									 * clause */
-	Oid			resorigtbl;		/* OID of column's source table */
-	AttrNumber	resorigcol;		/* column's number in source table */
-	bool		resjunk;		/* set to true to eliminate the attribute from
-								 * final target list */
+	/* expression to evaluate */
+	Expr	   *expr;
+	/* attribute number (see notes above) */
+	AttrNumber	resno;
+	/* name of the column (could be NULL) */
+	char	   *resname;
+	/* nonzero if referenced by a sort/group clause */
+	Index		ressortgroupref;
+	/* OID of column's source table */
+	Oid			resorigtbl;
+	/* column's number in source table */
+	AttrNumber	resorigcol;
+	/* set to true to eliminate the attribute from final target list */
+	bool		resjunk;
 } TargetEntry;
 
 
@@ -1642,11 +1788,16 @@ typedef struct JoinExpr
 	bool		isNatural;		/* Natural join? Will need to shape table */
 	Node	   *larg;			/* left subtree */
 	Node	   *rarg;			/* right subtree */
-	List	   *usingClause;	/* USING clause, if any (list of String) */
-	Alias	   *join_using_alias;	/* alias attached to USING clause, if any */
-	Node	   *quals;			/* qualifiers on join, if any */
-	Alias	   *alias;			/* user-written alias clause, if any */
-	int			rtindex;		/* RT index assigned for join, or 0 */
+	/* USING clause, if any (list of String) */
+	List	   *usingClause;
+	/* alias attached to USING clause, if any */
+	Alias	   *join_using_alias;
+	/* qualifiers on join, if any */
+	Node	   *quals;
+	/* user-written alias clause, if any */
+	Alias	   *alias;
+	/* RT index assigned for join, or 0 */
+	int			rtindex;
 } JoinExpr;
 
 /*----------
-- 
2.39.0

v3-0002-Move-query-jumble-code-to-src-backend-nodes.patchtext/x-diff; charset=us-asciiDownload
From cd672f8feb4c533c2ef1bbfb31088efc64ce7160 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 18 Jan 2023 14:37:07 +0900
Subject: [PATCH v3 2/3] Move query jumble code to src/backend/nodes/

This will ease a follow-up move that will generate automatically this
code:
- queryjumble.c -> queryjumblefuncs.c
- utils/queryjumble.h -> nodes/queryjumble.h
---
 src/include/{utils => nodes}/queryjumble.h                  | 2 +-
 src/include/parser/analyze.h                                | 2 +-
 src/backend/nodes/Makefile                                  | 1 +
 src/backend/nodes/meson.build                               | 1 +
 .../{utils/misc/queryjumble.c => nodes/queryjumblefuncs.c}  | 6 +++---
 src/backend/parser/analyze.c                                | 2 +-
 src/backend/postmaster/postmaster.c                         | 2 +-
 src/backend/utils/misc/Makefile                             | 1 -
 src/backend/utils/misc/guc_tables.c                         | 2 +-
 src/backend/utils/misc/meson.build                          | 1 -
 contrib/pg_stat_statements/pg_stat_statements.c             | 2 +-
 11 files changed, 11 insertions(+), 11 deletions(-)
 rename src/include/{utils => nodes}/queryjumble.h (98%)
 rename src/backend/{utils/misc/queryjumble.c => nodes/queryjumblefuncs.c} (99%)

diff --git a/src/include/utils/queryjumble.h b/src/include/nodes/queryjumble.h
similarity index 98%
rename from src/include/utils/queryjumble.h
rename to src/include/nodes/queryjumble.h
index d372801410..204b8f74fd 100644
--- a/src/include/utils/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/include/utils/queryjumble.h
+ *	  src/include/nodes/queryjumble.h
  *
  *-------------------------------------------------------------------------
  */
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index c97be6efcf..1cef1833a6 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -15,8 +15,8 @@
 #define ANALYZE_H
 
 #include "nodes/params.h"
+#include "nodes/queryjumble.h"
 #include "parser/parse_node.h"
-#include "utils/queryjumble.h"
 
 /* Hook for plugins to get control at end of parse analysis */
 typedef void (*post_parse_analyze_hook_type) (ParseState *pstate,
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 7c594be583..af12c64878 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -27,6 +27,7 @@ OBJS = \
 	outfuncs.o \
 	params.o \
 	print.o \
+	queryjumblefuncs.o \
 	read.o \
 	readfuncs.o \
 	tidbitmap.o \
diff --git a/src/backend/nodes/meson.build b/src/backend/nodes/meson.build
index 2ff7dbac1d..9230515e7f 100644
--- a/src/backend/nodes/meson.build
+++ b/src/backend/nodes/meson.build
@@ -10,6 +10,7 @@ backend_sources += files(
   'nodes.c',
   'params.c',
   'print.c',
+  'queryjumblefuncs.c',
   'read.c',
   'tidbitmap.c',
   'value.c',
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/nodes/queryjumblefuncs.c
similarity index 99%
rename from src/backend/utils/misc/queryjumble.c
rename to src/backend/nodes/queryjumblefuncs.c
index 328995a7dc..16084842a3 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -1,6 +1,6 @@
 /*-------------------------------------------------------------------------
  *
- * queryjumble.c
+ * queryjumblefuncs.c
  *	 Query normalization and fingerprinting.
  *
  * Normalization is a process whereby similar queries, typically differing only
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  src/backend/utils/misc/queryjumble.c
+ *	  src/backend/nodes/queryjumblefuncs.c
  *
  *-------------------------------------------------------------------------
  */
@@ -34,8 +34,8 @@
 
 #include "common/hashfn.h"
 #include "miscadmin.h"
+#include "nodes/queryjumble.h"
 #include "parser/scansup.h"
-#include "utils/queryjumble.h"
 
 #define JUMBLE_SIZE				1024	/* query serialization buffer size */
 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 5b90974e83..4a817b75ad 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/queryjumble.h"
 #include "optimizer/optimizer.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
@@ -50,7 +51,6 @@
 #include "utils/backend_status.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
-#include "utils/queryjumble.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9cedc1b9f0..f05a26d255 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -102,6 +102,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
+#include "nodes/queryjumble.h"
 #include "pg_getopt.h"
 #include "pgstat.h"
 #include "port/pg_bswap.h"
@@ -126,7 +127,6 @@
 #include "utils/memutils.h"
 #include "utils/pidfile.h"
 #include "utils/ps_status.h"
-#include "utils/queryjumble.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 #include "utils/varlena.h"
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index b9ee4eb48a..2910032930 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -26,7 +26,6 @@ OBJS = \
 	pg_rusage.o \
 	ps_status.o \
 	queryenvironment.o \
-	queryjumble.o \
 	rls.o \
 	sampling.o \
 	superuser.o \
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5025e80f89..f9bfbbbd95 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -43,6 +43,7 @@
 #include "jit/jit.h"
 #include "libpq/auth.h"
 #include "libpq/libpq.h"
+#include "nodes/queryjumble.h"
 #include "optimizer/cost.h"
 #include "optimizer/geqo.h"
 #include "optimizer/optimizer.h"
@@ -77,7 +78,6 @@
 #include "utils/pg_locale.h"
 #include "utils/portal.h"
 #include "utils/ps_status.h"
-#include "utils/queryjumble.h"
 #include "utils/inval.h"
 #include "utils/xml.h"
 
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index e3e99ec5cb..f719c97c05 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -11,7 +11,6 @@ backend_sources += files(
   'pg_rusage.c',
   'ps_status.c',
   'queryenvironment.c',
-  'queryjumble.c',
   'rls.c',
   'sampling.c',
   'superuser.c',
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index a7a72783e5..ad1fe44496 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -55,6 +55,7 @@
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/queryjumble.h"
 #include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parsetree.h"
@@ -69,7 +70,6 @@
 #include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
-#include "utils/queryjumble.h"
 #include "utils/memutils.h"
 #include "utils/timestamp.h"
 
-- 
2.39.0

v3-0003-Support-for-automated-query-jumble-with-all-Nodes.patchtext/x-diff; charset=us-asciiDownload
From 95ca73dbb1a316778f93eb52968b5a91bee55443 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 18 Jan 2023 15:21:36 +0900
Subject: [PATCH v3 3/3] Support for automated query jumble with all Nodes

This applies query jumbling in a consistent way to all the Nodes,
including DDLs & friends.
---
 src/include/nodes/bitmapset.h         |   2 +-
 src/include/nodes/nodes.h             |   4 +
 src/include/nodes/parsenodes.h        | 196 +++---
 src/include/nodes/plannodes.h         |   2 +-
 src/include/nodes/primnodes.h         | 327 +++++-----
 src/backend/nodes/README              |   1 +
 src/backend/nodes/gen_node_support.pl |  95 ++-
 src/backend/nodes/meson.build         |   2 +-
 src/backend/nodes/queryjumblefuncs.c  | 855 ++++++--------------------
 9 files changed, 561 insertions(+), 923 deletions(-)

diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 0dca6bc5fa..3d2225e1ae 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -50,7 +50,7 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
-	pg_node_attr(custom_copy_equal, special_read_write)
+	pg_node_attr(custom_copy_equal, special_read_write, no_query_jumble)
 
 	NodeTag		type;
 	int			nwords;			/* number of words in array */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 10752e8011..bcd0931add 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -59,6 +59,8 @@ typedef enum NodeTag
  *
  * - no_copy_equal: Shorthand for both no_copy and no_equal.
  *
+ * - no_query_jumble: Does not support jumble() at all.
+ *
  * - no_read: Does not support nodeRead() at all.
  *
  * - nodetag_only: Does not support copyObject(), equal(), outNode(),
@@ -97,6 +99,8 @@ typedef enum NodeTag
  * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
  *   (Otherwise, compare normally.)
  *
+ * - query_jumble_ignore: Ignore the field for the query jumbling.
+ *
  * - 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
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index eec51e3ee2..a4b06db293 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -116,6 +116,11 @@ typedef uint64 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.
+ *
+ *	  All the fields ignored for the query jumbling are not semantically
+ *	  significant (such as alias names), as is ignored anything that can
+ *	  be deduced from child nodes (else we'd just be double-hashing that
+ *	  piece of information).
  */
 typedef struct Query
 {
@@ -124,45 +129,47 @@ typedef struct Query
 	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
 
 	/* where did I come from? */
-	QuerySource querySource;
+	QuerySource querySource pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * query identifier (can be set by plugins); ignored for equal, as it
-	 * might not be set; also not stored
+	 * might not be set; also not stored.  This is the result of the query
+	 * jumble, hence ignored.
 	 */
-	uint64		queryId pg_node_attr(equal_ignore, read_write_ignore, read_as(0));
+	uint64		queryId pg_node_attr(equal_ignore, query_jumble_ignore, read_write_ignore, read_as(0));
 
 	/* do I set the command result tag? */
-	bool		canSetTag;
+	bool		canSetTag pg_node_attr(query_jumble_ignore);
 
 	Node	   *utilityStmt;	/* non-null if commandType == CMD_UTILITY */
 
 	/*
 	 * rtable index of target relation for INSERT/UPDATE/DELETE/MERGE; 0 for
-	 * SELECT.
+	 * SELECT.  This is ignored in the query jumble as unrelated to the
+	 * compilation of the query ID.
 	 */
-	int			resultRelation;
+	int			resultRelation pg_node_attr(query_jumble_ignore);
 
 	/* has aggregates in tlist or havingQual */
-	bool		hasAggs;
+	bool		hasAggs pg_node_attr(query_jumble_ignore);
 	/* has window functions in tlist */
-	bool		hasWindowFuncs;
+	bool		hasWindowFuncs pg_node_attr(query_jumble_ignore);
 	/* has set-returning functions in tlist */
-	bool		hasTargetSRFs;
+	bool		hasTargetSRFs pg_node_attr(query_jumble_ignore);
 	/* has subquery SubLink */
-	bool		hasSubLinks;
+	bool		hasSubLinks pg_node_attr(query_jumble_ignore);
 	/* distinctClause is from DISTINCT ON */
-	bool		hasDistinctOn;
+	bool		hasDistinctOn pg_node_attr(query_jumble_ignore);
 	/* WITH RECURSIVE was specified */
-	bool		hasRecursive;
+	bool		hasRecursive pg_node_attr(query_jumble_ignore);
 	/* has INSERT/UPDATE/DELETE in WITH */
-	bool		hasModifyingCTE;
+	bool		hasModifyingCTE pg_node_attr(query_jumble_ignore);
 	/* FOR [KEY] UPDATE/SHARE was specified */
-	bool		hasForUpdate;
+	bool		hasForUpdate pg_node_attr(query_jumble_ignore);
 	/* rewriter has applied some RLS policy */
-	bool		hasRowSecurity;
+	bool		hasRowSecurity pg_node_attr(query_jumble_ignore);
 	/* is a RETURN statement */
-	bool		isReturn;
+	bool		isReturn pg_node_attr(query_jumble_ignore);
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
@@ -172,18 +179,18 @@ typedef struct Query
 	 * list of RTEPermissionInfo nodes for the rtable entries having
 	 * perminfoindex > 0
 	 */
-	List	   *rteperminfos;
+	List	   *rteperminfos pg_node_attr(query_jumble_ignore);
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
 	List	   *mergeActionList;	/* list of actions for MERGE (only) */
 	/* whether to use outer join */
-	bool		mergeUseOuterJoin;
+	bool		mergeUseOuterJoin pg_node_attr(query_jumble_ignore);
 
 	List	   *targetList;		/* target list (of TargetEntry) */
 
 	/* OVERRIDING clause */
-	OverridingKind override;
+	OverridingKind override pg_node_attr(query_jumble_ignore);
 
 	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
 
@@ -215,10 +222,10 @@ typedef struct Query
 	 * A list of pg_constraint OIDs that the query depends on to be
 	 * semantically valid
 	 */
-	List	   *constraintDeps;
+	List	   *constraintDeps pg_node_attr(query_jumble_ignore);
 
 	/* a list of WithCheckOption's (added during rewrite) */
-	List	   *withCheckOptions;
+	List	   *withCheckOptions pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * The following two fields identify the portion of the source text string
@@ -227,9 +234,9 @@ typedef struct Query
 	 * both be -1 meaning "unknown".
 	 */
 	/* start location, or -1 if unknown */
-	int			stmt_location;
+	int			stmt_location pg_node_attr(query_jumble_ignore);
 	/* length in bytes; 0 means "rest of string" */
-	int			stmt_len;
+	int			stmt_len pg_node_attr(query_jumble_ignore);
 } Query;
 
 
@@ -265,7 +272,7 @@ typedef struct TypeName
 	int32		typemod;		/* prespecified type modifier */
 	List	   *arrayBounds;	/* array bounds */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } TypeName;
 
 /*
@@ -286,7 +293,7 @@ typedef struct ColumnRef
 	NodeTag		type;
 	List	   *fields;			/* field names (String nodes) or A_Star */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } ColumnRef;
 
 /*
@@ -297,7 +304,7 @@ typedef struct ParamRef
 	NodeTag		type;
 	int			number;			/* the number of the parameter */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } ParamRef;
 
 /*
@@ -331,7 +338,7 @@ typedef struct A_Expr
 	Node	   *lexpr;			/* left argument, or NULL if none */
 	Node	   *rexpr;			/* right argument, or NULL if none */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } A_Expr;
 
 /*
@@ -358,7 +365,7 @@ typedef struct A_Const
 	union ValUnion val;
 	bool		isnull;			/* SQL NULL constant */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } A_Const;
 
 /*
@@ -370,7 +377,7 @@ typedef struct TypeCast
 	Node	   *arg;			/* the expression being casted */
 	TypeName   *typeName;		/* the target type */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } TypeCast;
 
 /*
@@ -382,7 +389,7 @@ typedef struct CollateClause
 	Node	   *arg;			/* input expression */
 	List	   *collname;		/* possibly-qualified collation name */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } CollateClause;
 
 /*
@@ -403,7 +410,7 @@ typedef struct RoleSpec
 	RoleSpecType roletype;		/* Type of this rolespec */
 	char	   *rolename;		/* filled only for ROLESPEC_CSTRING */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } RoleSpec;
 
 /*
@@ -434,7 +441,7 @@ typedef struct FuncCall
 	bool		func_variadic;	/* last argument was labeled VARIADIC */
 	CoercionForm funcformat;	/* how to display this node */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } FuncCall;
 
 /*
@@ -492,7 +499,7 @@ typedef struct A_ArrayExpr
 	NodeTag		type;
 	List	   *elements;		/* array element expressions */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } A_ArrayExpr;
 
 /*
@@ -520,7 +527,7 @@ typedef struct ResTarget
 	List	   *indirection;	/* subscripts, field names, and '*', or NIL */
 	Node	   *val;			/* the value expression to compute or assign */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } ResTarget;
 
 /*
@@ -551,7 +558,7 @@ typedef struct SortBy
 	SortByNulls sortby_nulls;	/* NULLS FIRST/LAST */
 	List	   *useOp;			/* name of op to use, if SORTBY_USING */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } SortBy;
 
 /*
@@ -573,7 +580,7 @@ typedef struct WindowDef
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } WindowDef;
 
 /*
@@ -664,7 +671,7 @@ typedef struct RangeTableFunc
 	List	   *columns;		/* list of RangeTableFuncCol */
 	Alias	   *alias;			/* table alias & optional column aliases */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } RangeTableFunc;
 
 /*
@@ -683,7 +690,7 @@ typedef struct RangeTableFuncCol
 	Node	   *colexpr;		/* column filter expression */
 	Node	   *coldefexpr;		/* column default value expression */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } RangeTableFuncCol;
 
 /*
@@ -704,7 +711,7 @@ typedef struct RangeTableSample
 	List	   *args;			/* argument(s) for sampling method */
 	Node	   *repeatable;		/* REPEATABLE expression, or NULL if none */
 	/* method name location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } RangeTableSample;
 
 /*
@@ -748,7 +755,7 @@ typedef struct ColumnDef
 	List	   *constraints;	/* other constraints on column */
 	List	   *fdwoptions;		/* per-column FDW options */
 	/* parse location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } ColumnDef;
 
 /*
@@ -823,7 +830,7 @@ typedef struct DefElem
 								 * TypeName */
 	DefElemAction defaction;	/* unspecified action, or SET/ADD/DROP */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } DefElem;
 
 /*
@@ -853,7 +860,7 @@ typedef struct XmlSerialize
 	Node	   *expr;
 	TypeName   *typeName;
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } XmlSerialize;
 
 /* Partitioning related definitions */
@@ -872,7 +879,7 @@ typedef struct PartitionElem
 	List	   *collation;		/* name of collation; NIL = default */
 	List	   *opclass;		/* name of desired opclass; NIL = default */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } PartitionElem;
 
 typedef enum PartitionStrategy
@@ -893,7 +900,7 @@ typedef struct PartitionSpec
 	PartitionStrategy strategy;
 	List	   *partParams;		/* List of PartitionElems */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } PartitionSpec;
 
 /*
@@ -921,7 +928,7 @@ struct PartitionBoundSpec
 	List	   *upperdatums;	/* List of PartitionRangeDatums */
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 };
 
 /*
@@ -945,7 +952,7 @@ typedef struct PartitionRangeDatum
 								 * PARTITION_RANGE_DATUM_VALUE, else NULL */
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } PartitionRangeDatum;
 
 /*
@@ -1042,7 +1049,7 @@ typedef enum RTEKind
 
 typedef struct RangeTblEntry
 {
-	pg_node_attr(custom_read_write)
+	pg_node_attr(custom_read_write, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1265,6 +1272,8 @@ typedef struct RTEPermissionInfo
  * time.  We do however remember how many columns we thought the type had
  * (including dropped columns!), so that we can successfully ignore any
  * columns added after the query was parsed.
+ *
+ * The query jumbling needs only to track the function expression.
  */
 typedef struct RangeTblFunction
 {
@@ -1272,20 +1281,20 @@ typedef struct RangeTblFunction
 
 	Node	   *funcexpr;		/* expression tree for func call */
 	/* number of columns it contributes to RTE */
-	int			funccolcount;
+	int			funccolcount pg_node_attr(query_jumble_ignore);
 	/* These fields record the contents of a column definition list, if any: */
 	/* column names (list of String) */
-	List	   *funccolnames;
+	List	   *funccolnames pg_node_attr(query_jumble_ignore);
 	/* OID list of column type OIDs */
-	List	   *funccoltypes;
+	List	   *funccoltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of column typmods */
-	List	   *funccoltypmods;
+	List	   *funccoltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *funccolcollations;
+	List	   *funccolcollations pg_node_attr(query_jumble_ignore);
 
 	/* This is set during planning for use by the executor: */
 	/* PARAM_EXEC Param IDs affecting this func */
-	Bitmapset  *funcparams;
+	Bitmapset  *funcparams pg_node_attr(query_jumble_ignore);
 } RangeTblFunction;
 
 /*
@@ -1393,7 +1402,7 @@ typedef struct SortGroupClause
 	Oid			sortop;			/* the ordering operator ('<' op), or 0 */
 	bool		nulls_first;	/* do NULLs come before normal values? */
 	/* can eqop be implemented by hashing? */
-	bool		hashable;
+	bool		hashable pg_node_attr(query_jumble_ignore);
 } SortGroupClause;
 
 /*
@@ -1458,9 +1467,9 @@ typedef enum GroupingSetKind
 typedef struct GroupingSet
 {
 	NodeTag		type;
-	GroupingSetKind kind;
+	GroupingSetKind kind pg_node_attr(query_jumble_ignore);
 	List	   *content;
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } GroupingSet;
 
 /*
@@ -1479,35 +1488,38 @@ typedef struct GroupingSet
  * When refname isn't null, the partitionClause is always copied from there;
  * the orderClause might or might not be copied (see copiedOrder); the framing
  * options are never copied, per spec.
+ *
+ * The information relevant for the query jumbling is the partition clause
+ * type and its bounds.
  */
 typedef struct WindowClause
 {
 	NodeTag		type;
 	/* window name (NULL in an OVER clause) */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* referenced window name, if any */
-	char	   *refname;
+	char	   *refname pg_node_attr(query_jumble_ignore);
 	List	   *partitionClause;	/* PARTITION BY list */
 	/* ORDER BY list */
-	List	   *orderClause;
+	List	   *orderClause pg_node_attr(query_jumble_ignore);
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
 	/* qual to help short-circuit execution */
-	List	   *runCondition;
+	List	   *runCondition pg_node_attr(query_jumble_ignore);
 	/* in_range function for startOffset */
-	Oid			startInRangeFunc;
+	Oid			startInRangeFunc pg_node_attr(query_jumble_ignore);
 	/* in_range function for endOffset */
-	Oid			endInRangeFunc;
+	Oid			endInRangeFunc pg_node_attr(query_jumble_ignore);
 	/* collation for in_range tests */
-	Oid			inRangeColl;
+	Oid			inRangeColl pg_node_attr(query_jumble_ignore);
 	/* use ASC sort order for in_range tests? */
-	bool		inRangeAsc;
+	bool		inRangeAsc pg_node_attr(query_jumble_ignore);
 	/* nulls sort first for in_range tests? */
-	bool		inRangeNullsFirst;
+	bool		inRangeNullsFirst pg_node_attr(query_jumble_ignore);
 	Index		winref;			/* ID referenced by window functions */
 	/* did we copy orderClause from refname? */
-	bool		copiedOrder;
+	bool		copiedOrder pg_node_attr(query_jumble_ignore);
 } WindowClause;
 
 /*
@@ -1544,7 +1556,7 @@ typedef struct WithClause
 	List	   *ctes;			/* list of CommonTableExprs */
 	bool		recursive;		/* true = WITH RECURSIVE */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } WithClause;
 
 /*
@@ -1560,7 +1572,7 @@ typedef struct InferClause
 	Node	   *whereClause;	/* qualification (partial-index predicate) */
 	char	   *conname;		/* Constraint name, or NULL if unnamed */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } InferClause;
 
 /*
@@ -1577,7 +1589,7 @@ typedef struct OnConflictClause
 	List	   *targetList;		/* the target list (of ResTarget) */
 	Node	   *whereClause;	/* qualifications */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } OnConflictClause;
 
 /*
@@ -1598,7 +1610,7 @@ typedef struct CTESearchClause
 	List	   *search_col_list;
 	bool		search_breadth_first;
 	char	   *search_seq_column;
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } CTESearchClause;
 
 typedef struct CTECycleClause
@@ -1609,7 +1621,7 @@ typedef struct CTECycleClause
 	Node	   *cycle_mark_value;
 	Node	   *cycle_mark_default;
 	char	   *cycle_path_column;
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 	/* These fields are set during parse analysis: */
 	Oid			cycle_mark_type;	/* common type of _value and _default */
 	int			cycle_mark_typmod;
@@ -1625,27 +1637,27 @@ typedef struct CommonTableExpr
 	CTEMaterialize ctematerialized; /* is this an optimization fence? */
 	/* SelectStmt/InsertStmt/etc before parse analysis, Query afterwards: */
 	Node	   *ctequery;		/* the CTE's subquery */
-	CTESearchClause *search_clause;
-	CTECycleClause *cycle_clause;
+	CTESearchClause *search_clause pg_node_attr(query_jumble_ignore);
+	CTECycleClause *cycle_clause pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 	/* These fields are set during parse analysis: */
 	/* is this CTE actually recursive? */
-	bool		cterecursive;
+	bool		cterecursive pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * Number of RTEs referencing this CTE (excluding internal
-	 * self-references)
+	 * self-references), irrelevant for query jumbling.
 	 */
-	int			cterefcount;
+	int			cterefcount pg_node_attr(query_jumble_ignore);
 	/* list of output column names */
-	List	   *ctecolnames;
+	List	   *ctecolnames pg_node_attr(query_jumble_ignore);
 	/* OID list of output column type OIDs */
-	List	   *ctecoltypes;
+	List	   *ctecoltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of output column typmods */
-	List	   *ctecoltypmods;
+	List	   *ctecoltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *ctecolcollations;
+	List	   *ctecolcollations pg_node_attr(query_jumble_ignore);
 } CommonTableExpr;
 
 /* Convenience macro to get the output tlist of a CTE's query */
@@ -1683,11 +1695,11 @@ typedef struct MergeAction
 	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
 	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
 	/* OVERRIDING clause */
-	OverridingKind override;
+	OverridingKind override pg_node_attr(query_jumble_ignore);
 	Node	   *qual;			/* transformed WHEN conditions */
 	List	   *targetList;		/* the target list (of TargetEntry) */
 	/* target attribute numbers of an UPDATE */
-	List	   *updateColnos;
+	List	   *updateColnos pg_node_attr(query_jumble_ignore);
 } MergeAction;
 
 /*
@@ -1727,7 +1739,7 @@ typedef struct RawStmt
 	NodeTag		type;
 	Node	   *stmt;			/* raw parse tree */
 	/* start location, or -1 if unknown */
-	int			stmt_location;
+	int			stmt_location pg_node_attr(query_jumble_ignore);
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } RawStmt;
 
@@ -1897,15 +1909,15 @@ typedef struct SetOperationStmt
 	Node	   *rarg;			/* right child */
 	/* Eventually add fields for CORRESPONDING spec here */
 
-	/* Fields derived during parse analysis: */
+	/* Fields derived during parse analysis (irrelevant for query jumbling): */
 	/* OID list of output column type OIDs */
-	List	   *colTypes;
+	List	   *colTypes pg_node_attr(query_jumble_ignore);
 	/* integer list of output column typmods */
-	List	   *colTypmods;
+	List	   *colTypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of output column collation OIDs */
-	List	   *colCollations;
+	List	   *colCollations pg_node_attr(query_jumble_ignore);
 	/* a list of SortGroupClause's */
-	List	   *groupClauses;
+	List	   *groupClauses pg_node_attr(query_jumble_ignore);
 	/* groupClauses is NIL if UNION ALL, but must be set otherwise */
 } SetOperationStmt;
 
@@ -1936,7 +1948,7 @@ typedef struct PLAssignStmt
 	int			nnames;			/* number of names to use in ColumnRef */
 	SelectStmt *val;			/* the PL/pgSQL expression to assign */
 	/* name's token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } PLAssignStmt;
 
 
@@ -2444,7 +2456,7 @@ typedef struct Constraint
 	bool		deferrable;		/* DEFERRABLE? */
 	bool		initdeferred;	/* INITIALLY DEFERRED? */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 
 	/* Fields used for constraints with expressions (CHECK and DEFAULT): */
 	bool		is_no_inherit;	/* is constraint non-inheritable? */
@@ -3854,7 +3866,7 @@ typedef struct PublicationObjSpec
 	char	   *name;
 	PublicationTable *pubtable;
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } PublicationObjSpec;
 
 typedef struct CreatePublicationStmt
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f33d7fe167..5f105c48c3 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -100,7 +100,7 @@ typedef struct PlannedStmt
 
 	/* statement location in source string (copied from Query) */
 	/* start location, or -1 if unknown */
-	int			stmt_location;
+	int			stmt_location pg_node_attr(query_jumble_ignore);
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } PlannedStmt;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 8d80c90ce7..d20142bd0e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -86,7 +86,7 @@ typedef struct RangeVar
 	Alias	   *alias;
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } RangeVar;
 
 /*
@@ -99,31 +99,31 @@ typedef struct TableFunc
 {
 	NodeTag		type;
 	/* list of namespace URI expressions */
-	List	   *ns_uris;
+	List	   *ns_uris pg_node_attr(query_jumble_ignore);
 	/* list of namespace names or NULL */
-	List	   *ns_names;
+	List	   *ns_names pg_node_attr(query_jumble_ignore);
 	/* input document expression */
 	Node	   *docexpr;
 	/* row filter expression */
 	Node	   *rowexpr;
 	/* column names (list of String) */
-	List	   *colnames;
+	List	   *colnames pg_node_attr(query_jumble_ignore);
 	/* OID list of column type OIDs */
-	List	   *coltypes;
+	List	   *coltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of column typmods */
-	List	   *coltypmods;
+	List	   *coltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *colcollations;
+	List	   *colcollations pg_node_attr(query_jumble_ignore);
 	/* list of column filter expressions */
 	List	   *colexprs;
 	/* list of column default expressions */
-	List	   *coldefexprs;
+	List	   *coldefexprs pg_node_attr(query_jumble_ignore);
 	/* nullability flag for each output column */
-	Bitmapset  *notnulls;
+	Bitmapset  *notnulls pg_node_attr(query_jumble_ignore);
 	/* counts from 0; -1 if none specified */
-	int			ordinalitycol;
+	int			ordinalitycol pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } TableFunc;
 
 /*
@@ -230,11 +230,11 @@ typedef struct Var
 	AttrNumber	varattno;
 
 	/* pg_type OID for the type of this var */
-	Oid			vartype;
+	Oid			vartype pg_node_attr(query_jumble_ignore);
 	/* pg_attribute typmod value */
-	int32		vartypmod;
+	int32		vartypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			varcollid;
+	Oid			varcollid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * for subquery variables referencing outer relations; 0 in a normal var,
@@ -248,12 +248,12 @@ typedef struct Var
 	 * their varno/varattno match.
 	 */
 	/* syntactic relation index (0 if unknown) */
-	Index		varnosyn pg_node_attr(equal_ignore);
+	Index		varnosyn pg_node_attr(equal_ignore, query_jumble_ignore);
 	/* syntactic attribute number */
-	AttrNumber	varattnosyn pg_node_attr(equal_ignore);
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } Var;
 
 /*
@@ -263,6 +263,8 @@ typedef struct Var
  * must be in non-extended form (4-byte header, no compression or external
  * references).  This ensures that the Const node is self-contained and makes
  * it more likely that equal() will see logically identical values as equal.
+ *
+ * Only the constant type OID is relevant for the query jumbling.
  */
 typedef struct Const
 {
@@ -272,23 +274,26 @@ typedef struct Const
 	/* pg_type OID of the constant's datatype */
 	Oid			consttype;
 	/* typmod value, if any */
-	int32		consttypmod;
+	int32		consttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			constcollid;
+	Oid			constcollid pg_node_attr(query_jumble_ignore);
 	/* typlen of the constant's datatype */
-	int			constlen;
+	int			constlen pg_node_attr(query_jumble_ignore);
 	/* the constant's value */
-	Datum		constvalue;
+	Datum		constvalue pg_node_attr(query_jumble_ignore);
 	/* whether the constant is null (if true, constvalue is undefined) */
-	bool		constisnull;
+	bool		constisnull pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * Whether this datatype is passed by value.  If true, then all the
 	 * information is stored in the Datum.  If false, then the Datum contains
 	 * a pointer to the information.
 	 */
-	bool		constbyval;
-	/* token location, or -1 if unknown */
+	bool		constbyval pg_node_attr(query_jumble_ignore);
+	/*
+	 * token location, or -1 if unknown.  Note that this is used in the
+	 * query jumbling.
+	 */
 	int			location;
 } Const;
 
@@ -327,6 +332,7 @@ typedef enum ParamKind
 	PARAM_MULTIEXPR
 } ParamKind;
 
+/* typmod and collation information are irrelevant for the query jumbling. */
 typedef struct Param
 {
 	Expr		xpr;
@@ -334,11 +340,11 @@ typedef struct Param
 	int			paramid;		/* numeric ID for parameter */
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
 	/* typmod value, if known */
-	int32		paramtypmod;
+	int32		paramtypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			paramcollid;
+	Oid			paramcollid pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } Param;
 
 /*
@@ -389,6 +395,9 @@ typedef struct Param
  * and can share the result.  Aggregates with same 'transno' but different
  * 'aggno' can share the same transition state, only the final function needs
  * to be called separately.
+ *
+ * Information related to collations, transition types and internal states
+ * are irrelevant for the query jumbling.
  */
 typedef struct Aggref
 {
@@ -398,22 +407,22 @@ typedef struct Aggref
 	Oid			aggfnoid;
 
 	/* type Oid of result of the aggregate */
-	Oid			aggtype;
+	Oid			aggtype pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			aggcollid;
+	Oid			aggcollid pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * type Oid of aggregate's transition value; ignored for equal since it
 	 * might not be set yet
 	 */
-	Oid			aggtranstype pg_node_attr(equal_ignore);
+	Oid			aggtranstype pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* type Oids of direct and aggregated args */
-	List	   *aggargtypes;
+	List	   *aggargtypes pg_node_attr(query_jumble_ignore);
 
 	/* direct arguments, if an ordered-set agg */
 	List	   *aggdirectargs;
@@ -431,34 +440,34 @@ typedef struct Aggref
 	Expr	   *aggfilter;
 
 	/* true if argument list was really '*' */
-	bool		aggstar;
+	bool		aggstar pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		aggvariadic;
+	bool		aggvariadic pg_node_attr(query_jumble_ignore);
 
 	/* aggregate kind (see pg_aggregate.h) */
-	char		aggkind;
+	char		aggkind pg_node_attr(query_jumble_ignore);
 
 	/* aggregate input already sorted */
-	bool		aggpresorted pg_node_attr(equal_ignore);
+	bool		aggpresorted pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* > 0 if agg belongs to outer query */
-	Index		agglevelsup;
+	Index		agglevelsup pg_node_attr(query_jumble_ignore);
 
 	/* expected agg-splitting mode of parent Agg */
-	AggSplit	aggsplit;
+	AggSplit	aggsplit pg_node_attr(query_jumble_ignore);
 
 	/* unique ID within the Agg node */
-	int			aggno;
+	int			aggno pg_node_attr(query_jumble_ignore);
 
 	/* unique ID of transition state in the Agg */
-	int			aggtransno;
+	int			aggtransno pg_node_attr(query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } Aggref;
 
 /*
@@ -484,29 +493,35 @@ typedef struct Aggref
  *
  * In raw parse output we have only the args list; parse analysis fills in the
  * refs list, and the planner fills in the cols list.
+ *
+ * All the fields used as information for an internal state are irrelevant
+ * for the query jumbling.
  */
 typedef struct GroupingFunc
 {
 	Expr		xpr;
 
 	/* arguments, not evaluated but kept for benefit of EXPLAIN etc. */
-	List	   *args;
+	List	   *args pg_node_attr(query_jumble_ignore);
 
 	/* ressortgrouprefs of arguments */
 	List	   *refs pg_node_attr(equal_ignore);
 
 	/* actual column positions set by planner */
-	List	   *cols pg_node_attr(equal_ignore);
+	List	   *cols pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* same as Aggref.agglevelsup */
 	Index		agglevelsup;
 
 	/* token location */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } GroupingFunc;
 
 /*
  * WindowFunc
+ *
+ * Collation information is irrelevant for the query jumbling, as is the
+ * internal state information of the node like "winstar" and "winagg".
  */
 typedef struct WindowFunc
 {
@@ -514,11 +529,11 @@ typedef struct WindowFunc
 	/* pg_proc Oid of the function */
 	Oid			winfnoid;
 	/* type Oid of result of the window function */
-	Oid			wintype;
+	Oid			wintype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			wincollid;
+	Oid			wincollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the window function */
 	List	   *args;
 	/* FILTER expression, if any */
@@ -526,11 +541,11 @@ typedef struct WindowFunc
 	/* index of associated WindowClause */
 	Index		winref;
 	/* true if argument list was really '*' */
-	bool		winstar;
+	bool		winstar pg_node_attr(query_jumble_ignore);
 	/* is function a simple aggregate? */
-	bool		winagg;
+	bool		winagg pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } WindowFunc;
 
 /*
@@ -567,6 +582,8 @@ typedef struct WindowFunc
  * subscripting logic.  Likewise, reftypmod and refcollid will match the
  * container's properties in a store, but could be different in a fetch.
  *
+ * Any internal state data is ignored for the query jumbling.
+ *
  * Note: for the cases where a container is returned, if refexpr yields a R/W
  * expanded container, then the implementation is allowed to modify that
  * object in-place and return the same object.
@@ -575,15 +592,15 @@ typedef struct SubscriptingRef
 {
 	Expr		xpr;
 	/* type of the container proper */
-	Oid			refcontainertype;
+	Oid			refcontainertype pg_node_attr(query_jumble_ignore);
 	/* the container type's pg_type.typelem */
-	Oid			refelemtype;
+	Oid			refelemtype pg_node_attr(query_jumble_ignore);
 	/* type of the SubscriptingRef's result */
-	Oid			refrestype;
+	Oid			refrestype pg_node_attr(query_jumble_ignore);
 	/* typmod of the result */
-	int32		reftypmod;
+	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
-	Oid			refcollid;
+	Oid			refcollid pg_node_attr(query_jumble_ignore);
 	/* expressions that evaluate to upper container indexes */
 	List	   *refupperindexpr;
 
@@ -634,6 +651,9 @@ typedef enum CoercionForm
 
 /*
  * FuncExpr - expression node for a function call
+ *
+ * Collation information is irrelevant for the query jumbling, only the
+ * arguments and the function OID matter.
  */
 typedef struct FuncExpr
 {
@@ -641,25 +661,25 @@ typedef struct FuncExpr
 	/* PG_PROC OID of the function */
 	Oid			funcid;
 	/* PG_TYPE OID of result value */
-	Oid			funcresulttype;
+	Oid			funcresulttype pg_node_attr(query_jumble_ignore);
 	/* true if function returns set */
-	bool		funcretset;
+	bool		funcretset pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		funcvariadic;
+	bool		funcvariadic pg_node_attr(query_jumble_ignore);
 	/* how to display this function call */
-	CoercionForm funcformat;
+	CoercionForm funcformat pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			funccollid;
+	Oid			funccollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the function */
 	List	   *args;
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } FuncExpr;
 
 /*
@@ -682,11 +702,11 @@ typedef struct NamedArgExpr
 	/* the argument expression */
 	Expr	   *arg;
 	/* the name */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* argument's number in positional notation */
 	int			argnumber;
 	/* argument name location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } NamedArgExpr;
 
 /*
@@ -698,6 +718,9 @@ typedef struct NamedArgExpr
  * 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.
+ *
+ * Internal state information and collation data is irrelevant for the query
+ * jumbling.
  */
 typedef struct OpExpr
 {
@@ -707,25 +730,25 @@ typedef struct OpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of underlying function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_TYPE OID of result value */
-	Oid			opresulttype;
+	Oid			opresulttype pg_node_attr(query_jumble_ignore);
 
 	/* true if operator returns set */
-	bool		opretset;
+	bool		opretset pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			opcollid;
+	Oid			opcollid pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/* arguments to the operator (1 or 2) */
 	List	   *args;
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } OpExpr;
 
 /*
@@ -775,6 +798,10 @@ typedef OpExpr NullIfExpr;
  * 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.
+ *
+ *
+ * OID entries of the internal function types are irrelevant for the query
+ * jumbling, but the operator OID and the arguments are.
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -784,25 +811,25 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of comparison function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_PROC OID of hash func or InvalidOid */
-	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_PROC OID of negator of opfuncid function or InvalidOid.  See above */
-	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* true for ANY, false for ALL */
 	bool		useOr;
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/* the scalar and array operands */
 	List	   *args;
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } ScalarArrayOpExpr;
 
 /*
@@ -825,7 +852,7 @@ typedef struct BoolExpr
 	BoolExprType boolop;
 	List	   *args;			/* arguments to this expression */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } BoolExpr;
 
 /*
@@ -899,11 +926,11 @@ typedef struct SubLink
 	int			subLinkId;		/* ID (1..n); 0 if not MULTIEXPR */
 	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
 	/* originally specified operator name */
-	List	   *operName;
+	List	   *operName pg_node_attr(query_jumble_ignore);
 	/* subselect as Query* or raw parsetree */
 	Node	   *subselect;
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } SubLink;
 
 /*
@@ -1012,11 +1039,11 @@ typedef struct FieldSelect
 	Expr	   *arg;			/* input expression */
 	AttrNumber	fieldnum;		/* attribute number of field to extract */
 	/* type of the field (result type of this node) */
-	Oid			resulttype;
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* output typmod (usually -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation of the field */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 } FieldSelect;
 
 /* ----------------
@@ -1043,9 +1070,9 @@ typedef struct FieldStore
 	Expr	   *arg;			/* input tuple value */
 	List	   *newvals;		/* new value(s) for field(s) */
 	/* integer list of field attnums */
-	List	   *fieldnums;
+	List	   *fieldnums pg_node_attr(query_jumble_ignore);
 	/* type of result (same as type of arg) */
-	Oid			resulttype;
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 } FieldStore;
 
@@ -1068,13 +1095,13 @@ typedef struct RelabelType
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion expression */
 	/* output typmod (usually -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm relabelformat;
+	CoercionForm relabelformat pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } RelabelType;
 
 /* ----------------
@@ -1093,11 +1120,11 @@ typedef struct CoerceViaIO
 	Oid			resulttype;		/* output type of coercion */
 	/* output typmod is not stored, but is presumed -1 */
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coerceformat;
+	CoercionForm coerceformat pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } CoerceViaIO;
 
 /* ----------------
@@ -1120,13 +1147,13 @@ typedef struct ArrayCoerceExpr
 	Expr	   *elemexpr;		/* expression representing per-element work */
 	Oid			resulttype;		/* output type of coercion (an array type) */
 	/* output typmod (also element typmod) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coerceformat;
+	CoercionForm coerceformat pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } ArrayCoerceExpr;
 
 /* ----------------
@@ -1149,9 +1176,9 @@ typedef struct ConvertRowtypeExpr
 	Oid			resulttype;		/* output type (always a composite type) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 	/* how to display this node */
-	CoercionForm convertformat;
+	CoercionForm convertformat pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } ConvertRowtypeExpr;
 
 /*----------
@@ -1167,7 +1194,7 @@ typedef struct CollateExpr
 	Expr	   *arg;			/* input expression */
 	Oid			collOid;		/* collation's OID */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } CollateExpr;
 
 /*----------
@@ -1196,14 +1223,14 @@ typedef struct CaseExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			casetype;
+	Oid			casetype pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			casecollid;
+	Oid			casecollid pg_node_attr(query_jumble_ignore);
 	Expr	   *arg;			/* implicit equality comparison argument */
 	List	   *args;			/* the arguments (list of WHEN clauses) */
 	Expr	   *defresult;		/* the default result (ELSE clause) */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } CaseExpr;
 
 /*
@@ -1215,7 +1242,7 @@ typedef struct CaseWhen
 	Expr	   *expr;			/* condition expression */
 	Expr	   *result;			/* substitution result */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } CaseWhen;
 
 /*
@@ -1243,9 +1270,9 @@ typedef struct CaseTestExpr
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 } CaseTestExpr;
 
 /*
@@ -1260,17 +1287,17 @@ typedef struct ArrayExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			array_typeid;
+	Oid			array_typeid pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			array_collid;
+	Oid			array_collid pg_node_attr(query_jumble_ignore);
 	/* common type of array elements */
-	Oid			element_typeid;
+	Oid			element_typeid pg_node_attr(query_jumble_ignore);
 	/* the array elements or sub-arrays */
 	List	   *elements;
 	/* true if elements are sub-arrays */
-	bool		multidims;
+	bool		multidims pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } ArrayExpr;
 
 /*
@@ -1300,7 +1327,7 @@ typedef struct RowExpr
 	List	   *args;			/* the fields */
 
 	/* RECORDOID or a composite type's ID */
-	Oid			row_typeid;
+	Oid			row_typeid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * row_typeid cannot be a domain over composite, only plain composite.  To
@@ -1316,13 +1343,13 @@ typedef struct RowExpr
 	 */
 
 	/* how to display this node */
-	CoercionForm row_format;
+	CoercionForm row_format pg_node_attr(query_jumble_ignore);
 
 	/* list of String, or NIL */
-	List	   *colnames;
+	List	   *colnames pg_node_attr(query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } RowExpr;
 
 /*
@@ -1357,11 +1384,11 @@ typedef struct RowCompareExpr
 	/* LT LE GE or GT, never EQ or NE */
 	RowCompareType rctype;
 	/* OID list of pairwise comparison ops */
-	List	   *opnos;
+	List	   *opnos pg_node_attr(query_jumble_ignore);
 	/* OID list of containing operator families */
-	List	   *opfamilies;
+	List	   *opfamilies pg_node_attr(query_jumble_ignore);
 	/* OID list of collations for comparisons */
-	List	   *inputcollids;
+	List	   *inputcollids pg_node_attr(query_jumble_ignore);
 	/* the left-hand input arguments */
 	List	   *largs;
 	/* the right-hand input arguments */
@@ -1375,13 +1402,13 @@ typedef struct CoalesceExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			coalescetype;
+	Oid			coalescetype pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			coalescecollid;
+	Oid			coalescecollid pg_node_attr(query_jumble_ignore);
 	/* the arguments */
 	List	   *args;
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } CoalesceExpr;
 
 /*
@@ -1397,17 +1424,17 @@ typedef struct MinMaxExpr
 {
 	Expr		xpr;
 	/* common type of arguments and result */
-	Oid			minmaxtype;
+	Oid			minmaxtype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			minmaxcollid;
+	Oid			minmaxcollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* function to execute */
 	MinMaxOp	op;
 	/* the arguments */
 	List	   *args;
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } MinMaxExpr;
 
 /*
@@ -1445,20 +1472,20 @@ typedef struct XmlExpr
 	/* xml function ID */
 	XmlExprOp	op;
 	/* name in xml(NAME foo ...) syntaxes */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* non-XML expressions for xml_attributes */
 	List	   *named_args;
 	/* parallel list of String values */
-	List	   *arg_names;
+	List	   *arg_names pg_node_attr(query_jumble_ignore);
 	/* list of expressions */
 	List	   *args;
 	/* DOCUMENT or CONTENT */
-	XmlOptionType xmloption;
+	XmlOptionType xmloption pg_node_attr(query_jumble_ignore);
 	/* target type/typmod for XMLSERIALIZE */
-	Oid			type;
-	int32		typmod;
+	Oid			type pg_node_attr(query_jumble_ignore);
+	int32		typmod pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } XmlExpr;
 
 /* ----------------
@@ -1491,9 +1518,9 @@ typedef struct NullTest
 	Expr	   *arg;			/* input expression */
 	NullTestType nulltesttype;	/* IS NULL, IS NOT NULL */
 	/* T to perform field-by-field null checks */
-	bool		argisrow;
+	bool		argisrow pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } NullTest;
 
 /*
@@ -1516,7 +1543,7 @@ typedef struct BooleanTest
 	Expr	   *arg;			/* input expression */
 	BoolTestType booltesttype;	/* test type */
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } BooleanTest;
 
 /*
@@ -1527,6 +1554,8 @@ typedef struct BooleanTest
  * checked will be determined.  If the value passes, it is returned as the
  * result; if not, an error is raised.  Note that this is equivalent to
  * RelabelType in the scenario where no constraints are applied.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct CoerceToDomain
 {
@@ -1534,13 +1563,13 @@ typedef struct CoerceToDomain
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* domain type ID (result type) */
 	/* output typmod (currently always -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coercionformat;
+	CoercionForm coercionformat pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } CoerceToDomain;
 
 /*
@@ -1558,11 +1587,11 @@ typedef struct CoerceToDomainValue
 	/* type for substituted value */
 	Oid			typeId;
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } CoerceToDomainValue;
 
 /*
@@ -1571,6 +1600,8 @@ typedef struct CoerceToDomainValue
  * This is not an executable expression: it must be replaced by the actual
  * column default expression during rewriting.  But it is convenient to
  * treat it as an expression node during parsing and rewriting.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct SetToDefault
 {
@@ -1578,11 +1609,11 @@ typedef struct SetToDefault
 	/* type for substituted value */
 	Oid			typeId;
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
-	int			location;
+	int			location pg_node_attr(query_jumble_ignore);
 } SetToDefault;
 
 /*
@@ -1698,15 +1729,15 @@ typedef struct TargetEntry
 	/* attribute number (see notes above) */
 	AttrNumber	resno;
 	/* name of the column (could be NULL) */
-	char	   *resname;
+	char	   *resname pg_node_attr(query_jumble_ignore);
 	/* nonzero if referenced by a sort/group clause */
 	Index		ressortgroupref;
 	/* OID of column's source table */
-	Oid			resorigtbl;
+	Oid			resorigtbl pg_node_attr(query_jumble_ignore);
 	/* column's number in source table */
-	AttrNumber	resorigcol;
+	AttrNumber	resorigcol pg_node_attr(query_jumble_ignore);
 	/* set to true to eliminate the attribute from final target list */
-	bool		resjunk;
+	bool		resjunk pg_node_attr(query_jumble_ignore);
 } TargetEntry;
 
 
@@ -1789,13 +1820,13 @@ typedef struct JoinExpr
 	Node	   *larg;			/* left subtree */
 	Node	   *rarg;			/* right subtree */
 	/* USING clause, if any (list of String) */
-	List	   *usingClause;
+	List	   *usingClause pg_node_attr(query_jumble_ignore);
 	/* alias attached to USING clause, if any */
-	Alias	   *join_using_alias;
+	Alias	   *join_using_alias pg_node_attr(query_jumble_ignore);
 	/* qualifiers on join, if any */
 	Node	   *quals;
 	/* user-written alias clause, if any */
-	Alias	   *alias;
+	Alias	   *alias pg_node_attr(query_jumble_ignore);
 	/* RT index assigned for join, or 0 */
 	int			rtindex;
 } JoinExpr;
diff --git a/src/backend/nodes/README b/src/backend/nodes/README
index 489a67eb89..7cf6e3b041 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -51,6 +51,7 @@ FILES IN THIS DIRECTORY (src/backend/nodes/)
 	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
+	queryjumblefuncs.c - compute a node tree for query jumbling (*)
 
     (*) - Most functions in these files are generated by
     gen_node_support.pl and #include'd there.
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index b3c1ead496..13e2043d55 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -121,6 +121,8 @@ my %node_type_info;
 my @no_copy;
 # node types we don't want equal support for
 my @no_equal;
+# node types we don't want jumble support for
+my @no_query_jumble;
 # node types we don't want read support for
 my @no_read;
 # node types we don't want read/write support for
@@ -155,12 +157,13 @@ 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);
-# Lists are specially treated in all four support files, too.
+# Lists are specially treated in all five support files, too.
 # (Ideally we'd mark List as "special copy/equal" not "no copy/equal".
 # But until there's other use-cases for that, just hot-wire the tests
 # that would need to distinguish.)
 push @no_copy,            qw(List);
 push @no_equal,           qw(List);
+push @no_query_jumble,          qw(List);
 push @special_read_write, qw(List);
 
 # Nodes with custom copy/equal implementations are skipped from
@@ -332,6 +335,10 @@ foreach my $infile (@ARGV)
 							push @no_copy,  $in_struct;
 							push @no_equal, $in_struct;
 						}
+						elsif ($attr eq 'no_query_jumble')
+						{
+							push @no_query_jumble, $in_struct;
+						}
 						elsif ($attr eq 'no_read')
 						{
 							push @no_read, $in_struct;
@@ -457,6 +464,7 @@ foreach my $infile (@ARGV)
 								equal_as_scalar
 								equal_ignore
 								equal_ignore_if_zero
+								query_jumble_ignore
 								read_write_ignore
 								write_only_relids
 								write_only_nondefault_pathtarget
@@ -1225,6 +1233,91 @@ close $ofs;
 close $rfs;
 
 
+# queryjumblefuncs.c
+
+push @output_files, 'queryjumblefuncs.funcs.c';
+open my $jff, '>', "$output_path/queryjumblefuncs.funcs.c$tmpext" or die $!;
+push @output_files, 'queryjumblefuncs.switch.c';
+open my $jfs, '>', "$output_path/queryjumblefuncs.switch.c$tmpext" or die $!;
+
+printf $jff $header_comment, 'queryjumblefuncs.funcs.c';
+printf $jfs $header_comment, 'queryjumblefuncs.switch.c';
+
+print $jff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @nodetag_only;
+	my $struct_no_query_jumble = (elem $n, @no_query_jumble);
+
+	print $jfs "\t\t\tcase T_${n}:\n"
+	  . "\t\t\t\t_jumble${n}(jstate, expr);\n"
+	  . "\t\t\t\tbreak;\n"
+	  unless $struct_no_query_jumble;
+
+	print $jff "
+static void
+_jumble${n}(JumbleState *jstate, Node *node)
+{
+\t${n} *expr = (${n} *) node;\n
+" unless $struct_no_query_jumble;
+
+	# 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 $query_jumble_ignore = $struct_no_query_jumble;
+
+		# extract per-field attributes
+		foreach my $a (@a)
+		{
+			if ($a eq 'query_jumble_ignore')
+			{
+				$query_jumble_ignore = 1;
+			}
+		}
+
+		# node type
+		if (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+			and elem $1, @node_types)
+		{
+			print $jff "\tJUMBLE_NODE($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			print $jff "\tJUMBLE_LOCATION($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		elsif ($t eq 'char*')
+		{
+			print $jff "\tJUMBLE_STRING($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		else
+		{
+			print $jff "\tJUMBLE_FIELD($f);\n"
+			  unless $query_jumble_ignore;
+		}
+	}
+
+	# Some nodes have no attributes like CheckPointStmt,
+	# so tweak things for empty contents.
+	if (scalar(@{ $node_type_info{$n}->{fields} }) == 0)
+	{
+		print $jff "\t(void) expr;\n"
+		  unless $struct_no_query_jumble;
+	}
+
+	print $jff "}
+" unless $struct_no_query_jumble;
+}
+
+close $jff;
+close $jfs;
+
 # now rename the temporary files to their final names
 foreach my $file (@output_files)
 {
diff --git a/src/backend/nodes/meson.build b/src/backend/nodes/meson.build
index 9230515e7f..31467a12d3 100644
--- a/src/backend/nodes/meson.build
+++ b/src/backend/nodes/meson.build
@@ -10,7 +10,6 @@ backend_sources += files(
   'nodes.c',
   'params.c',
   'print.c',
-  'queryjumblefuncs.c',
   'read.c',
   'tidbitmap.c',
   'value.c',
@@ -21,6 +20,7 @@ backend_sources += files(
 nodefunc_sources = files(
   'copyfuncs.c',
   'equalfuncs.c',
+  'queryjumblefuncs.c',
   'outfuncs.c',
   'readfuncs.c',
 )
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 16084842a3..278150fba0 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -21,7 +21,7 @@
  * tree(s) generated from the query.  The executor can then use this value
  * to blame query costs on the proper queryId.
  *
- * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
@@ -45,15 +45,12 @@ int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
 /* True when compute_query_id is ON, or AUTO and a module requests them */
 bool		query_id_enabled = false;
 
-static uint64 compute_utility_query_id(const char *query_text,
-									   int query_location, int query_len);
 static void AppendJumble(JumbleState *jstate,
 						 const unsigned char *item, Size size);
-static void JumbleQueryInternal(JumbleState *jstate, Query *query);
-static void JumbleRangeTable(JumbleState *jstate, List *rtable);
-static void JumbleRowMarks(JumbleState *jstate, List *rowMarks);
-static void JumbleExpr(JumbleState *jstate, Node *node);
 static void RecordConstLocation(JumbleState *jstate, int location);
+static void _jumbleNode(JumbleState *jstate, Node *node);
+static void _jumbleList(JumbleState *jstate, Node *node);
+static void _jumbleRangeTblEntry(JumbleState *jstate, Node *node);
 
 /*
  * Given a possibly multi-statement source string, confine our attention to the
@@ -105,38 +102,29 @@ JumbleQuery(Query *query, const char *querytext)
 
 	Assert(IsQueryIdEnabled());
 
-	if (query->utilityStmt)
-	{
-		query->queryId = compute_utility_query_id(querytext,
-												  query->stmt_location,
-												  query->stmt_len);
-	}
-	else
-	{
-		jstate = (JumbleState *) palloc(sizeof(JumbleState));
+	jstate = (JumbleState *) palloc(sizeof(JumbleState));
 
-		/* Set up workspace for query jumbling */
-		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-		jstate->jumble_len = 0;
-		jstate->clocations_buf_size = 32;
-		jstate->clocations = (LocationLen *)
-			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-		jstate->clocations_count = 0;
-		jstate->highest_extern_param_id = 0;
+	/* Set up workspace for query jumbling */
+	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+	jstate->jumble_len = 0;
+	jstate->clocations_buf_size = 32;
+	jstate->clocations = (LocationLen *)
+		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+	jstate->clocations_count = 0;
+	jstate->highest_extern_param_id = 0;
 
-		/* Compute query ID and mark the Query node with it */
-		JumbleQueryInternal(jstate, query);
-		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-														  jstate->jumble_len,
-														  0));
+	/* Compute query ID and mark the Query node with it */
+	_jumbleNode(jstate, (Node *) query);
+	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+													  jstate->jumble_len,
+													  0));
 
-		/*
-		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-		 * prevent confusion with the utility-statement case.
-		 */
-		if (query->queryId == UINT64CONST(0))
-			query->queryId = UINT64CONST(1);
-	}
+	/*
+	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+	 * prevent confusion with the utility-statement case.
+	 */
+	if (query->queryId == UINT64CONST(0))
+		query->queryId = UINT64CONST(1);
 
 	return jstate;
 }
@@ -154,34 +142,6 @@ EnableQueryId(void)
 		query_id_enabled = true;
 }
 
-/*
- * Compute a query identifier for the given utility query string.
- */
-static uint64
-compute_utility_query_id(const char *query_text, int query_location, int query_len)
-{
-	uint64		queryId;
-	const char *sql;
-
-	/*
-	 * Confine our attention to the relevant part of the string, if the query
-	 * is a portion of a multi-statement source string.
-	 */
-	sql = CleanQuerytext(query_text, &query_location, &query_len);
-
-	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
-											   query_len, 0));
-
-	/*
-	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
-	 * 2 instead, queryID 1 is already in use for normal statements.
-	 */
-	if (queryId == UINT64CONST(0))
-		queryId = UINT64CONST(2);
-
-	return queryId;
-}
-
 /*
  * AppendJumble: Append a value that is substantive in a given query to
  * the current jumble.
@@ -219,621 +179,6 @@ AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
 	jstate->jumble_len = jumble_len;
 }
 
-/*
- * Wrappers around AppendJumble to encapsulate details of serialization
- * of individual local variable elements.
- */
-#define APP_JUMB(item) \
-	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
-#define APP_JUMB_STRING(str) \
-	AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1)
-
-/*
- * JumbleQueryInternal: Selectively serialize the query tree, appending
- * significant data to the "query jumble" while ignoring nonsignificant data.
- *
- * Rule of thumb for what to include is that we should ignore anything not
- * semantically significant (such as alias names) as well as anything that can
- * be deduced from child nodes (else we'd just be double-hashing that piece
- * of information).
- */
-static void
-JumbleQueryInternal(JumbleState *jstate, Query *query)
-{
-	Assert(IsA(query, Query));
-	Assert(query->utilityStmt == NULL);
-
-	APP_JUMB(query->commandType);
-	/* resultRelation is usually predictable from commandType */
-	JumbleExpr(jstate, (Node *) query->cteList);
-	JumbleRangeTable(jstate, query->rtable);
-	JumbleExpr(jstate, (Node *) query->jointree);
-	JumbleExpr(jstate, (Node *) query->mergeActionList);
-	JumbleExpr(jstate, (Node *) query->targetList);
-	JumbleExpr(jstate, (Node *) query->onConflict);
-	JumbleExpr(jstate, (Node *) query->returningList);
-	JumbleExpr(jstate, (Node *) query->groupClause);
-	APP_JUMB(query->groupDistinct);
-	JumbleExpr(jstate, (Node *) query->groupingSets);
-	JumbleExpr(jstate, query->havingQual);
-	JumbleExpr(jstate, (Node *) query->windowClause);
-	JumbleExpr(jstate, (Node *) query->distinctClause);
-	JumbleExpr(jstate, (Node *) query->sortClause);
-	JumbleExpr(jstate, query->limitOffset);
-	JumbleExpr(jstate, query->limitCount);
-	APP_JUMB(query->limitOption);
-	JumbleRowMarks(jstate, query->rowMarks);
-	JumbleExpr(jstate, query->setOperations);
-}
-
-/*
- * Jumble a range table
- */
-static void
-JumbleRangeTable(JumbleState *jstate, List *rtable)
-{
-	ListCell   *lc;
-
-	foreach(lc, rtable)
-	{
-		RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-		APP_JUMB(rte->rtekind);
-		switch (rte->rtekind)
-		{
-			case RTE_RELATION:
-				APP_JUMB(rte->relid);
-				JumbleExpr(jstate, (Node *) rte->tablesample);
-				APP_JUMB(rte->inh);
-				break;
-			case RTE_SUBQUERY:
-				JumbleQueryInternal(jstate, rte->subquery);
-				break;
-			case RTE_JOIN:
-				APP_JUMB(rte->jointype);
-				break;
-			case RTE_FUNCTION:
-				JumbleExpr(jstate, (Node *) rte->functions);
-				break;
-			case RTE_TABLEFUNC:
-				JumbleExpr(jstate, (Node *) rte->tablefunc);
-				break;
-			case RTE_VALUES:
-				JumbleExpr(jstate, (Node *) rte->values_lists);
-				break;
-			case RTE_CTE:
-
-				/*
-				 * Depending on the CTE name here isn't ideal, but it's the
-				 * only info we have to identify the referenced WITH item.
-				 */
-				APP_JUMB_STRING(rte->ctename);
-				APP_JUMB(rte->ctelevelsup);
-				break;
-			case RTE_NAMEDTUPLESTORE:
-				APP_JUMB_STRING(rte->enrname);
-				break;
-			case RTE_RESULT:
-				break;
-			default:
-				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
-				break;
-		}
-	}
-}
-
-/*
- * Jumble a rowMarks list
- */
-static void
-JumbleRowMarks(JumbleState *jstate, List *rowMarks)
-{
-	ListCell   *lc;
-
-	foreach(lc, rowMarks)
-	{
-		RowMarkClause *rowmark = lfirst_node(RowMarkClause, lc);
-
-		if (!rowmark->pushedDown)
-		{
-			APP_JUMB(rowmark->rti);
-			APP_JUMB(rowmark->strength);
-			APP_JUMB(rowmark->waitPolicy);
-		}
-	}
-}
-
-/*
- * Jumble an expression tree
- *
- * In general this function should handle all the same node types that
- * expression_tree_walker() does, and therefore it's coded to be as parallel
- * to that function as possible.  However, since we are only invoked on
- * queries immediately post-parse-analysis, we need not handle node types
- * that only appear in planning.
- *
- * Note: the reason we don't simply use expression_tree_walker() is that the
- * point of that function is to support tree walkers that don't care about
- * most tree node types, but here we care about all types.  We should complain
- * about any unrecognized node type.
- */
-static void
-JumbleExpr(JumbleState *jstate, Node *node)
-{
-	ListCell   *temp;
-
-	if (node == NULL)
-		return;
-
-	/* Guard against stack overflow due to overly complex expressions */
-	check_stack_depth();
-
-	/*
-	 * We always emit the node's NodeTag, then any additional fields that are
-	 * considered significant, and then we recurse to any child nodes.
-	 */
-	APP_JUMB(node->type);
-
-	switch (nodeTag(node))
-	{
-		case T_Var:
-			{
-				Var		   *var = (Var *) node;
-
-				APP_JUMB(var->varno);
-				APP_JUMB(var->varattno);
-				APP_JUMB(var->varlevelsup);
-			}
-			break;
-		case T_Const:
-			{
-				Const	   *c = (Const *) node;
-
-				/* We jumble only the constant's type, not its value */
-				APP_JUMB(c->consttype);
-				/* Also, record its parse location for query normalization */
-				RecordConstLocation(jstate, c->location);
-			}
-			break;
-		case T_Param:
-			{
-				Param	   *p = (Param *) node;
-
-				APP_JUMB(p->paramkind);
-				APP_JUMB(p->paramid);
-				APP_JUMB(p->paramtype);
-				/* Also, track the highest external Param id */
-				if (p->paramkind == PARAM_EXTERN &&
-					p->paramid > jstate->highest_extern_param_id)
-					jstate->highest_extern_param_id = p->paramid;
-			}
-			break;
-		case T_Aggref:
-			{
-				Aggref	   *expr = (Aggref *) node;
-
-				APP_JUMB(expr->aggfnoid);
-				JumbleExpr(jstate, (Node *) expr->aggdirectargs);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggorder);
-				JumbleExpr(jstate, (Node *) expr->aggdistinct);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_GroupingFunc:
-			{
-				GroupingFunc *grpnode = (GroupingFunc *) node;
-
-				JumbleExpr(jstate, (Node *) grpnode->refs);
-				APP_JUMB(grpnode->agglevelsup);
-			}
-			break;
-		case T_WindowFunc:
-			{
-				WindowFunc *expr = (WindowFunc *) node;
-
-				APP_JUMB(expr->winfnoid);
-				APP_JUMB(expr->winref);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_SubscriptingRef:
-			{
-				SubscriptingRef *sbsref = (SubscriptingRef *) node;
-
-				JumbleExpr(jstate, (Node *) sbsref->refupperindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refassgnexpr);
-			}
-			break;
-		case T_FuncExpr:
-			{
-				FuncExpr   *expr = (FuncExpr *) node;
-
-				APP_JUMB(expr->funcid);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_NamedArgExpr:
-			{
-				NamedArgExpr *nae = (NamedArgExpr *) node;
-
-				APP_JUMB(nae->argnumber);
-				JumbleExpr(jstate, (Node *) nae->arg);
-			}
-			break;
-		case T_OpExpr:
-		case T_DistinctExpr:	/* struct-equivalent to OpExpr */
-		case T_NullIfExpr:		/* struct-equivalent to OpExpr */
-			{
-				OpExpr	   *expr = (OpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_ScalarArrayOpExpr:
-			{
-				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				APP_JUMB(expr->useOr);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_BoolExpr:
-			{
-				BoolExpr   *expr = (BoolExpr *) node;
-
-				APP_JUMB(expr->boolop);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_SubLink:
-			{
-				SubLink    *sublink = (SubLink *) node;
-
-				APP_JUMB(sublink->subLinkType);
-				APP_JUMB(sublink->subLinkId);
-				JumbleExpr(jstate, (Node *) sublink->testexpr);
-				JumbleQueryInternal(jstate, castNode(Query, sublink->subselect));
-			}
-			break;
-		case T_FieldSelect:
-			{
-				FieldSelect *fs = (FieldSelect *) node;
-
-				APP_JUMB(fs->fieldnum);
-				JumbleExpr(jstate, (Node *) fs->arg);
-			}
-			break;
-		case T_FieldStore:
-			{
-				FieldStore *fstore = (FieldStore *) node;
-
-				JumbleExpr(jstate, (Node *) fstore->arg);
-				JumbleExpr(jstate, (Node *) fstore->newvals);
-			}
-			break;
-		case T_RelabelType:
-			{
-				RelabelType *rt = (RelabelType *) node;
-
-				APP_JUMB(rt->resulttype);
-				JumbleExpr(jstate, (Node *) rt->arg);
-			}
-			break;
-		case T_CoerceViaIO:
-			{
-				CoerceViaIO *cio = (CoerceViaIO *) node;
-
-				APP_JUMB(cio->resulttype);
-				JumbleExpr(jstate, (Node *) cio->arg);
-			}
-			break;
-		case T_ArrayCoerceExpr:
-			{
-				ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node;
-
-				APP_JUMB(acexpr->resulttype);
-				JumbleExpr(jstate, (Node *) acexpr->arg);
-				JumbleExpr(jstate, (Node *) acexpr->elemexpr);
-			}
-			break;
-		case T_ConvertRowtypeExpr:
-			{
-				ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node;
-
-				APP_JUMB(crexpr->resulttype);
-				JumbleExpr(jstate, (Node *) crexpr->arg);
-			}
-			break;
-		case T_CollateExpr:
-			{
-				CollateExpr *ce = (CollateExpr *) node;
-
-				APP_JUMB(ce->collOid);
-				JumbleExpr(jstate, (Node *) ce->arg);
-			}
-			break;
-		case T_CaseExpr:
-			{
-				CaseExpr   *caseexpr = (CaseExpr *) node;
-
-				JumbleExpr(jstate, (Node *) caseexpr->arg);
-				foreach(temp, caseexpr->args)
-				{
-					CaseWhen   *when = lfirst_node(CaseWhen, temp);
-
-					JumbleExpr(jstate, (Node *) when->expr);
-					JumbleExpr(jstate, (Node *) when->result);
-				}
-				JumbleExpr(jstate, (Node *) caseexpr->defresult);
-			}
-			break;
-		case T_CaseTestExpr:
-			{
-				CaseTestExpr *ct = (CaseTestExpr *) node;
-
-				APP_JUMB(ct->typeId);
-			}
-			break;
-		case T_ArrayExpr:
-			JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements);
-			break;
-		case T_RowExpr:
-			JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args);
-			break;
-		case T_RowCompareExpr:
-			{
-				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-
-				APP_JUMB(rcexpr->rctype);
-				JumbleExpr(jstate, (Node *) rcexpr->largs);
-				JumbleExpr(jstate, (Node *) rcexpr->rargs);
-			}
-			break;
-		case T_CoalesceExpr:
-			JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args);
-			break;
-		case T_MinMaxExpr:
-			{
-				MinMaxExpr *mmexpr = (MinMaxExpr *) node;
-
-				APP_JUMB(mmexpr->op);
-				JumbleExpr(jstate, (Node *) mmexpr->args);
-			}
-			break;
-		case T_XmlExpr:
-			{
-				XmlExpr    *xexpr = (XmlExpr *) node;
-
-				APP_JUMB(xexpr->op);
-				JumbleExpr(jstate, (Node *) xexpr->named_args);
-				JumbleExpr(jstate, (Node *) xexpr->args);
-			}
-			break;
-		case T_NullTest:
-			{
-				NullTest   *nt = (NullTest *) node;
-
-				APP_JUMB(nt->nulltesttype);
-				JumbleExpr(jstate, (Node *) nt->arg);
-			}
-			break;
-		case T_BooleanTest:
-			{
-				BooleanTest *bt = (BooleanTest *) node;
-
-				APP_JUMB(bt->booltesttype);
-				JumbleExpr(jstate, (Node *) bt->arg);
-			}
-			break;
-		case T_CoerceToDomain:
-			{
-				CoerceToDomain *cd = (CoerceToDomain *) node;
-
-				APP_JUMB(cd->resulttype);
-				JumbleExpr(jstate, (Node *) cd->arg);
-			}
-			break;
-		case T_CoerceToDomainValue:
-			{
-				CoerceToDomainValue *cdv = (CoerceToDomainValue *) node;
-
-				APP_JUMB(cdv->typeId);
-			}
-			break;
-		case T_SetToDefault:
-			{
-				SetToDefault *sd = (SetToDefault *) node;
-
-				APP_JUMB(sd->typeId);
-			}
-			break;
-		case T_CurrentOfExpr:
-			{
-				CurrentOfExpr *ce = (CurrentOfExpr *) node;
-
-				APP_JUMB(ce->cvarno);
-				if (ce->cursor_name)
-					APP_JUMB_STRING(ce->cursor_name);
-				APP_JUMB(ce->cursor_param);
-			}
-			break;
-		case T_NextValueExpr:
-			{
-				NextValueExpr *nve = (NextValueExpr *) node;
-
-				APP_JUMB(nve->seqid);
-				APP_JUMB(nve->typeId);
-			}
-			break;
-		case T_InferenceElem:
-			{
-				InferenceElem *ie = (InferenceElem *) node;
-
-				APP_JUMB(ie->infercollid);
-				APP_JUMB(ie->inferopclass);
-				JumbleExpr(jstate, ie->expr);
-			}
-			break;
-		case T_TargetEntry:
-			{
-				TargetEntry *tle = (TargetEntry *) node;
-
-				APP_JUMB(tle->resno);
-				APP_JUMB(tle->ressortgroupref);
-				JumbleExpr(jstate, (Node *) tle->expr);
-			}
-			break;
-		case T_RangeTblRef:
-			{
-				RangeTblRef *rtr = (RangeTblRef *) node;
-
-				APP_JUMB(rtr->rtindex);
-			}
-			break;
-		case T_JoinExpr:
-			{
-				JoinExpr   *join = (JoinExpr *) node;
-
-				APP_JUMB(join->jointype);
-				APP_JUMB(join->isNatural);
-				APP_JUMB(join->rtindex);
-				JumbleExpr(jstate, join->larg);
-				JumbleExpr(jstate, join->rarg);
-				JumbleExpr(jstate, join->quals);
-			}
-			break;
-		case T_FromExpr:
-			{
-				FromExpr   *from = (FromExpr *) node;
-
-				JumbleExpr(jstate, (Node *) from->fromlist);
-				JumbleExpr(jstate, from->quals);
-			}
-			break;
-		case T_OnConflictExpr:
-			{
-				OnConflictExpr *conf = (OnConflictExpr *) node;
-
-				APP_JUMB(conf->action);
-				JumbleExpr(jstate, (Node *) conf->arbiterElems);
-				JumbleExpr(jstate, conf->arbiterWhere);
-				JumbleExpr(jstate, (Node *) conf->onConflictSet);
-				JumbleExpr(jstate, conf->onConflictWhere);
-				APP_JUMB(conf->constraint);
-				APP_JUMB(conf->exclRelIndex);
-				JumbleExpr(jstate, (Node *) conf->exclRelTlist);
-			}
-			break;
-		case T_MergeAction:
-			{
-				MergeAction *mergeaction = (MergeAction *) node;
-
-				APP_JUMB(mergeaction->matched);
-				APP_JUMB(mergeaction->commandType);
-				JumbleExpr(jstate, mergeaction->qual);
-				JumbleExpr(jstate, (Node *) mergeaction->targetList);
-			}
-			break;
-		case T_List:
-			foreach(temp, (List *) node)
-			{
-				JumbleExpr(jstate, (Node *) lfirst(temp));
-			}
-			break;
-		case T_IntList:
-			foreach(temp, (List *) node)
-			{
-				APP_JUMB(lfirst_int(temp));
-			}
-			break;
-		case T_SortGroupClause:
-			{
-				SortGroupClause *sgc = (SortGroupClause *) node;
-
-				APP_JUMB(sgc->tleSortGroupRef);
-				APP_JUMB(sgc->eqop);
-				APP_JUMB(sgc->sortop);
-				APP_JUMB(sgc->nulls_first);
-			}
-			break;
-		case T_GroupingSet:
-			{
-				GroupingSet *gsnode = (GroupingSet *) node;
-
-				JumbleExpr(jstate, (Node *) gsnode->content);
-			}
-			break;
-		case T_WindowClause:
-			{
-				WindowClause *wc = (WindowClause *) node;
-
-				APP_JUMB(wc->winref);
-				APP_JUMB(wc->frameOptions);
-				JumbleExpr(jstate, (Node *) wc->partitionClause);
-				JumbleExpr(jstate, (Node *) wc->orderClause);
-				JumbleExpr(jstate, wc->startOffset);
-				JumbleExpr(jstate, wc->endOffset);
-			}
-			break;
-		case T_CommonTableExpr:
-			{
-				CommonTableExpr *cte = (CommonTableExpr *) node;
-
-				/* we store the string name because RTE_CTE RTEs need it */
-				APP_JUMB_STRING(cte->ctename);
-				APP_JUMB(cte->ctematerialized);
-				JumbleQueryInternal(jstate, castNode(Query, cte->ctequery));
-			}
-			break;
-		case T_SetOperationStmt:
-			{
-				SetOperationStmt *setop = (SetOperationStmt *) node;
-
-				APP_JUMB(setop->op);
-				APP_JUMB(setop->all);
-				JumbleExpr(jstate, setop->larg);
-				JumbleExpr(jstate, setop->rarg);
-			}
-			break;
-		case T_RangeTblFunction:
-			{
-				RangeTblFunction *rtfunc = (RangeTblFunction *) node;
-
-				JumbleExpr(jstate, rtfunc->funcexpr);
-			}
-			break;
-		case T_TableFunc:
-			{
-				TableFunc  *tablefunc = (TableFunc *) node;
-
-				JumbleExpr(jstate, tablefunc->docexpr);
-				JumbleExpr(jstate, tablefunc->rowexpr);
-				JumbleExpr(jstate, (Node *) tablefunc->colexprs);
-			}
-			break;
-		case T_TableSampleClause:
-			{
-				TableSampleClause *tsc = (TableSampleClause *) node;
-
-				APP_JUMB(tsc->tsmhandler);
-				JumbleExpr(jstate, (Node *) tsc->args);
-				JumbleExpr(jstate, (Node *) tsc->repeatable);
-			}
-			break;
-		default:
-			/* Only a warning, since we can stumble along anyway */
-			elog(WARNING, "unrecognized node type: %d",
-				 (int) nodeTag(node));
-			break;
-	}
-}
-
 /*
  * Record location of constant within query string of query tree
  * that is currently being walked.
@@ -859,3 +204,155 @@ RecordConstLocation(JumbleState *jstate, int location)
 		jstate->clocations_count++;
 	}
 }
+
+#define JUMBLE_NODE(item) \
+	_jumbleNode(jstate, (Node *) expr->item)
+#define JUMBLE_LOCATION(location) \
+	RecordConstLocation(jstate, expr->location)
+#define JUMBLE_FIELD(item) \
+	AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item))
+#define JUMBLE_FIELD_SINGLE(item) \
+	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
+#define JUMBLE_STRING(str) \
+do { \
+	if (expr->str) \
+		AppendJumble(jstate, (const unsigned char *) (expr->str), strlen(expr->str) + 1); \
+} while(0)
+
+#include "queryjumblefuncs.funcs.c"
+
+static void
+_jumbleNode(JumbleState *jstate, Node *node)
+{
+	Node	   *expr = node;
+
+	if (expr == NULL)
+		return;
+
+	/* Guard against stack overflow due to overly complex expressions */
+	check_stack_depth();
+
+	/*
+	 * We always emit the node's NodeTag, then any additional fields that are
+	 * considered significant, and then we recurse to any child nodes.
+	 */
+	JUMBLE_FIELD(type);
+
+	switch (nodeTag(expr))
+	{
+#include "queryjumblefuncs.switch.c"
+
+		case T_List:
+		case T_IntList:
+		case T_OidList:
+		case T_XidList:
+			_jumbleList(jstate, expr);
+			break;
+
+		case T_RangeTblEntry:
+			_jumbleRangeTblEntry(jstate, expr);
+			break;
+
+		default:
+			/* Only a warning, since we can stumble along anyway */
+			elog(WARNING, "unrecognized node type: %d",
+				 (int) nodeTag(expr));
+			break;
+	}
+
+	/* Special cases */
+	switch (nodeTag(expr))
+	{
+		case T_Param:
+			{
+				Param	   *p = (Param *) node;
+
+				/* Also, track the highest external Param id */
+				if (p->paramkind == PARAM_EXTERN &&
+					p->paramid > jstate->highest_extern_param_id)
+					jstate->highest_extern_param_id = p->paramid;
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+_jumbleList(JumbleState *jstate, Node *node)
+{
+	List	   *expr = (List *) node;
+	ListCell   *l;
+
+	switch (expr->type)
+	{
+		case T_List:
+			foreach(l, expr)
+				_jumbleNode(jstate, lfirst(l));
+			break;
+		case T_IntList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_int(l));
+			break;
+		case T_OidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_oid(l));
+			break;
+		case T_XidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_xid(l));
+			break;
+		default:
+			elog(ERROR, "unrecognized list node type: %d",
+				 (int) expr->type);
+			return;
+	}
+}
+
+static void
+_jumbleRangeTblEntry(JumbleState *jstate, Node *node)
+{
+	RangeTblEntry *expr = (RangeTblEntry *) node;
+
+	JUMBLE_FIELD(rtekind);
+	switch (expr->rtekind)
+	{
+		case RTE_RELATION:
+			JUMBLE_FIELD(relid);
+			JUMBLE_NODE(tablesample);
+			JUMBLE_FIELD(inh);
+			break;
+		case RTE_SUBQUERY:
+			JUMBLE_NODE(subquery);
+			break;
+		case RTE_JOIN:
+			JUMBLE_FIELD(jointype);
+			break;
+		case RTE_FUNCTION:
+			JUMBLE_NODE(functions);
+			break;
+		case RTE_TABLEFUNC:
+			JUMBLE_NODE(tablefunc);
+			break;
+		case RTE_VALUES:
+			JUMBLE_NODE(values_lists);
+			break;
+		case RTE_CTE:
+
+			/*
+			 * Depending on the CTE name here isn't ideal, but it's the only
+			 * info we have to identify the referenced WITH item.
+			 */
+			JUMBLE_STRING(ctename);
+			JUMBLE_FIELD(ctelevelsup);
+			break;
+		case RTE_NAMEDTUPLESTORE:
+			JUMBLE_STRING(enrname);
+			break;
+		case RTE_RESULT:
+			break;
+		default:
+			elog(ERROR, "unrecognized RTE kind: %d", (int) expr->rtekind);
+			break;
+	}
+}
-- 
2.39.0

#11Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Michael Paquier (#10)
Re: Generating code for query jumbling through gen_node_support.pl

On 18.01.23 08:04, Michael Paquier wrote:

On Tue, Jan 17, 2023 at 04:52:28PM +0900, Michael Paquier wrote:

On Tue, Jan 17, 2023 at 08:43:44AM +0100, Peter Eisentraut wrote:

Ok, I understand now, and I agree with this approach over the opposite. I
was confused because the snippet you showed above used "jumble_ignore", but
your patch is correct as it uses "jumble_location".

Okay. I'll refresh the patch set so as we have only "jumble_ignore",
then, like v1, with preparatory patches for what you mentioned and
anything that comes into mind.

This is done as of the patch series v3 attached:
- 0001 reformats all the comments of the nodes.
- 0002 moves the current files for query jumble as of queryjumble.c ->
queryjumblefuncs.c and utils/queryjumble.h -> nodes/queryjumble.h.
- 0003 is the core feature, where I have done a second pass over the
nodes to make sure that things map with HEAD, incorporating the extra
docs coming from v2, adding a bit more.

This patch structure looks good.

That said, the term "jumble" is really weird, because in the sense that we
are using it here it means, approximately, "to mix together", "to unify".
So what we are doing with the Const nodes is really to *not* jumble the
location, but for all other node types we are jumbling the location. At
least that is my understanding.

I am quite familiar with this term, FWIW. That's what we've inherited
from the days where this has been introduced in pg_stat_statements.

I have renamed the node attributes to query_jumble_ignore and
no_query_jumble at the end, after considering Peter's point that only
"jumble" could be fuzzy here. The file names are changed in
consequence.

I see that in the 0003 patch, most location fields now have an explicit
markup with query_jumble_ignore. I thought we had previously resolved
to consider location fields to be automatically ignored unless
explicitly included (like for the Const node). This appears to invert
that? Am I missing something?

#12Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#11)
Re: Generating code for query jumbling through gen_node_support.pl

On Thu, Jan 19, 2023 at 09:42:03AM +0100, Peter Eisentraut wrote:

I see that in the 0003 patch, most location fields now have an explicit
markup with query_jumble_ignore. I thought we had previously resolved to
consider location fields to be automatically ignored unless explicitly
included (like for the Const node). This appears to invert that? Am I
missing something?

My misunderstanding then, I thought that you were OK with what was
part of v1, where all these fields was marked as "ignore". But you
actually prefer v2, with the second field "location" on top of
"ignore". I can update 0003 to refresh that.

Would you be OK if I apply 0001 (with the comments of the locations
still reshaped to ease future property additions) and 0002?
--
Michael

#13Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#11)
4 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Thu, Jan 19, 2023 at 09:42:03AM +0100, Peter Eisentraut wrote:

I see that in the 0003 patch, most location fields now have an explicit
markup with query_jumble_ignore. I thought we had previously resolved to
consider location fields to be automatically ignored unless explicitly
included (like for the Const node). This appears to invert that? Am I
missing something?

As a result, I have rebased the patch set to use the two-attribute
approach: query_jumble_ignore and query_jumble_location.

On top of the three previous patches, I am adding 0004 to implement a
GUC able to switch the computation of the utility statements between
what I am calling "string" to compute the query IDs based on the hash
of the query string and the previous default, or "jumble", to use the
parsed tree, with a few more tests to see the difference. Perhaps it
is not worth bothering, but it could be possible that some users don't
want to pay the penalty of doing the query jumbling with the parsed
tree for utilities, as well..
--
Michael

Attachments:

v4-0001-Rework-format-of-comments-for-nodes.patchtext/x-diff; charset=us-asciiDownload
From 43a5bdffdb9dbe3ecb40e9bf8a5f0e9f12ceff32 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 18 Jan 2023 14:26:12 +0900
Subject: [PATCH v4 1/4] Rework format of comments for nodes

This is similar to 835d476, except that this one is to add node
properties related to query jumbling.
---
 src/include/nodes/parsenodes.h | 261 ++++++++++++------
 src/include/nodes/plannodes.h  |   3 +-
 src/include/nodes/primnodes.h  | 469 ++++++++++++++++++++++-----------
 3 files changed, 487 insertions(+), 246 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cfeca96d53..eec51e3ee2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -123,7 +123,8 @@ typedef struct Query
 
 	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
 
-	QuerySource querySource;	/* where did I come from? */
+	/* where did I come from? */
+	QuerySource querySource;
 
 	/*
 	 * query identifier (can be set by plugins); ignored for equal, as it
@@ -131,39 +132,58 @@ typedef struct Query
 	 */
 	uint64		queryId pg_node_attr(equal_ignore, read_write_ignore, read_as(0));
 
-	bool		canSetTag;		/* do I set the command result tag? */
+	/* do I set the command result tag? */
+	bool		canSetTag;
 
 	Node	   *utilityStmt;	/* non-null if commandType == CMD_UTILITY */
 
-	int			resultRelation; /* rtable index of target relation for
-								 * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
+	/*
+	 * rtable index of target relation for INSERT/UPDATE/DELETE/MERGE; 0 for
+	 * SELECT.
+	 */
+	int			resultRelation;
 
-	bool		hasAggs;		/* has aggregates in tlist or havingQual */
-	bool		hasWindowFuncs; /* has window functions in tlist */
-	bool		hasTargetSRFs;	/* has set-returning functions in tlist */
-	bool		hasSubLinks;	/* has subquery SubLink */
-	bool		hasDistinctOn;	/* distinctClause is from DISTINCT ON */
-	bool		hasRecursive;	/* WITH RECURSIVE was specified */
-	bool		hasModifyingCTE;	/* has INSERT/UPDATE/DELETE in WITH */
-	bool		hasForUpdate;	/* FOR [KEY] UPDATE/SHARE was specified */
-	bool		hasRowSecurity; /* rewriter has applied some RLS policy */
-
-	bool		isReturn;		/* is a RETURN statement */
+	/* has aggregates in tlist or havingQual */
+	bool		hasAggs;
+	/* has window functions in tlist */
+	bool		hasWindowFuncs;
+	/* has set-returning functions in tlist */
+	bool		hasTargetSRFs;
+	/* has subquery SubLink */
+	bool		hasSubLinks;
+	/* distinctClause is from DISTINCT ON */
+	bool		hasDistinctOn;
+	/* WITH RECURSIVE was specified */
+	bool		hasRecursive;
+	/* has INSERT/UPDATE/DELETE in WITH */
+	bool		hasModifyingCTE;
+	/* FOR [KEY] UPDATE/SHARE was specified */
+	bool		hasForUpdate;
+	/* rewriter has applied some RLS policy */
+	bool		hasRowSecurity;
+	/* is a RETURN statement */
+	bool		isReturn;
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
-	List	   *rteperminfos;	/* list of RTEPermissionInfo nodes for the
-								 * rtable entries having perminfoindex > 0 */
+
+	/*
+	 * list of RTEPermissionInfo nodes for the rtable entries having
+	 * perminfoindex > 0
+	 */
+	List	   *rteperminfos;
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
 	List	   *mergeActionList;	/* list of actions for MERGE (only) */
-	bool		mergeUseOuterJoin;	/* whether to use outer join */
+	/* whether to use outer join */
+	bool		mergeUseOuterJoin;
 
 	List	   *targetList;		/* target list (of TargetEntry) */
 
-	OverridingKind override;	/* OVERRIDING clause */
+	/* OVERRIDING clause */
+	OverridingKind override;
 
 	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
 
@@ -191,11 +211,14 @@ typedef struct Query
 	Node	   *setOperations;	/* set-operation tree if this is top level of
 								 * a UNION/INTERSECT/EXCEPT query */
 
-	List	   *constraintDeps; /* a list of pg_constraint OIDs that the query
-								 * depends on to be semantically valid */
+	/*
+	 * A list of pg_constraint OIDs that the query depends on to be
+	 * semantically valid
+	 */
+	List	   *constraintDeps;
 
-	List	   *withCheckOptions;	/* a list of WithCheckOption's (added
-									 * during rewrite) */
+	/* a list of WithCheckOption's (added during rewrite) */
+	List	   *withCheckOptions;
 
 	/*
 	 * The following two fields identify the portion of the source text string
@@ -203,8 +226,10 @@ typedef struct Query
 	 * Queries, not in sub-queries.  When not set, they might both be zero, or
 	 * both be -1 meaning "unknown".
 	 */
-	int			stmt_location;	/* start location, or -1 if unknown */
-	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
+	/* start location, or -1 if unknown */
+	int			stmt_location;
+	/* length in bytes; 0 means "rest of string" */
+	int			stmt_len;
 } Query;
 
 
@@ -239,7 +264,8 @@ typedef struct TypeName
 	List	   *typmods;		/* type modifier expression(s) */
 	int32		typemod;		/* prespecified type modifier */
 	List	   *arrayBounds;	/* array bounds */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } TypeName;
 
 /*
@@ -259,7 +285,8 @@ typedef struct ColumnRef
 {
 	NodeTag		type;
 	List	   *fields;			/* field names (String nodes) or A_Star */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } ColumnRef;
 
 /*
@@ -269,7 +296,8 @@ typedef struct ParamRef
 {
 	NodeTag		type;
 	int			number;			/* the number of the parameter */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } ParamRef;
 
 /*
@@ -302,7 +330,8 @@ typedef struct A_Expr
 	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 */
+	/* token location, or -1 if unknown */
+	int			location;
 } A_Expr;
 
 /*
@@ -328,7 +357,8 @@ typedef struct A_Const
 	NodeTag		type;
 	union ValUnion val;
 	bool		isnull;			/* SQL NULL constant */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } A_Const;
 
 /*
@@ -339,7 +369,8 @@ typedef struct TypeCast
 	NodeTag		type;
 	Node	   *arg;			/* the expression being casted */
 	TypeName   *typeName;		/* the target type */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } TypeCast;
 
 /*
@@ -350,7 +381,8 @@ typedef struct CollateClause
 	NodeTag		type;
 	Node	   *arg;			/* input expression */
 	List	   *collname;		/* possibly-qualified collation name */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } CollateClause;
 
 /*
@@ -370,7 +402,8 @@ typedef struct RoleSpec
 	NodeTag		type;
 	RoleSpecType roletype;		/* Type of this rolespec */
 	char	   *rolename;		/* filled only for ROLESPEC_CSTRING */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } RoleSpec;
 
 /*
@@ -400,7 +433,8 @@ typedef struct FuncCall
 	bool		agg_distinct;	/* arguments were labeled DISTINCT */
 	bool		func_variadic;	/* last argument was labeled VARIADIC */
 	CoercionForm funcformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } FuncCall;
 
 /*
@@ -457,7 +491,8 @@ typedef struct A_ArrayExpr
 {
 	NodeTag		type;
 	List	   *elements;		/* array element expressions */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } A_ArrayExpr;
 
 /*
@@ -484,7 +519,8 @@ typedef struct ResTarget
 	char	   *name;			/* column name or NULL */
 	List	   *indirection;	/* subscripts, field names, and '*', or NIL */
 	Node	   *val;			/* the value expression to compute or assign */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } ResTarget;
 
 /*
@@ -514,7 +550,8 @@ typedef struct SortBy
 	SortByDir	sortby_dir;		/* ASC/DESC/USING/default */
 	SortByNulls sortby_nulls;	/* NULLS FIRST/LAST */
 	List	   *useOp;			/* name of op to use, if SORTBY_USING */
-	int			location;		/* operator location, or -1 if none/unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } SortBy;
 
 /*
@@ -535,7 +572,8 @@ typedef struct WindowDef
 	int			frameOptions;	/* frame_clause options, see below */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
-	int			location;		/* parse location, or -1 if none/unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } WindowDef;
 
 /*
@@ -625,7 +663,8 @@ typedef struct RangeTableFunc
 	List	   *namespaces;		/* list of namespaces as ResTarget */
 	List	   *columns;		/* list of RangeTableFuncCol */
 	Alias	   *alias;			/* table alias & optional column aliases */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } RangeTableFunc;
 
 /*
@@ -643,7 +682,8 @@ typedef struct RangeTableFuncCol
 	bool		is_not_null;	/* does it have NOT NULL? */
 	Node	   *colexpr;		/* column filter expression */
 	Node	   *coldefexpr;		/* column default value expression */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } RangeTableFuncCol;
 
 /*
@@ -663,7 +703,8 @@ typedef struct RangeTableSample
 	List	   *method;			/* sampling method name (possibly qualified) */
 	List	   *args;			/* argument(s) for sampling method */
 	Node	   *repeatable;		/* REPEATABLE expression, or NULL if none */
-	int			location;		/* method name location, or -1 if unknown */
+	/* method name location, or -1 if unknown */
+	int			location;
 } RangeTableSample;
 
 /*
@@ -706,7 +747,8 @@ typedef struct ColumnDef
 	Oid			collOid;		/* collation OID (InvalidOid if not set) */
 	List	   *constraints;	/* other constraints on column */
 	List	   *fdwoptions;		/* per-column FDW options */
-	int			location;		/* parse location, or -1 if none/unknown */
+	/* parse location, or -1 if unknown */
+	int			location;
 } ColumnDef;
 
 /*
@@ -780,7 +822,8 @@ typedef struct DefElem
 	Node	   *arg;			/* typically Integer, Float, String, or
 								 * TypeName */
 	DefElemAction defaction;	/* unspecified action, or SET/ADD/DROP */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } DefElem;
 
 /*
@@ -809,7 +852,8 @@ typedef struct XmlSerialize
 	XmlOptionType xmloption;	/* DOCUMENT or CONTENT */
 	Node	   *expr;
 	TypeName   *typeName;
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } XmlSerialize;
 
 /* Partitioning related definitions */
@@ -827,7 +871,8 @@ typedef struct PartitionElem
 	Node	   *expr;			/* expression to partition on, or NULL */
 	List	   *collation;		/* name of collation; NIL = default */
 	List	   *opclass;		/* name of desired opclass; NIL = default */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } PartitionElem;
 
 typedef enum PartitionStrategy
@@ -847,7 +892,8 @@ typedef struct PartitionSpec
 	NodeTag		type;
 	PartitionStrategy strategy;
 	List	   *partParams;		/* List of PartitionElems */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } PartitionSpec;
 
 /*
@@ -874,7 +920,8 @@ struct PartitionBoundSpec
 	List	   *lowerdatums;	/* List of PartitionRangeDatums */
 	List	   *upperdatums;	/* List of PartitionRangeDatums */
 
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 };
 
 /*
@@ -897,7 +944,8 @@ typedef struct PartitionRangeDatum
 	Node	   *value;			/* Const (or A_Const in raw tree), if kind is
 								 * PARTITION_RANGE_DATUM_VALUE, else NULL */
 
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } PartitionRangeDatum;
 
 /*
@@ -1223,14 +1271,21 @@ typedef struct RangeTblFunction
 	NodeTag		type;
 
 	Node	   *funcexpr;		/* expression tree for func call */
-	int			funccolcount;	/* number of columns it contributes to RTE */
+	/* number of columns it contributes to RTE */
+	int			funccolcount;
 	/* These fields record the contents of a column definition list, if any: */
-	List	   *funccolnames;	/* column names (list of String) */
-	List	   *funccoltypes;	/* OID list of column type OIDs */
-	List	   *funccoltypmods; /* integer list of column typmods */
-	List	   *funccolcollations;	/* OID list of column collation OIDs */
+	/* column names (list of String) */
+	List	   *funccolnames;
+	/* OID list of column type OIDs */
+	List	   *funccoltypes;
+	/* integer list of column typmods */
+	List	   *funccoltypmods;
+	/* OID list of column collation OIDs */
+	List	   *funccolcollations;
+
 	/* This is set during planning for use by the executor: */
-	Bitmapset  *funcparams;		/* PARAM_EXEC Param IDs affecting this func */
+	/* PARAM_EXEC Param IDs affecting this func */
+	Bitmapset  *funcparams;
 } RangeTblFunction;
 
 /*
@@ -1337,7 +1392,8 @@ typedef struct SortGroupClause
 	Oid			eqop;			/* the equality operator ('=' op) */
 	Oid			sortop;			/* the ordering operator ('<' op), or 0 */
 	bool		nulls_first;	/* do NULLs come before normal values? */
-	bool		hashable;		/* can eqop be implemented by hashing? */
+	/* can eqop be implemented by hashing? */
+	bool		hashable;
 } SortGroupClause;
 
 /*
@@ -1427,21 +1483,31 @@ typedef struct GroupingSet
 typedef struct WindowClause
 {
 	NodeTag		type;
-	char	   *name;			/* window name (NULL in an OVER clause) */
-	char	   *refname;		/* referenced window name, if any */
+	/* window name (NULL in an OVER clause) */
+	char	   *name;
+	/* referenced window name, if any */
+	char	   *refname;
 	List	   *partitionClause;	/* PARTITION BY list */
-	List	   *orderClause;	/* ORDER BY list */
+	/* ORDER BY list */
+	List	   *orderClause;
 	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 */
-	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? */
+	/* qual to help short-circuit execution */
+	List	   *runCondition;
+	/* 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;
 	Index		winref;			/* ID referenced by window functions */
-	bool		copiedOrder;	/* did we copy orderClause from refname? */
+	/* did we copy orderClause from refname? */
+	bool		copiedOrder;
 } WindowClause;
 
 /*
@@ -1477,7 +1543,8 @@ typedef struct WithClause
 	NodeTag		type;
 	List	   *ctes;			/* list of CommonTableExprs */
 	bool		recursive;		/* true = WITH RECURSIVE */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } WithClause;
 
 /*
@@ -1492,7 +1559,8 @@ typedef struct InferClause
 	List	   *indexElems;		/* IndexElems to infer unique index */
 	Node	   *whereClause;	/* qualification (partial-index predicate) */
 	char	   *conname;		/* Constraint name, or NULL if unnamed */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } InferClause;
 
 /*
@@ -1508,7 +1576,8 @@ typedef struct OnConflictClause
 	InferClause *infer;			/* Optional index inference clause */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	Node	   *whereClause;	/* qualifications */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } OnConflictClause;
 
 /*
@@ -1558,15 +1627,25 @@ typedef struct CommonTableExpr
 	Node	   *ctequery;		/* the CTE's subquery */
 	CTESearchClause *search_clause;
 	CTECycleClause *cycle_clause;
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 	/* These fields are set during parse analysis: */
-	bool		cterecursive;	/* is this CTE actually recursive? */
-	int			cterefcount;	/* number of RTEs referencing this CTE
-								 * (excluding internal self-references) */
-	List	   *ctecolnames;	/* list of output column names */
-	List	   *ctecoltypes;	/* OID list of output column type OIDs */
-	List	   *ctecoltypmods;	/* integer list of output column typmods */
-	List	   *ctecolcollations;	/* OID list of column collation OIDs */
+	/* is this CTE actually recursive? */
+	bool		cterecursive;
+
+	/*
+	 * Number of RTEs referencing this CTE (excluding internal
+	 * self-references)
+	 */
+	int			cterefcount;
+	/* list of output column names */
+	List	   *ctecolnames;
+	/* OID list of output column type OIDs */
+	List	   *ctecoltypes;
+	/* integer list of output column typmods */
+	List	   *ctecoltypmods;
+	/* OID list of column collation OIDs */
+	List	   *ctecolcollations;
 } CommonTableExpr;
 
 /* Convenience macro to get the output tlist of a CTE's query */
@@ -1603,10 +1682,12 @@ typedef struct MergeAction
 	NodeTag		type;
 	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
 	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
-	OverridingKind override;	/* OVERRIDING clause */
+	/* OVERRIDING clause */
+	OverridingKind override;
 	Node	   *qual;			/* transformed WHEN conditions */
 	List	   *targetList;		/* the target list (of TargetEntry) */
-	List	   *updateColnos;	/* target attribute numbers of an UPDATE */
+	/* target attribute numbers of an UPDATE */
+	List	   *updateColnos;
 } MergeAction;
 
 /*
@@ -1645,7 +1726,8 @@ typedef struct RawStmt
 {
 	NodeTag		type;
 	Node	   *stmt;			/* raw parse tree */
-	int			stmt_location;	/* start location, or -1 if unknown */
+	/* start location, or -1 if unknown */
+	int			stmt_location;
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } RawStmt;
 
@@ -1816,10 +1898,14 @@ typedef struct SetOperationStmt
 	/* Eventually add fields for CORRESPONDING spec here */
 
 	/* Fields derived during parse analysis: */
-	List	   *colTypes;		/* OID list of output column type OIDs */
-	List	   *colTypmods;		/* integer list of output column typmods */
-	List	   *colCollations;	/* OID list of output column collation OIDs */
-	List	   *groupClauses;	/* a list of SortGroupClause's */
+	/* OID list of output column type OIDs */
+	List	   *colTypes;
+	/* integer list of output column typmods */
+	List	   *colTypmods;
+	/* OID list of output column collation OIDs */
+	List	   *colCollations;
+	/* a list of SortGroupClause's */
+	List	   *groupClauses;
 	/* groupClauses is NIL if UNION ALL, but must be set otherwise */
 } SetOperationStmt;
 
@@ -1849,7 +1935,8 @@ typedef struct PLAssignStmt
 	List	   *indirection;	/* subscripts and field names, if any */
 	int			nnames;			/* number of names to use in ColumnRef */
 	SelectStmt *val;			/* the PL/pgSQL expression to assign */
-	int			location;		/* name's token location, or -1 if unknown */
+	/* name's token location, or -1 if unknown */
+	int			location;
 } PLAssignStmt;
 
 
@@ -2356,7 +2443,8 @@ typedef struct Constraint
 	char	   *conname;		/* Constraint name, or NULL if unnamed */
 	bool		deferrable;		/* DEFERRABLE? */
 	bool		initdeferred;	/* INITIALLY DEFERRED? */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 
 	/* Fields used for constraints with expressions (CHECK and DEFAULT): */
 	bool		is_no_inherit;	/* is constraint non-inheritable? */
@@ -3765,7 +3853,8 @@ typedef struct PublicationObjSpec
 	PublicationObjSpecType pubobjtype;	/* type of this publication object */
 	char	   *name;
 	PublicationTable *pubtable;
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } PublicationObjSpec;
 
 typedef struct CreatePublicationStmt
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index c1234fcf36..f33d7fe167 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -99,7 +99,8 @@ typedef struct PlannedStmt
 	Node	   *utilityStmt;	/* non-null if this is utility stmt */
 
 	/* statement location in source string (copied from Query) */
-	int			stmt_location;	/* start location, or -1 if unknown */
+	/* start location, or -1 if unknown */
+	int			stmt_location;
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } PlannedStmt;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 83e40e56d3..8d80c90ce7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -98,19 +98,32 @@ typedef struct RangeVar
 typedef struct TableFunc
 {
 	NodeTag		type;
-	List	   *ns_uris;		/* list of namespace URI expressions */
-	List	   *ns_names;		/* list of namespace names or NULL */
-	Node	   *docexpr;		/* input document expression */
-	Node	   *rowexpr;		/* row filter expression */
-	List	   *colnames;		/* column names (list of String) */
-	List	   *coltypes;		/* OID list of column type OIDs */
-	List	   *coltypmods;		/* integer list of column typmods */
-	List	   *colcollations;	/* OID list of column collation OIDs */
-	List	   *colexprs;		/* list of column filter expressions */
-	List	   *coldefexprs;	/* list of column default expressions */
-	Bitmapset  *notnulls;		/* nullability flag for each output column */
-	int			ordinalitycol;	/* counts from 0; -1 if none specified */
-	int			location;		/* token location, or -1 if unknown */
+	/* list of namespace URI expressions */
+	List	   *ns_uris;
+	/* list of namespace names or NULL */
+	List	   *ns_names;
+	/* input document expression */
+	Node	   *docexpr;
+	/* row filter expression */
+	Node	   *rowexpr;
+	/* column names (list of String) */
+	List	   *colnames;
+	/* OID list of column type OIDs */
+	List	   *coltypes;
+	/* integer list of column typmods */
+	List	   *coltypmods;
+	/* OID list of column collation OIDs */
+	List	   *colcollations;
+	/* list of column filter expressions */
+	List	   *colexprs;
+	/* list of column default expressions */
+	List	   *coldefexprs;
+	/* nullability flag for each output column */
+	Bitmapset  *notnulls;
+	/* counts from 0; -1 if none specified */
+	int			ordinalitycol;
+	/* token location, or -1 if unknown */
+	int			location;
 } TableFunc;
 
 /*
@@ -256,18 +269,27 @@ 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 */
-	Oid			constcollid;	/* OID of collation, or InvalidOid if none */
-	int			constlen;		/* typlen of the constant's datatype */
-	Datum		constvalue;		/* the constant's value */
-	bool		constisnull;	/* whether the constant is null (if true,
-								 * constvalue is undefined) */
-	bool		constbyval;		/* whether this datatype is passed by value.
-								 * If true, then all the information is stored
-								 * in the Datum. If false, then the Datum
-								 * contains a pointer to the information. */
-	int			location;		/* token location, or -1 if unknown */
+	/* pg_type OID of the constant's datatype */
+	Oid			consttype;
+	/* typmod value, if any */
+	int32		consttypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			constcollid;
+	/* typlen of the constant's datatype */
+	int			constlen;
+	/* the constant's value */
+	Datum		constvalue;
+	/* whether the constant is null (if true, constvalue is undefined) */
+	bool		constisnull;
+
+	/*
+	 * Whether this datatype is passed by value.  If true, then all the
+	 * information is stored in the Datum.  If false, then the Datum contains
+	 * a pointer to the information.
+	 */
+	bool		constbyval;
+	/* token location, or -1 if unknown */
+	int			location;
 } Const;
 
 /*
@@ -311,9 +333,12 @@ typedef struct Param
 	ParamKind	paramkind;		/* kind of parameter. See above */
 	int			paramid;		/* numeric ID for parameter */
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
-	int32		paramtypmod;	/* typmod value, if known */
-	Oid			paramcollid;	/* OID of collation, or InvalidOid if none */
-	int			location;		/* token location, or -1 if unknown */
+	/* typmod value, if known */
+	int32		paramtypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			paramcollid;
+	/* token location, or -1 if unknown */
+	int			location;
 } Param;
 
 /*
@@ -486,16 +511,26 @@ typedef struct GroupingFunc
 typedef struct WindowFunc
 {
 	Expr		xpr;
-	Oid			winfnoid;		/* pg_proc Oid of the function */
-	Oid			wintype;		/* type Oid of result of the window function */
-	Oid			wincollid;		/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
-	List	   *args;			/* arguments to the window function */
-	Expr	   *aggfilter;		/* FILTER expression, if any */
-	Index		winref;			/* index of associated WindowClause */
-	bool		winstar;		/* true if argument list was really '*' */
-	bool		winagg;			/* is function a simple aggregate? */
-	int			location;		/* token location, or -1 if unknown */
+	/* pg_proc Oid of the function */
+	Oid			winfnoid;
+	/* type Oid of result of the window function */
+	Oid			wintype;
+	/* OID of collation of result */
+	Oid			wincollid;
+	/* OID of collation that function should use */
+	Oid			inputcollid;
+	/* arguments to the window function */
+	List	   *args;
+	/* FILTER expression, if any */
+	Expr	   *aggfilter;
+	/* index of associated WindowClause */
+	Index		winref;
+	/* true if argument list was really '*' */
+	bool		winstar;
+	/* is function a simple aggregate? */
+	bool		winagg;
+	/* token location, or -1 if unknown */
+	int			location;
 } WindowFunc;
 
 /*
@@ -539,20 +574,28 @@ typedef struct WindowFunc
 typedef struct SubscriptingRef
 {
 	Expr		xpr;
-	Oid			refcontainertype;	/* type of the container proper */
-	Oid			refelemtype;	/* the container type's pg_type.typelem */
-	Oid			refrestype;		/* type of the SubscriptingRef's result */
-	int32		reftypmod;		/* typmod of the result */
-	Oid			refcollid;		/* collation of result, or InvalidOid if none */
-	List	   *refupperindexpr;	/* expressions that evaluate to upper
-									 * container indexes */
-	List	   *reflowerindexpr;	/* expressions that evaluate to lower
-									 * container indexes, or NIL for single
-									 * container element */
-	Expr	   *refexpr;		/* the expression that evaluates to a
-								 * container value */
-	Expr	   *refassgnexpr;	/* expression for the source value, or NULL if
-								 * fetch */
+	/* type of the container proper */
+	Oid			refcontainertype;
+	/* the container type's pg_type.typelem */
+	Oid			refelemtype;
+	/* type of the SubscriptingRef's result */
+	Oid			refrestype;
+	/* typmod of the result */
+	int32		reftypmod;
+	/* collation of result, or InvalidOid if none */
+	Oid			refcollid;
+	/* expressions that evaluate to upper container indexes */
+	List	   *refupperindexpr;
+
+	/*
+	 * expressions that evaluate to lower container indexes, or NIL for single
+	 * container element.
+	 */
+	List	   *reflowerindexpr;
+	/* the expression that evaluates to a container value */
+	Expr	   *refexpr;
+	/* expression for the source value, or NULL if fetch */
+	Expr	   *refassgnexpr;
 } SubscriptingRef;
 
 /*
@@ -595,16 +638,28 @@ typedef enum CoercionForm
 typedef struct FuncExpr
 {
 	Expr		xpr;
-	Oid			funcid;			/* PG_PROC OID of the function */
-	Oid			funcresulttype; /* PG_TYPE OID of result value */
-	bool		funcretset;		/* true if function returns set */
-	bool		funcvariadic;	/* true if variadic arguments have been
-								 * combined into an array last argument */
-	CoercionForm funcformat;	/* how to display this function call */
-	Oid			funccollid;		/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
-	List	   *args;			/* arguments to the function */
-	int			location;		/* token location, or -1 if unknown */
+	/* PG_PROC OID of the function */
+	Oid			funcid;
+	/* PG_TYPE OID of result value */
+	Oid			funcresulttype;
+	/* true if function returns set */
+	bool		funcretset;
+
+	/*
+	 * true if variadic arguments have been combined into an array last
+	 * argument
+	 */
+	bool		funcvariadic;
+	/* how to display this function call */
+	CoercionForm funcformat;
+	/* OID of collation of result */
+	Oid			funccollid;
+	/* OID of collation that function should use */
+	Oid			inputcollid;
+	/* arguments to the function */
+	List	   *args;
+	/* token location, or -1 if unknown */
+	int			location;
 } FuncExpr;
 
 /*
@@ -624,10 +679,14 @@ typedef struct FuncExpr
 typedef struct NamedArgExpr
 {
 	Expr		xpr;
-	Expr	   *arg;			/* the argument expression */
-	char	   *name;			/* the name */
-	int			argnumber;		/* argument's number in positional notation */
-	int			location;		/* argument name location, or -1 if unknown */
+	/* the argument expression */
+	Expr	   *arg;
+	/* the name */
+	char	   *name;
+	/* argument's number in positional notation */
+	int			argnumber;
+	/* argument name location, or -1 if unknown */
+	int			location;
 } NamedArgExpr;
 
 /*
@@ -765,7 +824,8 @@ typedef struct BoolExpr
 	Expr		xpr;
 	BoolExprType boolop;
 	List	   *args;			/* arguments to this expression */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } BoolExpr;
 
 /*
@@ -838,9 +898,12 @@ typedef struct SubLink
 	SubLinkType subLinkType;	/* see above */
 	int			subLinkId;		/* ID (1..n); 0 if not MULTIEXPR */
 	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
-	List	   *operName;		/* originally specified operator name */
-	Node	   *subselect;		/* subselect as Query* or raw parsetree */
-	int			location;		/* token location, or -1 if unknown */
+	/* originally specified operator name */
+	List	   *operName;
+	/* subselect as Query* or raw parsetree */
+	Node	   *subselect;
+	/* token location, or -1 if unknown */
+	int			location;
 } SubLink;
 
 /*
@@ -948,10 +1011,12 @@ typedef struct FieldSelect
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	AttrNumber	fieldnum;		/* attribute number of field to extract */
-	Oid			resulttype;		/* type of the field (result type of this
-								 * node) */
-	int32		resulttypmod;	/* output typmod (usually -1) */
-	Oid			resultcollid;	/* OID of collation of the field */
+	/* type of the field (result type of this node) */
+	Oid			resulttype;
+	/* output typmod (usually -1) */
+	int32		resulttypmod;
+	/* OID of collation of the field */
+	Oid			resultcollid;
 } FieldSelect;
 
 /* ----------------
@@ -977,8 +1042,10 @@ typedef struct FieldStore
 	Expr		xpr;
 	Expr	   *arg;			/* input tuple value */
 	List	   *newvals;		/* new value(s) for field(s) */
-	List	   *fieldnums;		/* integer list of field attnums */
-	Oid			resulttype;		/* type of result (same as type of arg) */
+	/* integer list of field attnums */
+	List	   *fieldnums;
+	/* type of result (same as type of arg) */
+	Oid			resulttype;
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 } FieldStore;
 
@@ -1000,10 +1067,14 @@ typedef struct RelabelType
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion expression */
-	int32		resulttypmod;	/* output typmod (usually -1) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm relabelformat; /* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* output typmod (usually -1) */
+	int32		resulttypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			resultcollid;
+	/* how to display this node */
+	CoercionForm relabelformat;
+	/* token location, or -1 if unknown */
+	int			location;
 } RelabelType;
 
 /* ----------------
@@ -1021,9 +1092,12 @@ typedef struct CoerceViaIO
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion */
 	/* output typmod is not stored, but is presumed -1 */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coerceformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* OID of collation, or InvalidOid if none */
+	Oid			resultcollid;
+	/* how to display this node */
+	CoercionForm coerceformat;
+	/* token location, or -1 if unknown */
+	int			location;
 } CoerceViaIO;
 
 /* ----------------
@@ -1045,10 +1119,14 @@ typedef struct ArrayCoerceExpr
 	Expr	   *arg;			/* input expression (yields an array) */
 	Expr	   *elemexpr;		/* expression representing per-element work */
 	Oid			resulttype;		/* output type of coercion (an array type) */
-	int32		resulttypmod;	/* output typmod (also element typmod) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coerceformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* output typmod (also element typmod) */
+	int32		resulttypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			resultcollid;
+	/* how to display this node */
+	CoercionForm coerceformat;
+	/* token location, or -1 if unknown */
+	int			location;
 } ArrayCoerceExpr;
 
 /* ----------------
@@ -1070,8 +1148,10 @@ typedef struct ConvertRowtypeExpr
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type (always a composite type) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
-	CoercionForm convertformat; /* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* how to display this node */
+	CoercionForm convertformat;
+	/* token location, or -1 if unknown */
+	int			location;
 } ConvertRowtypeExpr;
 
 /*----------
@@ -1086,7 +1166,8 @@ typedef struct CollateExpr
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			collOid;		/* collation's OID */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } CollateExpr;
 
 /*----------
@@ -1114,12 +1195,15 @@ typedef struct CollateExpr
 typedef struct CaseExpr
 {
 	Expr		xpr;
-	Oid			casetype;		/* type of expression result */
-	Oid			casecollid;		/* OID of collation, or InvalidOid if none */
+	/* type of expression result */
+	Oid			casetype;
+	/* OID of collation, or InvalidOid if none */
+	Oid			casecollid;
 	Expr	   *arg;			/* implicit equality comparison argument */
 	List	   *args;			/* the arguments (list of WHEN clauses) */
 	Expr	   *defresult;		/* the default result (ELSE clause) */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } CaseExpr;
 
 /*
@@ -1130,7 +1214,8 @@ typedef struct CaseWhen
 	Expr		xpr;
 	Expr	   *expr;			/* condition expression */
 	Expr	   *result;			/* substitution result */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } CaseWhen;
 
 /*
@@ -1157,8 +1242,10 @@ typedef struct CaseTestExpr
 {
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
+	/* typemod for substituted value */
+	int32		typeMod;
+	/* collation for the substituted value */
+	Oid			collation;
 } CaseTestExpr;
 
 /*
@@ -1172,12 +1259,18 @@ typedef struct CaseTestExpr
 typedef struct ArrayExpr
 {
 	Expr		xpr;
-	Oid			array_typeid;	/* type of expression result */
-	Oid			array_collid;	/* OID of collation, or InvalidOid if none */
-	Oid			element_typeid; /* common type of array elements */
-	List	   *elements;		/* the array elements or sub-arrays */
-	bool		multidims;		/* true if elements are sub-arrays */
-	int			location;		/* token location, or -1 if unknown */
+	/* type of expression result */
+	Oid			array_typeid;
+	/* OID of collation, or InvalidOid if none */
+	Oid			array_collid;
+	/* common type of array elements */
+	Oid			element_typeid;
+	/* the array elements or sub-arrays */
+	List	   *elements;
+	/* true if elements are sub-arrays */
+	bool		multidims;
+	/* token location, or -1 if unknown */
+	int			location;
 } ArrayExpr;
 
 /*
@@ -1205,7 +1298,9 @@ typedef struct RowExpr
 {
 	Expr		xpr;
 	List	   *args;			/* the fields */
-	Oid			row_typeid;		/* RECORDOID or a composite type's ID */
+
+	/* RECORDOID or a composite type's ID */
+	Oid			row_typeid;
 
 	/*
 	 * row_typeid cannot be a domain over composite, only plain composite.  To
@@ -1219,9 +1314,15 @@ typedef struct RowExpr
 	 * We don't need to store a collation either.  The result type is
 	 * necessarily composite, and composite types never have a collation.
 	 */
-	CoercionForm row_format;	/* how to display this node */
-	List	   *colnames;		/* list of String, or NIL */
-	int			location;		/* token location, or -1 if unknown */
+
+	/* how to display this node */
+	CoercionForm row_format;
+
+	/* list of String, or NIL */
+	List	   *colnames;
+
+	/* token location, or -1 if unknown */
+	int			location;
 } RowExpr;
 
 /*
@@ -1252,12 +1353,19 @@ typedef enum RowCompareType
 typedef struct RowCompareExpr
 {
 	Expr		xpr;
-	RowCompareType rctype;		/* LT LE GE or GT, never EQ or NE */
-	List	   *opnos;			/* OID list of pairwise comparison ops */
-	List	   *opfamilies;		/* OID list of containing operator families */
-	List	   *inputcollids;	/* OID list of collations for comparisons */
-	List	   *largs;			/* the left-hand input arguments */
-	List	   *rargs;			/* the right-hand input arguments */
+
+	/* LT LE GE or GT, never EQ or NE */
+	RowCompareType rctype;
+	/* OID list of pairwise comparison ops */
+	List	   *opnos;
+	/* OID list of containing operator families */
+	List	   *opfamilies;
+	/* OID list of collations for comparisons */
+	List	   *inputcollids;
+	/* the left-hand input arguments */
+	List	   *largs;
+	/* the right-hand input arguments */
+	List	   *rargs;
 } RowCompareExpr;
 
 /*
@@ -1266,10 +1374,14 @@ typedef struct RowCompareExpr
 typedef struct CoalesceExpr
 {
 	Expr		xpr;
-	Oid			coalescetype;	/* type of expression result */
-	Oid			coalescecollid; /* OID of collation, or InvalidOid if none */
-	List	   *args;			/* the arguments */
-	int			location;		/* token location, or -1 if unknown */
+	/* type of expression result */
+	Oid			coalescetype;
+	/* OID of collation, or InvalidOid if none */
+	Oid			coalescecollid;
+	/* the arguments */
+	List	   *args;
+	/* token location, or -1 if unknown */
+	int			location;
 } CoalesceExpr;
 
 /*
@@ -1284,12 +1396,18 @@ typedef enum MinMaxOp
 typedef struct MinMaxExpr
 {
 	Expr		xpr;
-	Oid			minmaxtype;		/* common type of arguments and result */
-	Oid			minmaxcollid;	/* OID of collation of result */
-	Oid			inputcollid;	/* OID of collation that function should use */
-	MinMaxOp	op;				/* function to execute */
-	List	   *args;			/* the arguments */
-	int			location;		/* token location, or -1 if unknown */
+	/* common type of arguments and result */
+	Oid			minmaxtype;
+	/* OID of collation of result */
+	Oid			minmaxcollid;
+	/* OID of collation that function should use */
+	Oid			inputcollid;
+	/* function to execute */
+	MinMaxOp	op;
+	/* the arguments */
+	List	   *args;
+	/* token location, or -1 if unknown */
+	int			location;
 } MinMaxExpr;
 
 /*
@@ -1324,15 +1442,23 @@ typedef enum XmlOptionType
 typedef struct XmlExpr
 {
 	Expr		xpr;
-	XmlExprOp	op;				/* xml function ID */
-	char	   *name;			/* name in xml(NAME foo ...) syntaxes */
-	List	   *named_args;		/* non-XML expressions for xml_attributes */
-	List	   *arg_names;		/* parallel list of String values */
-	List	   *args;			/* list of expressions */
-	XmlOptionType xmloption;	/* DOCUMENT or CONTENT */
-	Oid			type;			/* target type/typmod for XMLSERIALIZE */
+	/* xml function ID */
+	XmlExprOp	op;
+	/* name in xml(NAME foo ...) syntaxes */
+	char	   *name;
+	/* non-XML expressions for xml_attributes */
+	List	   *named_args;
+	/* parallel list of String values */
+	List	   *arg_names;
+	/* list of expressions */
+	List	   *args;
+	/* DOCUMENT or CONTENT */
+	XmlOptionType xmloption;
+	/* target type/typmod for XMLSERIALIZE */
+	Oid			type;
 	int32		typmod;
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } XmlExpr;
 
 /* ----------------
@@ -1364,8 +1490,10 @@ typedef struct NullTest
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	NullTestType nulltesttype;	/* IS NULL, IS NOT NULL */
-	bool		argisrow;		/* T to perform field-by-field null checks */
-	int			location;		/* token location, or -1 if unknown */
+	/* T to perform field-by-field null checks */
+	bool		argisrow;
+	/* token location, or -1 if unknown */
+	int			location;
 } NullTest;
 
 /*
@@ -1387,7 +1515,8 @@ typedef struct BooleanTest
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	BoolTestType booltesttype;	/* test type */
-	int			location;		/* token location, or -1 if unknown */
+	/* token location, or -1 if unknown */
+	int			location;
 } BooleanTest;
 
 /*
@@ -1404,10 +1533,14 @@ typedef struct CoerceToDomain
 	Expr		xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* domain type ID (result type) */
-	int32		resulttypmod;	/* output typmod (currently always -1) */
-	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
-	CoercionForm coercionformat;	/* how to display this node */
-	int			location;		/* token location, or -1 if unknown */
+	/* output typmod (currently always -1) */
+	int32		resulttypmod;
+	/* OID of collation, or InvalidOid if none */
+	Oid			resultcollid;
+	/* how to display this node */
+	CoercionForm coercionformat;
+	/* token location, or -1 if unknown */
+	int			location;
 } CoerceToDomain;
 
 /*
@@ -1422,10 +1555,14 @@ typedef struct CoerceToDomain
 typedef struct CoerceToDomainValue
 {
 	Expr		xpr;
-	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
-	int			location;		/* token location, or -1 if unknown */
+	/* type for substituted value */
+	Oid			typeId;
+	/* typemod for substituted value */
+	int32		typeMod;
+	/* collation for the substituted value */
+	Oid			collation;
+	/* token location, or -1 if unknown */
+	int			location;
 } CoerceToDomainValue;
 
 /*
@@ -1438,10 +1575,14 @@ typedef struct CoerceToDomainValue
 typedef struct SetToDefault
 {
 	Expr		xpr;
-	Oid			typeId;			/* type for substituted value */
-	int32		typeMod;		/* typemod for substituted value */
-	Oid			collation;		/* collation for the substituted value */
-	int			location;		/* token location, or -1 if unknown */
+	/* type for substituted value */
+	Oid			typeId;
+	/* typemod for substituted value */
+	int32		typeMod;
+	/* collation for the substituted value */
+	Oid			collation;
+	/* token location, or -1 if unknown */
+	int			location;
 } SetToDefault;
 
 /*
@@ -1552,15 +1693,20 @@ typedef struct InferenceElem
 typedef struct TargetEntry
 {
 	Expr		xpr;
-	Expr	   *expr;			/* expression to evaluate */
-	AttrNumber	resno;			/* attribute number (see notes above) */
-	char	   *resname;		/* name of the column (could be NULL) */
-	Index		ressortgroupref;	/* nonzero if referenced by a sort/group
-									 * clause */
-	Oid			resorigtbl;		/* OID of column's source table */
-	AttrNumber	resorigcol;		/* column's number in source table */
-	bool		resjunk;		/* set to true to eliminate the attribute from
-								 * final target list */
+	/* expression to evaluate */
+	Expr	   *expr;
+	/* attribute number (see notes above) */
+	AttrNumber	resno;
+	/* name of the column (could be NULL) */
+	char	   *resname;
+	/* nonzero if referenced by a sort/group clause */
+	Index		ressortgroupref;
+	/* OID of column's source table */
+	Oid			resorigtbl;
+	/* column's number in source table */
+	AttrNumber	resorigcol;
+	/* set to true to eliminate the attribute from final target list */
+	bool		resjunk;
 } TargetEntry;
 
 
@@ -1642,11 +1788,16 @@ typedef struct JoinExpr
 	bool		isNatural;		/* Natural join? Will need to shape table */
 	Node	   *larg;			/* left subtree */
 	Node	   *rarg;			/* right subtree */
-	List	   *usingClause;	/* USING clause, if any (list of String) */
-	Alias	   *join_using_alias;	/* alias attached to USING clause, if any */
-	Node	   *quals;			/* qualifiers on join, if any */
-	Alias	   *alias;			/* user-written alias clause, if any */
-	int			rtindex;		/* RT index assigned for join, or 0 */
+	/* USING clause, if any (list of String) */
+	List	   *usingClause;
+	/* alias attached to USING clause, if any */
+	Alias	   *join_using_alias;
+	/* qualifiers on join, if any */
+	Node	   *quals;
+	/* user-written alias clause, if any */
+	Alias	   *alias;
+	/* RT index assigned for join, or 0 */
+	int			rtindex;
 } JoinExpr;
 
 /*----------
-- 
2.39.0

v4-0002-Move-query-jumble-code-to-src-backend-nodes.patchtext/x-diff; charset=us-asciiDownload
From cd672f8feb4c533c2ef1bbfb31088efc64ce7160 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 18 Jan 2023 14:37:07 +0900
Subject: [PATCH v4 2/4] Move query jumble code to src/backend/nodes/

This will ease a follow-up move that will generate automatically this
code:
- queryjumble.c -> queryjumblefuncs.c
- utils/queryjumble.h -> nodes/queryjumble.h
---
 src/include/{utils => nodes}/queryjumble.h                  | 2 +-
 src/include/parser/analyze.h                                | 2 +-
 src/backend/nodes/Makefile                                  | 1 +
 src/backend/nodes/meson.build                               | 1 +
 .../{utils/misc/queryjumble.c => nodes/queryjumblefuncs.c}  | 6 +++---
 src/backend/parser/analyze.c                                | 2 +-
 src/backend/postmaster/postmaster.c                         | 2 +-
 src/backend/utils/misc/Makefile                             | 1 -
 src/backend/utils/misc/guc_tables.c                         | 2 +-
 src/backend/utils/misc/meson.build                          | 1 -
 contrib/pg_stat_statements/pg_stat_statements.c             | 2 +-
 11 files changed, 11 insertions(+), 11 deletions(-)
 rename src/include/{utils => nodes}/queryjumble.h (98%)
 rename src/backend/{utils/misc/queryjumble.c => nodes/queryjumblefuncs.c} (99%)

diff --git a/src/include/utils/queryjumble.h b/src/include/nodes/queryjumble.h
similarity index 98%
rename from src/include/utils/queryjumble.h
rename to src/include/nodes/queryjumble.h
index d372801410..204b8f74fd 100644
--- a/src/include/utils/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/include/utils/queryjumble.h
+ *	  src/include/nodes/queryjumble.h
  *
  *-------------------------------------------------------------------------
  */
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index c97be6efcf..1cef1833a6 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -15,8 +15,8 @@
 #define ANALYZE_H
 
 #include "nodes/params.h"
+#include "nodes/queryjumble.h"
 #include "parser/parse_node.h"
-#include "utils/queryjumble.h"
 
 /* Hook for plugins to get control at end of parse analysis */
 typedef void (*post_parse_analyze_hook_type) (ParseState *pstate,
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 7c594be583..af12c64878 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -27,6 +27,7 @@ OBJS = \
 	outfuncs.o \
 	params.o \
 	print.o \
+	queryjumblefuncs.o \
 	read.o \
 	readfuncs.o \
 	tidbitmap.o \
diff --git a/src/backend/nodes/meson.build b/src/backend/nodes/meson.build
index 2ff7dbac1d..9230515e7f 100644
--- a/src/backend/nodes/meson.build
+++ b/src/backend/nodes/meson.build
@@ -10,6 +10,7 @@ backend_sources += files(
   'nodes.c',
   'params.c',
   'print.c',
+  'queryjumblefuncs.c',
   'read.c',
   'tidbitmap.c',
   'value.c',
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/nodes/queryjumblefuncs.c
similarity index 99%
rename from src/backend/utils/misc/queryjumble.c
rename to src/backend/nodes/queryjumblefuncs.c
index 328995a7dc..16084842a3 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -1,6 +1,6 @@
 /*-------------------------------------------------------------------------
  *
- * queryjumble.c
+ * queryjumblefuncs.c
  *	 Query normalization and fingerprinting.
  *
  * Normalization is a process whereby similar queries, typically differing only
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  src/backend/utils/misc/queryjumble.c
+ *	  src/backend/nodes/queryjumblefuncs.c
  *
  *-------------------------------------------------------------------------
  */
@@ -34,8 +34,8 @@
 
 #include "common/hashfn.h"
 #include "miscadmin.h"
+#include "nodes/queryjumble.h"
 #include "parser/scansup.h"
-#include "utils/queryjumble.h"
 
 #define JUMBLE_SIZE				1024	/* query serialization buffer size */
 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 5b90974e83..4a817b75ad 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/queryjumble.h"
 #include "optimizer/optimizer.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
@@ -50,7 +51,6 @@
 #include "utils/backend_status.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
-#include "utils/queryjumble.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9cedc1b9f0..f05a26d255 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -102,6 +102,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
+#include "nodes/queryjumble.h"
 #include "pg_getopt.h"
 #include "pgstat.h"
 #include "port/pg_bswap.h"
@@ -126,7 +127,6 @@
 #include "utils/memutils.h"
 #include "utils/pidfile.h"
 #include "utils/ps_status.h"
-#include "utils/queryjumble.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 #include "utils/varlena.h"
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index b9ee4eb48a..2910032930 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -26,7 +26,6 @@ OBJS = \
 	pg_rusage.o \
 	ps_status.o \
 	queryenvironment.o \
-	queryjumble.o \
 	rls.o \
 	sampling.o \
 	superuser.o \
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5025e80f89..f9bfbbbd95 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -43,6 +43,7 @@
 #include "jit/jit.h"
 #include "libpq/auth.h"
 #include "libpq/libpq.h"
+#include "nodes/queryjumble.h"
 #include "optimizer/cost.h"
 #include "optimizer/geqo.h"
 #include "optimizer/optimizer.h"
@@ -77,7 +78,6 @@
 #include "utils/pg_locale.h"
 #include "utils/portal.h"
 #include "utils/ps_status.h"
-#include "utils/queryjumble.h"
 #include "utils/inval.h"
 #include "utils/xml.h"
 
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index e3e99ec5cb..f719c97c05 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -11,7 +11,6 @@ backend_sources += files(
   'pg_rusage.c',
   'ps_status.c',
   'queryenvironment.c',
-  'queryjumble.c',
   'rls.c',
   'sampling.c',
   'superuser.c',
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index a7a72783e5..ad1fe44496 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -55,6 +55,7 @@
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/queryjumble.h"
 #include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parsetree.h"
@@ -69,7 +70,6 @@
 #include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
-#include "utils/queryjumble.h"
 #include "utils/memutils.h"
 #include "utils/timestamp.h"
 
-- 
2.39.0

v4-0003-Support-for-automated-query-jumble-with-all-Nodes.patchtext/x-diff; charset=us-asciiDownload
From a304b483d6b9101daf7494830af46d19da464161 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 20 Jan 2023 13:22:40 +0900
Subject: [PATCH v4 3/4] Support for automated query jumble with all Nodes

This applies query jumbling in a consistent way to all the Nodes,
including DDLs & friends.
---
 src/include/nodes/bitmapset.h         |   2 +-
 src/include/nodes/nodes.h             |   7 +
 src/include/nodes/parsenodes.h        | 126 ++--
 src/include/nodes/primnodes.h         | 269 ++++----
 src/backend/nodes/README              |   1 +
 src/backend/nodes/gen_node_support.pl | 105 +++-
 src/backend/nodes/meson.build         |   2 +-
 src/backend/nodes/queryjumblefuncs.c  | 855 ++++++--------------------
 8 files changed, 509 insertions(+), 858 deletions(-)

diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 0dca6bc5fa..3d2225e1ae 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -50,7 +50,7 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
-	pg_node_attr(custom_copy_equal, special_read_write)
+	pg_node_attr(custom_copy_equal, special_read_write, no_query_jumble)
 
 	NodeTag		type;
 	int			nwords;			/* number of words in array */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 10752e8011..6ecd944a90 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -59,6 +59,8 @@ typedef enum NodeTag
  *
  * - no_copy_equal: Shorthand for both no_copy and no_equal.
  *
+ * - no_query_jumble: Does not support jumble() at all.
+ *
  * - no_read: Does not support nodeRead() at all.
  *
  * - nodetag_only: Does not support copyObject(), equal(), outNode(),
@@ -97,6 +99,11 @@ typedef enum NodeTag
  * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
  *   (Otherwise, compare normally.)
  *
+ * - query_jumble_ignore: Ignore the field for the query jumbling.
+ *
+ * - query_jumble_location: Mark the field as a location to track.  This is
+ *   only allowed for integer fields that include "location" in their name.
+ *
  * - 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
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index eec51e3ee2..826ca0f84a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -116,6 +116,11 @@ typedef uint64 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.
+ *
+ *	  All the fields ignored for the query jumbling are not semantically
+ *	  significant (such as alias names), as is ignored anything that can
+ *	  be deduced from child nodes (else we'd just be double-hashing that
+ *	  piece of information).
  */
 typedef struct Query
 {
@@ -124,45 +129,47 @@ typedef struct Query
 	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
 
 	/* where did I come from? */
-	QuerySource querySource;
+	QuerySource querySource pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * query identifier (can be set by plugins); ignored for equal, as it
-	 * might not be set; also not stored
+	 * might not be set; also not stored.  This is the result of the query
+	 * jumble, hence ignored.
 	 */
-	uint64		queryId pg_node_attr(equal_ignore, read_write_ignore, read_as(0));
+	uint64		queryId pg_node_attr(equal_ignore, query_jumble_ignore, read_write_ignore, read_as(0));
 
 	/* do I set the command result tag? */
-	bool		canSetTag;
+	bool		canSetTag pg_node_attr(query_jumble_ignore);
 
 	Node	   *utilityStmt;	/* non-null if commandType == CMD_UTILITY */
 
 	/*
 	 * rtable index of target relation for INSERT/UPDATE/DELETE/MERGE; 0 for
-	 * SELECT.
+	 * SELECT.  This is ignored in the query jumble as unrelated to the
+	 * compilation of the query ID.
 	 */
-	int			resultRelation;
+	int			resultRelation pg_node_attr(query_jumble_ignore);
 
 	/* has aggregates in tlist or havingQual */
-	bool		hasAggs;
+	bool		hasAggs pg_node_attr(query_jumble_ignore);
 	/* has window functions in tlist */
-	bool		hasWindowFuncs;
+	bool		hasWindowFuncs pg_node_attr(query_jumble_ignore);
 	/* has set-returning functions in tlist */
-	bool		hasTargetSRFs;
+	bool		hasTargetSRFs pg_node_attr(query_jumble_ignore);
 	/* has subquery SubLink */
-	bool		hasSubLinks;
+	bool		hasSubLinks pg_node_attr(query_jumble_ignore);
 	/* distinctClause is from DISTINCT ON */
-	bool		hasDistinctOn;
+	bool		hasDistinctOn pg_node_attr(query_jumble_ignore);
 	/* WITH RECURSIVE was specified */
-	bool		hasRecursive;
+	bool		hasRecursive pg_node_attr(query_jumble_ignore);
 	/* has INSERT/UPDATE/DELETE in WITH */
-	bool		hasModifyingCTE;
+	bool		hasModifyingCTE pg_node_attr(query_jumble_ignore);
 	/* FOR [KEY] UPDATE/SHARE was specified */
-	bool		hasForUpdate;
+	bool		hasForUpdate pg_node_attr(query_jumble_ignore);
 	/* rewriter has applied some RLS policy */
-	bool		hasRowSecurity;
+	bool		hasRowSecurity pg_node_attr(query_jumble_ignore);
 	/* is a RETURN statement */
-	bool		isReturn;
+	bool		isReturn pg_node_attr(query_jumble_ignore);
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
@@ -172,18 +179,18 @@ typedef struct Query
 	 * list of RTEPermissionInfo nodes for the rtable entries having
 	 * perminfoindex > 0
 	 */
-	List	   *rteperminfos;
+	List	   *rteperminfos pg_node_attr(query_jumble_ignore);
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
 	List	   *mergeActionList;	/* list of actions for MERGE (only) */
 	/* whether to use outer join */
-	bool		mergeUseOuterJoin;
+	bool		mergeUseOuterJoin pg_node_attr(query_jumble_ignore);
 
 	List	   *targetList;		/* target list (of TargetEntry) */
 
 	/* OVERRIDING clause */
-	OverridingKind override;
+	OverridingKind override pg_node_attr(query_jumble_ignore);
 
 	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
 
@@ -215,10 +222,10 @@ typedef struct Query
 	 * A list of pg_constraint OIDs that the query depends on to be
 	 * semantically valid
 	 */
-	List	   *constraintDeps;
+	List	   *constraintDeps pg_node_attr(query_jumble_ignore);
 
 	/* a list of WithCheckOption's (added during rewrite) */
-	List	   *withCheckOptions;
+	List	   *withCheckOptions pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * The following two fields identify the portion of the source text string
@@ -229,7 +236,7 @@ typedef struct Query
 	/* start location, or -1 if unknown */
 	int			stmt_location;
 	/* length in bytes; 0 means "rest of string" */
-	int			stmt_len;
+	int			stmt_len pg_node_attr(query_jumble_ignore);
 } Query;
 
 
@@ -1042,7 +1049,7 @@ typedef enum RTEKind
 
 typedef struct RangeTblEntry
 {
-	pg_node_attr(custom_read_write)
+	pg_node_attr(custom_read_write, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1265,6 +1272,8 @@ typedef struct RTEPermissionInfo
  * time.  We do however remember how many columns we thought the type had
  * (including dropped columns!), so that we can successfully ignore any
  * columns added after the query was parsed.
+ *
+ * The query jumbling needs only to track the function expression.
  */
 typedef struct RangeTblFunction
 {
@@ -1272,20 +1281,20 @@ typedef struct RangeTblFunction
 
 	Node	   *funcexpr;		/* expression tree for func call */
 	/* number of columns it contributes to RTE */
-	int			funccolcount;
+	int			funccolcount pg_node_attr(query_jumble_ignore);
 	/* These fields record the contents of a column definition list, if any: */
 	/* column names (list of String) */
-	List	   *funccolnames;
+	List	   *funccolnames pg_node_attr(query_jumble_ignore);
 	/* OID list of column type OIDs */
-	List	   *funccoltypes;
+	List	   *funccoltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of column typmods */
-	List	   *funccoltypmods;
+	List	   *funccoltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *funccolcollations;
+	List	   *funccolcollations pg_node_attr(query_jumble_ignore);
 
 	/* This is set during planning for use by the executor: */
 	/* PARAM_EXEC Param IDs affecting this func */
-	Bitmapset  *funcparams;
+	Bitmapset  *funcparams pg_node_attr(query_jumble_ignore);
 } RangeTblFunction;
 
 /*
@@ -1393,7 +1402,7 @@ typedef struct SortGroupClause
 	Oid			sortop;			/* the ordering operator ('<' op), or 0 */
 	bool		nulls_first;	/* do NULLs come before normal values? */
 	/* can eqop be implemented by hashing? */
-	bool		hashable;
+	bool		hashable pg_node_attr(query_jumble_ignore);
 } SortGroupClause;
 
 /*
@@ -1458,7 +1467,7 @@ typedef enum GroupingSetKind
 typedef struct GroupingSet
 {
 	NodeTag		type;
-	GroupingSetKind kind;
+	GroupingSetKind kind pg_node_attr(query_jumble_ignore);
 	List	   *content;
 	int			location;
 } GroupingSet;
@@ -1479,35 +1488,38 @@ typedef struct GroupingSet
  * When refname isn't null, the partitionClause is always copied from there;
  * the orderClause might or might not be copied (see copiedOrder); the framing
  * options are never copied, per spec.
+ *
+ * The information relevant for the query jumbling is the partition clause
+ * type and its bounds.
  */
 typedef struct WindowClause
 {
 	NodeTag		type;
 	/* window name (NULL in an OVER clause) */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* referenced window name, if any */
-	char	   *refname;
+	char	   *refname pg_node_attr(query_jumble_ignore);
 	List	   *partitionClause;	/* PARTITION BY list */
 	/* ORDER BY list */
-	List	   *orderClause;
+	List	   *orderClause pg_node_attr(query_jumble_ignore);
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
 	/* qual to help short-circuit execution */
-	List	   *runCondition;
+	List	   *runCondition pg_node_attr(query_jumble_ignore);
 	/* in_range function for startOffset */
-	Oid			startInRangeFunc;
+	Oid			startInRangeFunc pg_node_attr(query_jumble_ignore);
 	/* in_range function for endOffset */
-	Oid			endInRangeFunc;
+	Oid			endInRangeFunc pg_node_attr(query_jumble_ignore);
 	/* collation for in_range tests */
-	Oid			inRangeColl;
+	Oid			inRangeColl pg_node_attr(query_jumble_ignore);
 	/* use ASC sort order for in_range tests? */
-	bool		inRangeAsc;
+	bool		inRangeAsc pg_node_attr(query_jumble_ignore);
 	/* nulls sort first for in_range tests? */
-	bool		inRangeNullsFirst;
+	bool		inRangeNullsFirst pg_node_attr(query_jumble_ignore);
 	Index		winref;			/* ID referenced by window functions */
 	/* did we copy orderClause from refname? */
-	bool		copiedOrder;
+	bool		copiedOrder pg_node_attr(query_jumble_ignore);
 } WindowClause;
 
 /*
@@ -1625,27 +1637,27 @@ typedef struct CommonTableExpr
 	CTEMaterialize ctematerialized; /* is this an optimization fence? */
 	/* SelectStmt/InsertStmt/etc before parse analysis, Query afterwards: */
 	Node	   *ctequery;		/* the CTE's subquery */
-	CTESearchClause *search_clause;
-	CTECycleClause *cycle_clause;
+	CTESearchClause *search_clause pg_node_attr(query_jumble_ignore);
+	CTECycleClause *cycle_clause pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 	/* These fields are set during parse analysis: */
 	/* is this CTE actually recursive? */
-	bool		cterecursive;
+	bool		cterecursive pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * Number of RTEs referencing this CTE (excluding internal
-	 * self-references)
+	 * self-references), irrelevant for query jumbling.
 	 */
-	int			cterefcount;
+	int			cterefcount pg_node_attr(query_jumble_ignore);
 	/* list of output column names */
-	List	   *ctecolnames;
+	List	   *ctecolnames pg_node_attr(query_jumble_ignore);
 	/* OID list of output column type OIDs */
-	List	   *ctecoltypes;
+	List	   *ctecoltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of output column typmods */
-	List	   *ctecoltypmods;
+	List	   *ctecoltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *ctecolcollations;
+	List	   *ctecolcollations pg_node_attr(query_jumble_ignore);
 } CommonTableExpr;
 
 /* Convenience macro to get the output tlist of a CTE's query */
@@ -1683,11 +1695,11 @@ typedef struct MergeAction
 	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
 	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
 	/* OVERRIDING clause */
-	OverridingKind override;
+	OverridingKind override pg_node_attr(query_jumble_ignore);
 	Node	   *qual;			/* transformed WHEN conditions */
 	List	   *targetList;		/* the target list (of TargetEntry) */
 	/* target attribute numbers of an UPDATE */
-	List	   *updateColnos;
+	List	   *updateColnos pg_node_attr(query_jumble_ignore);
 } MergeAction;
 
 /*
@@ -1897,15 +1909,15 @@ typedef struct SetOperationStmt
 	Node	   *rarg;			/* right child */
 	/* Eventually add fields for CORRESPONDING spec here */
 
-	/* Fields derived during parse analysis: */
+	/* Fields derived during parse analysis (irrelevant for query jumbling): */
 	/* OID list of output column type OIDs */
-	List	   *colTypes;
+	List	   *colTypes pg_node_attr(query_jumble_ignore);
 	/* integer list of output column typmods */
-	List	   *colTypmods;
+	List	   *colTypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of output column collation OIDs */
-	List	   *colCollations;
+	List	   *colCollations pg_node_attr(query_jumble_ignore);
 	/* a list of SortGroupClause's */
-	List	   *groupClauses;
+	List	   *groupClauses pg_node_attr(query_jumble_ignore);
 	/* groupClauses is NIL if UNION ALL, but must be set otherwise */
 } SetOperationStmt;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 8d80c90ce7..8a4f64adc2 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -99,29 +99,29 @@ typedef struct TableFunc
 {
 	NodeTag		type;
 	/* list of namespace URI expressions */
-	List	   *ns_uris;
+	List	   *ns_uris pg_node_attr(query_jumble_ignore);
 	/* list of namespace names or NULL */
-	List	   *ns_names;
+	List	   *ns_names pg_node_attr(query_jumble_ignore);
 	/* input document expression */
 	Node	   *docexpr;
 	/* row filter expression */
 	Node	   *rowexpr;
 	/* column names (list of String) */
-	List	   *colnames;
+	List	   *colnames pg_node_attr(query_jumble_ignore);
 	/* OID list of column type OIDs */
-	List	   *coltypes;
+	List	   *coltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of column typmods */
-	List	   *coltypmods;
+	List	   *coltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *colcollations;
+	List	   *colcollations pg_node_attr(query_jumble_ignore);
 	/* list of column filter expressions */
 	List	   *colexprs;
 	/* list of column default expressions */
-	List	   *coldefexprs;
+	List	   *coldefexprs pg_node_attr(query_jumble_ignore);
 	/* nullability flag for each output column */
-	Bitmapset  *notnulls;
+	Bitmapset  *notnulls pg_node_attr(query_jumble_ignore);
 	/* counts from 0; -1 if none specified */
-	int			ordinalitycol;
+	int			ordinalitycol pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } TableFunc;
@@ -230,11 +230,11 @@ typedef struct Var
 	AttrNumber	varattno;
 
 	/* pg_type OID for the type of this var */
-	Oid			vartype;
+	Oid			vartype pg_node_attr(query_jumble_ignore);
 	/* pg_attribute typmod value */
-	int32		vartypmod;
+	int32		vartypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			varcollid;
+	Oid			varcollid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * for subquery variables referencing outer relations; 0 in a normal var,
@@ -248,9 +248,9 @@ typedef struct Var
 	 * their varno/varattno match.
 	 */
 	/* syntactic relation index (0 if unknown) */
-	Index		varnosyn pg_node_attr(equal_ignore);
+	Index		varnosyn pg_node_attr(equal_ignore, query_jumble_ignore);
 	/* syntactic attribute number */
-	AttrNumber	varattnosyn pg_node_attr(equal_ignore);
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -263,6 +263,8 @@ typedef struct Var
  * must be in non-extended form (4-byte header, no compression or external
  * references).  This ensures that the Const node is self-contained and makes
  * it more likely that equal() will see logically identical values as equal.
+ *
+ * Only the constant type OID is relevant for the query jumbling.
  */
 typedef struct Const
 {
@@ -272,24 +274,27 @@ typedef struct Const
 	/* pg_type OID of the constant's datatype */
 	Oid			consttype;
 	/* typmod value, if any */
-	int32		consttypmod;
+	int32		consttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			constcollid;
+	Oid			constcollid pg_node_attr(query_jumble_ignore);
 	/* typlen of the constant's datatype */
-	int			constlen;
+	int			constlen pg_node_attr(query_jumble_ignore);
 	/* the constant's value */
-	Datum		constvalue;
+	Datum		constvalue pg_node_attr(query_jumble_ignore);
 	/* whether the constant is null (if true, constvalue is undefined) */
-	bool		constisnull;
+	bool		constisnull pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * Whether this datatype is passed by value.  If true, then all the
 	 * information is stored in the Datum.  If false, then the Datum contains
 	 * a pointer to the information.
 	 */
-	bool		constbyval;
-	/* token location, or -1 if unknown */
-	int			location;
+	bool		constbyval pg_node_attr(query_jumble_ignore);
+	/*
+	 * token location, or -1 if unknown.  All constants are tracked as
+	 * locations in query jumbling, to be marked as parameters.
+	 */
+	int			location pg_node_attr(query_jumble_location);
 } Const;
 
 /*
@@ -327,6 +332,7 @@ typedef enum ParamKind
 	PARAM_MULTIEXPR
 } ParamKind;
 
+/* typmod and collation information are irrelevant for the query jumbling. */
 typedef struct Param
 {
 	Expr		xpr;
@@ -334,9 +340,9 @@ typedef struct Param
 	int			paramid;		/* numeric ID for parameter */
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
 	/* typmod value, if known */
-	int32		paramtypmod;
+	int32		paramtypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			paramcollid;
+	Oid			paramcollid pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } Param;
@@ -389,6 +395,9 @@ typedef struct Param
  * and can share the result.  Aggregates with same 'transno' but different
  * 'aggno' can share the same transition state, only the final function needs
  * to be called separately.
+ *
+ * Information related to collations, transition types and internal states
+ * are irrelevant for the query jumbling.
  */
 typedef struct Aggref
 {
@@ -398,22 +407,22 @@ typedef struct Aggref
 	Oid			aggfnoid;
 
 	/* type Oid of result of the aggregate */
-	Oid			aggtype;
+	Oid			aggtype pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			aggcollid;
+	Oid			aggcollid pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * type Oid of aggregate's transition value; ignored for equal since it
 	 * might not be set yet
 	 */
-	Oid			aggtranstype pg_node_attr(equal_ignore);
+	Oid			aggtranstype pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* type Oids of direct and aggregated args */
-	List	   *aggargtypes;
+	List	   *aggargtypes pg_node_attr(query_jumble_ignore);
 
 	/* direct arguments, if an ordered-set agg */
 	List	   *aggdirectargs;
@@ -431,31 +440,31 @@ typedef struct Aggref
 	Expr	   *aggfilter;
 
 	/* true if argument list was really '*' */
-	bool		aggstar;
+	bool		aggstar pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		aggvariadic;
+	bool		aggvariadic pg_node_attr(query_jumble_ignore);
 
 	/* aggregate kind (see pg_aggregate.h) */
-	char		aggkind;
+	char		aggkind pg_node_attr(query_jumble_ignore);
 
 	/* aggregate input already sorted */
-	bool		aggpresorted pg_node_attr(equal_ignore);
+	bool		aggpresorted pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* > 0 if agg belongs to outer query */
-	Index		agglevelsup;
+	Index		agglevelsup pg_node_attr(query_jumble_ignore);
 
 	/* expected agg-splitting mode of parent Agg */
-	AggSplit	aggsplit;
+	AggSplit	aggsplit pg_node_attr(query_jumble_ignore);
 
 	/* unique ID within the Agg node */
-	int			aggno;
+	int			aggno pg_node_attr(query_jumble_ignore);
 
 	/* unique ID of transition state in the Agg */
-	int			aggtransno;
+	int			aggtransno pg_node_attr(query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -484,19 +493,22 @@ typedef struct Aggref
  *
  * In raw parse output we have only the args list; parse analysis fills in the
  * refs list, and the planner fills in the cols list.
+ *
+ * All the fields used as information for an internal state are irrelevant
+ * for the query jumbling.
  */
 typedef struct GroupingFunc
 {
 	Expr		xpr;
 
 	/* arguments, not evaluated but kept for benefit of EXPLAIN etc. */
-	List	   *args;
+	List	   *args pg_node_attr(query_jumble_ignore);
 
 	/* ressortgrouprefs of arguments */
 	List	   *refs pg_node_attr(equal_ignore);
 
 	/* actual column positions set by planner */
-	List	   *cols pg_node_attr(equal_ignore);
+	List	   *cols pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* same as Aggref.agglevelsup */
 	Index		agglevelsup;
@@ -507,6 +519,9 @@ typedef struct GroupingFunc
 
 /*
  * WindowFunc
+ *
+ * Collation information is irrelevant for the query jumbling, as is the
+ * internal state information of the node like "winstar" and "winagg".
  */
 typedef struct WindowFunc
 {
@@ -514,11 +529,11 @@ typedef struct WindowFunc
 	/* pg_proc Oid of the function */
 	Oid			winfnoid;
 	/* type Oid of result of the window function */
-	Oid			wintype;
+	Oid			wintype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			wincollid;
+	Oid			wincollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the window function */
 	List	   *args;
 	/* FILTER expression, if any */
@@ -526,9 +541,9 @@ typedef struct WindowFunc
 	/* index of associated WindowClause */
 	Index		winref;
 	/* true if argument list was really '*' */
-	bool		winstar;
+	bool		winstar pg_node_attr(query_jumble_ignore);
 	/* is function a simple aggregate? */
-	bool		winagg;
+	bool		winagg pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } WindowFunc;
@@ -567,6 +582,8 @@ typedef struct WindowFunc
  * subscripting logic.  Likewise, reftypmod and refcollid will match the
  * container's properties in a store, but could be different in a fetch.
  *
+ * Any internal state data is ignored for the query jumbling.
+ *
  * Note: for the cases where a container is returned, if refexpr yields a R/W
  * expanded container, then the implementation is allowed to modify that
  * object in-place and return the same object.
@@ -575,15 +592,15 @@ typedef struct SubscriptingRef
 {
 	Expr		xpr;
 	/* type of the container proper */
-	Oid			refcontainertype;
+	Oid			refcontainertype pg_node_attr(query_jumble_ignore);
 	/* the container type's pg_type.typelem */
-	Oid			refelemtype;
+	Oid			refelemtype pg_node_attr(query_jumble_ignore);
 	/* type of the SubscriptingRef's result */
-	Oid			refrestype;
+	Oid			refrestype pg_node_attr(query_jumble_ignore);
 	/* typmod of the result */
-	int32		reftypmod;
+	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
-	Oid			refcollid;
+	Oid			refcollid pg_node_attr(query_jumble_ignore);
 	/* expressions that evaluate to upper container indexes */
 	List	   *refupperindexpr;
 
@@ -634,6 +651,9 @@ typedef enum CoercionForm
 
 /*
  * FuncExpr - expression node for a function call
+ *
+ * Collation information is irrelevant for the query jumbling, only the
+ * arguments and the function OID matter.
  */
 typedef struct FuncExpr
 {
@@ -641,21 +661,21 @@ typedef struct FuncExpr
 	/* PG_PROC OID of the function */
 	Oid			funcid;
 	/* PG_TYPE OID of result value */
-	Oid			funcresulttype;
+	Oid			funcresulttype pg_node_attr(query_jumble_ignore);
 	/* true if function returns set */
-	bool		funcretset;
+	bool		funcretset pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		funcvariadic;
+	bool		funcvariadic pg_node_attr(query_jumble_ignore);
 	/* how to display this function call */
-	CoercionForm funcformat;
+	CoercionForm funcformat pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			funccollid;
+	Oid			funccollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the function */
 	List	   *args;
 	/* token location, or -1 if unknown */
@@ -682,7 +702,7 @@ typedef struct NamedArgExpr
 	/* the argument expression */
 	Expr	   *arg;
 	/* the name */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* argument's number in positional notation */
 	int			argnumber;
 	/* argument name location, or -1 if unknown */
@@ -698,6 +718,9 @@ typedef struct NamedArgExpr
  * 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.
+ *
+ * Internal state information and collation data is irrelevant for the query
+ * jumbling.
  */
 typedef struct OpExpr
 {
@@ -707,19 +730,19 @@ typedef struct OpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of underlying function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_TYPE OID of result value */
-	Oid			opresulttype;
+	Oid			opresulttype pg_node_attr(query_jumble_ignore);
 
 	/* true if operator returns set */
-	bool		opretset;
+	bool		opretset pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			opcollid;
+	Oid			opcollid pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/* arguments to the operator (1 or 2) */
 	List	   *args;
@@ -775,6 +798,10 @@ typedef OpExpr NullIfExpr;
  * 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.
+ *
+ *
+ * OID entries of the internal function types are irrelevant for the query
+ * jumbling, but the operator OID and the arguments are.
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -784,19 +811,19 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of comparison function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_PROC OID of hash func or InvalidOid */
-	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_PROC OID of negator of opfuncid function or InvalidOid.  See above */
-	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* true for ANY, false for ALL */
 	bool		useOr;
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/* the scalar and array operands */
 	List	   *args;
@@ -899,7 +926,7 @@ typedef struct SubLink
 	int			subLinkId;		/* ID (1..n); 0 if not MULTIEXPR */
 	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
 	/* originally specified operator name */
-	List	   *operName;
+	List	   *operName pg_node_attr(query_jumble_ignore);
 	/* subselect as Query* or raw parsetree */
 	Node	   *subselect;
 	/* token location, or -1 if unknown */
@@ -1012,11 +1039,11 @@ typedef struct FieldSelect
 	Expr	   *arg;			/* input expression */
 	AttrNumber	fieldnum;		/* attribute number of field to extract */
 	/* type of the field (result type of this node) */
-	Oid			resulttype;
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* output typmod (usually -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation of the field */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 } FieldSelect;
 
 /* ----------------
@@ -1043,9 +1070,9 @@ typedef struct FieldStore
 	Expr	   *arg;			/* input tuple value */
 	List	   *newvals;		/* new value(s) for field(s) */
 	/* integer list of field attnums */
-	List	   *fieldnums;
+	List	   *fieldnums pg_node_attr(query_jumble_ignore);
 	/* type of result (same as type of arg) */
-	Oid			resulttype;
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 } FieldStore;
 
@@ -1068,11 +1095,11 @@ typedef struct RelabelType
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion expression */
 	/* output typmod (usually -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm relabelformat;
+	CoercionForm relabelformat pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } RelabelType;
@@ -1093,9 +1120,9 @@ typedef struct CoerceViaIO
 	Oid			resulttype;		/* output type of coercion */
 	/* output typmod is not stored, but is presumed -1 */
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coerceformat;
+	CoercionForm coerceformat pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } CoerceViaIO;
@@ -1120,11 +1147,11 @@ typedef struct ArrayCoerceExpr
 	Expr	   *elemexpr;		/* expression representing per-element work */
 	Oid			resulttype;		/* output type of coercion (an array type) */
 	/* output typmod (also element typmod) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coerceformat;
+	CoercionForm coerceformat pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } ArrayCoerceExpr;
@@ -1149,7 +1176,7 @@ typedef struct ConvertRowtypeExpr
 	Oid			resulttype;		/* output type (always a composite type) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 	/* how to display this node */
-	CoercionForm convertformat;
+	CoercionForm convertformat pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } ConvertRowtypeExpr;
@@ -1196,9 +1223,9 @@ typedef struct CaseExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			casetype;
+	Oid			casetype pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			casecollid;
+	Oid			casecollid pg_node_attr(query_jumble_ignore);
 	Expr	   *arg;			/* implicit equality comparison argument */
 	List	   *args;			/* the arguments (list of WHEN clauses) */
 	Expr	   *defresult;		/* the default result (ELSE clause) */
@@ -1243,9 +1270,9 @@ typedef struct CaseTestExpr
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 } CaseTestExpr;
 
 /*
@@ -1260,15 +1287,15 @@ typedef struct ArrayExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			array_typeid;
+	Oid			array_typeid pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			array_collid;
+	Oid			array_collid pg_node_attr(query_jumble_ignore);
 	/* common type of array elements */
-	Oid			element_typeid;
+	Oid			element_typeid pg_node_attr(query_jumble_ignore);
 	/* the array elements or sub-arrays */
 	List	   *elements;
 	/* true if elements are sub-arrays */
-	bool		multidims;
+	bool		multidims pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } ArrayExpr;
@@ -1300,7 +1327,7 @@ typedef struct RowExpr
 	List	   *args;			/* the fields */
 
 	/* RECORDOID or a composite type's ID */
-	Oid			row_typeid;
+	Oid			row_typeid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * row_typeid cannot be a domain over composite, only plain composite.  To
@@ -1316,10 +1343,10 @@ typedef struct RowExpr
 	 */
 
 	/* how to display this node */
-	CoercionForm row_format;
+	CoercionForm row_format pg_node_attr(query_jumble_ignore);
 
 	/* list of String, or NIL */
-	List	   *colnames;
+	List	   *colnames pg_node_attr(query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -1357,11 +1384,11 @@ typedef struct RowCompareExpr
 	/* LT LE GE or GT, never EQ or NE */
 	RowCompareType rctype;
 	/* OID list of pairwise comparison ops */
-	List	   *opnos;
+	List	   *opnos pg_node_attr(query_jumble_ignore);
 	/* OID list of containing operator families */
-	List	   *opfamilies;
+	List	   *opfamilies pg_node_attr(query_jumble_ignore);
 	/* OID list of collations for comparisons */
-	List	   *inputcollids;
+	List	   *inputcollids pg_node_attr(query_jumble_ignore);
 	/* the left-hand input arguments */
 	List	   *largs;
 	/* the right-hand input arguments */
@@ -1375,9 +1402,9 @@ typedef struct CoalesceExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			coalescetype;
+	Oid			coalescetype pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			coalescecollid;
+	Oid			coalescecollid pg_node_attr(query_jumble_ignore);
 	/* the arguments */
 	List	   *args;
 	/* token location, or -1 if unknown */
@@ -1397,11 +1424,11 @@ typedef struct MinMaxExpr
 {
 	Expr		xpr;
 	/* common type of arguments and result */
-	Oid			minmaxtype;
+	Oid			minmaxtype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			minmaxcollid;
+	Oid			minmaxcollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* function to execute */
 	MinMaxOp	op;
 	/* the arguments */
@@ -1445,18 +1472,18 @@ typedef struct XmlExpr
 	/* xml function ID */
 	XmlExprOp	op;
 	/* name in xml(NAME foo ...) syntaxes */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* non-XML expressions for xml_attributes */
 	List	   *named_args;
 	/* parallel list of String values */
-	List	   *arg_names;
+	List	   *arg_names pg_node_attr(query_jumble_ignore);
 	/* list of expressions */
 	List	   *args;
 	/* DOCUMENT or CONTENT */
-	XmlOptionType xmloption;
+	XmlOptionType xmloption pg_node_attr(query_jumble_ignore);
 	/* target type/typmod for XMLSERIALIZE */
-	Oid			type;
-	int32		typmod;
+	Oid			type pg_node_attr(query_jumble_ignore);
+	int32		typmod pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } XmlExpr;
@@ -1491,7 +1518,7 @@ typedef struct NullTest
 	Expr	   *arg;			/* input expression */
 	NullTestType nulltesttype;	/* IS NULL, IS NOT NULL */
 	/* T to perform field-by-field null checks */
-	bool		argisrow;
+	bool		argisrow pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } NullTest;
@@ -1527,6 +1554,8 @@ typedef struct BooleanTest
  * checked will be determined.  If the value passes, it is returned as the
  * result; if not, an error is raised.  Note that this is equivalent to
  * RelabelType in the scenario where no constraints are applied.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct CoerceToDomain
 {
@@ -1534,11 +1563,11 @@ typedef struct CoerceToDomain
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* domain type ID (result type) */
 	/* output typmod (currently always -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coercionformat;
+	CoercionForm coercionformat pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } CoerceToDomain;
@@ -1558,9 +1587,9 @@ typedef struct CoerceToDomainValue
 	/* type for substituted value */
 	Oid			typeId;
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } CoerceToDomainValue;
@@ -1571,6 +1600,8 @@ typedef struct CoerceToDomainValue
  * This is not an executable expression: it must be replaced by the actual
  * column default expression during rewriting.  But it is convenient to
  * treat it as an expression node during parsing and rewriting.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct SetToDefault
 {
@@ -1578,9 +1609,9 @@ typedef struct SetToDefault
 	/* type for substituted value */
 	Oid			typeId;
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } SetToDefault;
@@ -1698,15 +1729,15 @@ typedef struct TargetEntry
 	/* attribute number (see notes above) */
 	AttrNumber	resno;
 	/* name of the column (could be NULL) */
-	char	   *resname;
+	char	   *resname pg_node_attr(query_jumble_ignore);
 	/* nonzero if referenced by a sort/group clause */
 	Index		ressortgroupref;
 	/* OID of column's source table */
-	Oid			resorigtbl;
+	Oid			resorigtbl pg_node_attr(query_jumble_ignore);
 	/* column's number in source table */
-	AttrNumber	resorigcol;
+	AttrNumber	resorigcol pg_node_attr(query_jumble_ignore);
 	/* set to true to eliminate the attribute from final target list */
-	bool		resjunk;
+	bool		resjunk pg_node_attr(query_jumble_ignore);
 } TargetEntry;
 
 
@@ -1789,13 +1820,13 @@ typedef struct JoinExpr
 	Node	   *larg;			/* left subtree */
 	Node	   *rarg;			/* right subtree */
 	/* USING clause, if any (list of String) */
-	List	   *usingClause;
+	List	   *usingClause pg_node_attr(query_jumble_ignore);
 	/* alias attached to USING clause, if any */
-	Alias	   *join_using_alias;
+	Alias	   *join_using_alias pg_node_attr(query_jumble_ignore);
 	/* qualifiers on join, if any */
 	Node	   *quals;
 	/* user-written alias clause, if any */
-	Alias	   *alias;
+	Alias	   *alias pg_node_attr(query_jumble_ignore);
 	/* RT index assigned for join, or 0 */
 	int			rtindex;
 } JoinExpr;
diff --git a/src/backend/nodes/README b/src/backend/nodes/README
index 489a67eb89..7cf6e3b041 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -51,6 +51,7 @@ FILES IN THIS DIRECTORY (src/backend/nodes/)
 	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
+	queryjumblefuncs.c - compute a node tree for query jumbling (*)
 
     (*) - Most functions in these files are generated by
     gen_node_support.pl and #include'd there.
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index b3c1ead496..27b9e4e630 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -121,6 +121,8 @@ my %node_type_info;
 my @no_copy;
 # node types we don't want equal support for
 my @no_equal;
+# node types we don't want jumble support for
+my @no_query_jumble;
 # node types we don't want read support for
 my @no_read;
 # node types we don't want read/write support for
@@ -155,12 +157,13 @@ 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);
-# Lists are specially treated in all four support files, too.
+# Lists are specially treated in all five support files, too.
 # (Ideally we'd mark List as "special copy/equal" not "no copy/equal".
 # But until there's other use-cases for that, just hot-wire the tests
 # that would need to distinguish.)
 push @no_copy,            qw(List);
 push @no_equal,           qw(List);
+push @no_query_jumble,          qw(List);
 push @special_read_write, qw(List);
 
 # Nodes with custom copy/equal implementations are skipped from
@@ -332,6 +335,10 @@ foreach my $infile (@ARGV)
 							push @no_copy,  $in_struct;
 							push @no_equal, $in_struct;
 						}
+						elsif ($attr eq 'no_query_jumble')
+						{
+							push @no_query_jumble, $in_struct;
+						}
 						elsif ($attr eq 'no_read')
 						{
 							push @no_read, $in_struct;
@@ -457,6 +464,8 @@ foreach my $infile (@ARGV)
 								equal_as_scalar
 								equal_ignore
 								equal_ignore_if_zero
+								query_jumble_ignore
+								query_jumble_location
 								read_write_ignore
 								write_only_relids
 								write_only_nondefault_pathtarget
@@ -1225,6 +1234,100 @@ close $ofs;
 close $rfs;
 
 
+# queryjumblefuncs.c
+
+push @output_files, 'queryjumblefuncs.funcs.c';
+open my $jff, '>', "$output_path/queryjumblefuncs.funcs.c$tmpext" or die $!;
+push @output_files, 'queryjumblefuncs.switch.c';
+open my $jfs, '>', "$output_path/queryjumblefuncs.switch.c$tmpext" or die $!;
+
+printf $jff $header_comment, 'queryjumblefuncs.funcs.c';
+printf $jfs $header_comment, 'queryjumblefuncs.switch.c';
+
+print $jff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @nodetag_only;
+	my $struct_no_query_jumble = (elem $n, @no_query_jumble);
+
+	print $jfs "\t\t\tcase T_${n}:\n"
+	  . "\t\t\t\t_jumble${n}(jstate, expr);\n"
+	  . "\t\t\t\tbreak;\n"
+	  unless $struct_no_query_jumble;
+
+	print $jff "
+static void
+_jumble${n}(JumbleState *jstate, Node *node)
+{
+\t${n} *expr = (${n} *) node;\n
+" unless $struct_no_query_jumble;
+
+	# 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 $query_jumble_ignore = $struct_no_query_jumble;
+		my $query_jumble_location = 0;
+
+		# extract per-field attributes
+		foreach my $a (@a)
+		{
+			if ($a eq 'query_jumble_ignore')
+			{
+				$query_jumble_ignore = 1;
+			}
+			elsif ($a eq 'query_jumble_location')
+			{
+				$query_jumble_location = 1;
+			}
+		}
+
+		# node type
+		if (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+			and elem $1, @node_types)
+		{
+			print $jff "\tJUMBLE_NODE($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			# Track the node's location only if directly requested.
+			if ($query_jumble_location)
+			{
+				print $jff "\tJUMBLE_LOCATION($f);\n"
+				  unless $query_jumble_ignore;
+			}
+		}
+		elsif ($t eq 'char*')
+		{
+			print $jff "\tJUMBLE_STRING($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		else
+		{
+			print $jff "\tJUMBLE_FIELD($f);\n"
+			  unless $query_jumble_ignore;
+		}
+	}
+
+	# Some nodes have no attributes like CheckPointStmt,
+	# so tweak things for empty contents.
+	if (scalar(@{ $node_type_info{$n}->{fields} }) == 0)
+	{
+		print $jff "\t(void) expr;\n"
+		  unless $struct_no_query_jumble;
+	}
+
+	print $jff "}
+" unless $struct_no_query_jumble;
+}
+
+close $jff;
+close $jfs;
+
 # now rename the temporary files to their final names
 foreach my $file (@output_files)
 {
diff --git a/src/backend/nodes/meson.build b/src/backend/nodes/meson.build
index 9230515e7f..31467a12d3 100644
--- a/src/backend/nodes/meson.build
+++ b/src/backend/nodes/meson.build
@@ -10,7 +10,6 @@ backend_sources += files(
   'nodes.c',
   'params.c',
   'print.c',
-  'queryjumblefuncs.c',
   'read.c',
   'tidbitmap.c',
   'value.c',
@@ -21,6 +20,7 @@ backend_sources += files(
 nodefunc_sources = files(
   'copyfuncs.c',
   'equalfuncs.c',
+  'queryjumblefuncs.c',
   'outfuncs.c',
   'readfuncs.c',
 )
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 16084842a3..278150fba0 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -21,7 +21,7 @@
  * tree(s) generated from the query.  The executor can then use this value
  * to blame query costs on the proper queryId.
  *
- * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
@@ -45,15 +45,12 @@ int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
 /* True when compute_query_id is ON, or AUTO and a module requests them */
 bool		query_id_enabled = false;
 
-static uint64 compute_utility_query_id(const char *query_text,
-									   int query_location, int query_len);
 static void AppendJumble(JumbleState *jstate,
 						 const unsigned char *item, Size size);
-static void JumbleQueryInternal(JumbleState *jstate, Query *query);
-static void JumbleRangeTable(JumbleState *jstate, List *rtable);
-static void JumbleRowMarks(JumbleState *jstate, List *rowMarks);
-static void JumbleExpr(JumbleState *jstate, Node *node);
 static void RecordConstLocation(JumbleState *jstate, int location);
+static void _jumbleNode(JumbleState *jstate, Node *node);
+static void _jumbleList(JumbleState *jstate, Node *node);
+static void _jumbleRangeTblEntry(JumbleState *jstate, Node *node);
 
 /*
  * Given a possibly multi-statement source string, confine our attention to the
@@ -105,38 +102,29 @@ JumbleQuery(Query *query, const char *querytext)
 
 	Assert(IsQueryIdEnabled());
 
-	if (query->utilityStmt)
-	{
-		query->queryId = compute_utility_query_id(querytext,
-												  query->stmt_location,
-												  query->stmt_len);
-	}
-	else
-	{
-		jstate = (JumbleState *) palloc(sizeof(JumbleState));
+	jstate = (JumbleState *) palloc(sizeof(JumbleState));
 
-		/* Set up workspace for query jumbling */
-		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-		jstate->jumble_len = 0;
-		jstate->clocations_buf_size = 32;
-		jstate->clocations = (LocationLen *)
-			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-		jstate->clocations_count = 0;
-		jstate->highest_extern_param_id = 0;
+	/* Set up workspace for query jumbling */
+	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+	jstate->jumble_len = 0;
+	jstate->clocations_buf_size = 32;
+	jstate->clocations = (LocationLen *)
+		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+	jstate->clocations_count = 0;
+	jstate->highest_extern_param_id = 0;
 
-		/* Compute query ID and mark the Query node with it */
-		JumbleQueryInternal(jstate, query);
-		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-														  jstate->jumble_len,
-														  0));
+	/* Compute query ID and mark the Query node with it */
+	_jumbleNode(jstate, (Node *) query);
+	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+													  jstate->jumble_len,
+													  0));
 
-		/*
-		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-		 * prevent confusion with the utility-statement case.
-		 */
-		if (query->queryId == UINT64CONST(0))
-			query->queryId = UINT64CONST(1);
-	}
+	/*
+	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+	 * prevent confusion with the utility-statement case.
+	 */
+	if (query->queryId == UINT64CONST(0))
+		query->queryId = UINT64CONST(1);
 
 	return jstate;
 }
@@ -154,34 +142,6 @@ EnableQueryId(void)
 		query_id_enabled = true;
 }
 
-/*
- * Compute a query identifier for the given utility query string.
- */
-static uint64
-compute_utility_query_id(const char *query_text, int query_location, int query_len)
-{
-	uint64		queryId;
-	const char *sql;
-
-	/*
-	 * Confine our attention to the relevant part of the string, if the query
-	 * is a portion of a multi-statement source string.
-	 */
-	sql = CleanQuerytext(query_text, &query_location, &query_len);
-
-	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
-											   query_len, 0));
-
-	/*
-	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
-	 * 2 instead, queryID 1 is already in use for normal statements.
-	 */
-	if (queryId == UINT64CONST(0))
-		queryId = UINT64CONST(2);
-
-	return queryId;
-}
-
 /*
  * AppendJumble: Append a value that is substantive in a given query to
  * the current jumble.
@@ -219,621 +179,6 @@ AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
 	jstate->jumble_len = jumble_len;
 }
 
-/*
- * Wrappers around AppendJumble to encapsulate details of serialization
- * of individual local variable elements.
- */
-#define APP_JUMB(item) \
-	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
-#define APP_JUMB_STRING(str) \
-	AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1)
-
-/*
- * JumbleQueryInternal: Selectively serialize the query tree, appending
- * significant data to the "query jumble" while ignoring nonsignificant data.
- *
- * Rule of thumb for what to include is that we should ignore anything not
- * semantically significant (such as alias names) as well as anything that can
- * be deduced from child nodes (else we'd just be double-hashing that piece
- * of information).
- */
-static void
-JumbleQueryInternal(JumbleState *jstate, Query *query)
-{
-	Assert(IsA(query, Query));
-	Assert(query->utilityStmt == NULL);
-
-	APP_JUMB(query->commandType);
-	/* resultRelation is usually predictable from commandType */
-	JumbleExpr(jstate, (Node *) query->cteList);
-	JumbleRangeTable(jstate, query->rtable);
-	JumbleExpr(jstate, (Node *) query->jointree);
-	JumbleExpr(jstate, (Node *) query->mergeActionList);
-	JumbleExpr(jstate, (Node *) query->targetList);
-	JumbleExpr(jstate, (Node *) query->onConflict);
-	JumbleExpr(jstate, (Node *) query->returningList);
-	JumbleExpr(jstate, (Node *) query->groupClause);
-	APP_JUMB(query->groupDistinct);
-	JumbleExpr(jstate, (Node *) query->groupingSets);
-	JumbleExpr(jstate, query->havingQual);
-	JumbleExpr(jstate, (Node *) query->windowClause);
-	JumbleExpr(jstate, (Node *) query->distinctClause);
-	JumbleExpr(jstate, (Node *) query->sortClause);
-	JumbleExpr(jstate, query->limitOffset);
-	JumbleExpr(jstate, query->limitCount);
-	APP_JUMB(query->limitOption);
-	JumbleRowMarks(jstate, query->rowMarks);
-	JumbleExpr(jstate, query->setOperations);
-}
-
-/*
- * Jumble a range table
- */
-static void
-JumbleRangeTable(JumbleState *jstate, List *rtable)
-{
-	ListCell   *lc;
-
-	foreach(lc, rtable)
-	{
-		RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-		APP_JUMB(rte->rtekind);
-		switch (rte->rtekind)
-		{
-			case RTE_RELATION:
-				APP_JUMB(rte->relid);
-				JumbleExpr(jstate, (Node *) rte->tablesample);
-				APP_JUMB(rte->inh);
-				break;
-			case RTE_SUBQUERY:
-				JumbleQueryInternal(jstate, rte->subquery);
-				break;
-			case RTE_JOIN:
-				APP_JUMB(rte->jointype);
-				break;
-			case RTE_FUNCTION:
-				JumbleExpr(jstate, (Node *) rte->functions);
-				break;
-			case RTE_TABLEFUNC:
-				JumbleExpr(jstate, (Node *) rte->tablefunc);
-				break;
-			case RTE_VALUES:
-				JumbleExpr(jstate, (Node *) rte->values_lists);
-				break;
-			case RTE_CTE:
-
-				/*
-				 * Depending on the CTE name here isn't ideal, but it's the
-				 * only info we have to identify the referenced WITH item.
-				 */
-				APP_JUMB_STRING(rte->ctename);
-				APP_JUMB(rte->ctelevelsup);
-				break;
-			case RTE_NAMEDTUPLESTORE:
-				APP_JUMB_STRING(rte->enrname);
-				break;
-			case RTE_RESULT:
-				break;
-			default:
-				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
-				break;
-		}
-	}
-}
-
-/*
- * Jumble a rowMarks list
- */
-static void
-JumbleRowMarks(JumbleState *jstate, List *rowMarks)
-{
-	ListCell   *lc;
-
-	foreach(lc, rowMarks)
-	{
-		RowMarkClause *rowmark = lfirst_node(RowMarkClause, lc);
-
-		if (!rowmark->pushedDown)
-		{
-			APP_JUMB(rowmark->rti);
-			APP_JUMB(rowmark->strength);
-			APP_JUMB(rowmark->waitPolicy);
-		}
-	}
-}
-
-/*
- * Jumble an expression tree
- *
- * In general this function should handle all the same node types that
- * expression_tree_walker() does, and therefore it's coded to be as parallel
- * to that function as possible.  However, since we are only invoked on
- * queries immediately post-parse-analysis, we need not handle node types
- * that only appear in planning.
- *
- * Note: the reason we don't simply use expression_tree_walker() is that the
- * point of that function is to support tree walkers that don't care about
- * most tree node types, but here we care about all types.  We should complain
- * about any unrecognized node type.
- */
-static void
-JumbleExpr(JumbleState *jstate, Node *node)
-{
-	ListCell   *temp;
-
-	if (node == NULL)
-		return;
-
-	/* Guard against stack overflow due to overly complex expressions */
-	check_stack_depth();
-
-	/*
-	 * We always emit the node's NodeTag, then any additional fields that are
-	 * considered significant, and then we recurse to any child nodes.
-	 */
-	APP_JUMB(node->type);
-
-	switch (nodeTag(node))
-	{
-		case T_Var:
-			{
-				Var		   *var = (Var *) node;
-
-				APP_JUMB(var->varno);
-				APP_JUMB(var->varattno);
-				APP_JUMB(var->varlevelsup);
-			}
-			break;
-		case T_Const:
-			{
-				Const	   *c = (Const *) node;
-
-				/* We jumble only the constant's type, not its value */
-				APP_JUMB(c->consttype);
-				/* Also, record its parse location for query normalization */
-				RecordConstLocation(jstate, c->location);
-			}
-			break;
-		case T_Param:
-			{
-				Param	   *p = (Param *) node;
-
-				APP_JUMB(p->paramkind);
-				APP_JUMB(p->paramid);
-				APP_JUMB(p->paramtype);
-				/* Also, track the highest external Param id */
-				if (p->paramkind == PARAM_EXTERN &&
-					p->paramid > jstate->highest_extern_param_id)
-					jstate->highest_extern_param_id = p->paramid;
-			}
-			break;
-		case T_Aggref:
-			{
-				Aggref	   *expr = (Aggref *) node;
-
-				APP_JUMB(expr->aggfnoid);
-				JumbleExpr(jstate, (Node *) expr->aggdirectargs);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggorder);
-				JumbleExpr(jstate, (Node *) expr->aggdistinct);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_GroupingFunc:
-			{
-				GroupingFunc *grpnode = (GroupingFunc *) node;
-
-				JumbleExpr(jstate, (Node *) grpnode->refs);
-				APP_JUMB(grpnode->agglevelsup);
-			}
-			break;
-		case T_WindowFunc:
-			{
-				WindowFunc *expr = (WindowFunc *) node;
-
-				APP_JUMB(expr->winfnoid);
-				APP_JUMB(expr->winref);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_SubscriptingRef:
-			{
-				SubscriptingRef *sbsref = (SubscriptingRef *) node;
-
-				JumbleExpr(jstate, (Node *) sbsref->refupperindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refassgnexpr);
-			}
-			break;
-		case T_FuncExpr:
-			{
-				FuncExpr   *expr = (FuncExpr *) node;
-
-				APP_JUMB(expr->funcid);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_NamedArgExpr:
-			{
-				NamedArgExpr *nae = (NamedArgExpr *) node;
-
-				APP_JUMB(nae->argnumber);
-				JumbleExpr(jstate, (Node *) nae->arg);
-			}
-			break;
-		case T_OpExpr:
-		case T_DistinctExpr:	/* struct-equivalent to OpExpr */
-		case T_NullIfExpr:		/* struct-equivalent to OpExpr */
-			{
-				OpExpr	   *expr = (OpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_ScalarArrayOpExpr:
-			{
-				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				APP_JUMB(expr->useOr);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_BoolExpr:
-			{
-				BoolExpr   *expr = (BoolExpr *) node;
-
-				APP_JUMB(expr->boolop);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_SubLink:
-			{
-				SubLink    *sublink = (SubLink *) node;
-
-				APP_JUMB(sublink->subLinkType);
-				APP_JUMB(sublink->subLinkId);
-				JumbleExpr(jstate, (Node *) sublink->testexpr);
-				JumbleQueryInternal(jstate, castNode(Query, sublink->subselect));
-			}
-			break;
-		case T_FieldSelect:
-			{
-				FieldSelect *fs = (FieldSelect *) node;
-
-				APP_JUMB(fs->fieldnum);
-				JumbleExpr(jstate, (Node *) fs->arg);
-			}
-			break;
-		case T_FieldStore:
-			{
-				FieldStore *fstore = (FieldStore *) node;
-
-				JumbleExpr(jstate, (Node *) fstore->arg);
-				JumbleExpr(jstate, (Node *) fstore->newvals);
-			}
-			break;
-		case T_RelabelType:
-			{
-				RelabelType *rt = (RelabelType *) node;
-
-				APP_JUMB(rt->resulttype);
-				JumbleExpr(jstate, (Node *) rt->arg);
-			}
-			break;
-		case T_CoerceViaIO:
-			{
-				CoerceViaIO *cio = (CoerceViaIO *) node;
-
-				APP_JUMB(cio->resulttype);
-				JumbleExpr(jstate, (Node *) cio->arg);
-			}
-			break;
-		case T_ArrayCoerceExpr:
-			{
-				ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node;
-
-				APP_JUMB(acexpr->resulttype);
-				JumbleExpr(jstate, (Node *) acexpr->arg);
-				JumbleExpr(jstate, (Node *) acexpr->elemexpr);
-			}
-			break;
-		case T_ConvertRowtypeExpr:
-			{
-				ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node;
-
-				APP_JUMB(crexpr->resulttype);
-				JumbleExpr(jstate, (Node *) crexpr->arg);
-			}
-			break;
-		case T_CollateExpr:
-			{
-				CollateExpr *ce = (CollateExpr *) node;
-
-				APP_JUMB(ce->collOid);
-				JumbleExpr(jstate, (Node *) ce->arg);
-			}
-			break;
-		case T_CaseExpr:
-			{
-				CaseExpr   *caseexpr = (CaseExpr *) node;
-
-				JumbleExpr(jstate, (Node *) caseexpr->arg);
-				foreach(temp, caseexpr->args)
-				{
-					CaseWhen   *when = lfirst_node(CaseWhen, temp);
-
-					JumbleExpr(jstate, (Node *) when->expr);
-					JumbleExpr(jstate, (Node *) when->result);
-				}
-				JumbleExpr(jstate, (Node *) caseexpr->defresult);
-			}
-			break;
-		case T_CaseTestExpr:
-			{
-				CaseTestExpr *ct = (CaseTestExpr *) node;
-
-				APP_JUMB(ct->typeId);
-			}
-			break;
-		case T_ArrayExpr:
-			JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements);
-			break;
-		case T_RowExpr:
-			JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args);
-			break;
-		case T_RowCompareExpr:
-			{
-				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-
-				APP_JUMB(rcexpr->rctype);
-				JumbleExpr(jstate, (Node *) rcexpr->largs);
-				JumbleExpr(jstate, (Node *) rcexpr->rargs);
-			}
-			break;
-		case T_CoalesceExpr:
-			JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args);
-			break;
-		case T_MinMaxExpr:
-			{
-				MinMaxExpr *mmexpr = (MinMaxExpr *) node;
-
-				APP_JUMB(mmexpr->op);
-				JumbleExpr(jstate, (Node *) mmexpr->args);
-			}
-			break;
-		case T_XmlExpr:
-			{
-				XmlExpr    *xexpr = (XmlExpr *) node;
-
-				APP_JUMB(xexpr->op);
-				JumbleExpr(jstate, (Node *) xexpr->named_args);
-				JumbleExpr(jstate, (Node *) xexpr->args);
-			}
-			break;
-		case T_NullTest:
-			{
-				NullTest   *nt = (NullTest *) node;
-
-				APP_JUMB(nt->nulltesttype);
-				JumbleExpr(jstate, (Node *) nt->arg);
-			}
-			break;
-		case T_BooleanTest:
-			{
-				BooleanTest *bt = (BooleanTest *) node;
-
-				APP_JUMB(bt->booltesttype);
-				JumbleExpr(jstate, (Node *) bt->arg);
-			}
-			break;
-		case T_CoerceToDomain:
-			{
-				CoerceToDomain *cd = (CoerceToDomain *) node;
-
-				APP_JUMB(cd->resulttype);
-				JumbleExpr(jstate, (Node *) cd->arg);
-			}
-			break;
-		case T_CoerceToDomainValue:
-			{
-				CoerceToDomainValue *cdv = (CoerceToDomainValue *) node;
-
-				APP_JUMB(cdv->typeId);
-			}
-			break;
-		case T_SetToDefault:
-			{
-				SetToDefault *sd = (SetToDefault *) node;
-
-				APP_JUMB(sd->typeId);
-			}
-			break;
-		case T_CurrentOfExpr:
-			{
-				CurrentOfExpr *ce = (CurrentOfExpr *) node;
-
-				APP_JUMB(ce->cvarno);
-				if (ce->cursor_name)
-					APP_JUMB_STRING(ce->cursor_name);
-				APP_JUMB(ce->cursor_param);
-			}
-			break;
-		case T_NextValueExpr:
-			{
-				NextValueExpr *nve = (NextValueExpr *) node;
-
-				APP_JUMB(nve->seqid);
-				APP_JUMB(nve->typeId);
-			}
-			break;
-		case T_InferenceElem:
-			{
-				InferenceElem *ie = (InferenceElem *) node;
-
-				APP_JUMB(ie->infercollid);
-				APP_JUMB(ie->inferopclass);
-				JumbleExpr(jstate, ie->expr);
-			}
-			break;
-		case T_TargetEntry:
-			{
-				TargetEntry *tle = (TargetEntry *) node;
-
-				APP_JUMB(tle->resno);
-				APP_JUMB(tle->ressortgroupref);
-				JumbleExpr(jstate, (Node *) tle->expr);
-			}
-			break;
-		case T_RangeTblRef:
-			{
-				RangeTblRef *rtr = (RangeTblRef *) node;
-
-				APP_JUMB(rtr->rtindex);
-			}
-			break;
-		case T_JoinExpr:
-			{
-				JoinExpr   *join = (JoinExpr *) node;
-
-				APP_JUMB(join->jointype);
-				APP_JUMB(join->isNatural);
-				APP_JUMB(join->rtindex);
-				JumbleExpr(jstate, join->larg);
-				JumbleExpr(jstate, join->rarg);
-				JumbleExpr(jstate, join->quals);
-			}
-			break;
-		case T_FromExpr:
-			{
-				FromExpr   *from = (FromExpr *) node;
-
-				JumbleExpr(jstate, (Node *) from->fromlist);
-				JumbleExpr(jstate, from->quals);
-			}
-			break;
-		case T_OnConflictExpr:
-			{
-				OnConflictExpr *conf = (OnConflictExpr *) node;
-
-				APP_JUMB(conf->action);
-				JumbleExpr(jstate, (Node *) conf->arbiterElems);
-				JumbleExpr(jstate, conf->arbiterWhere);
-				JumbleExpr(jstate, (Node *) conf->onConflictSet);
-				JumbleExpr(jstate, conf->onConflictWhere);
-				APP_JUMB(conf->constraint);
-				APP_JUMB(conf->exclRelIndex);
-				JumbleExpr(jstate, (Node *) conf->exclRelTlist);
-			}
-			break;
-		case T_MergeAction:
-			{
-				MergeAction *mergeaction = (MergeAction *) node;
-
-				APP_JUMB(mergeaction->matched);
-				APP_JUMB(mergeaction->commandType);
-				JumbleExpr(jstate, mergeaction->qual);
-				JumbleExpr(jstate, (Node *) mergeaction->targetList);
-			}
-			break;
-		case T_List:
-			foreach(temp, (List *) node)
-			{
-				JumbleExpr(jstate, (Node *) lfirst(temp));
-			}
-			break;
-		case T_IntList:
-			foreach(temp, (List *) node)
-			{
-				APP_JUMB(lfirst_int(temp));
-			}
-			break;
-		case T_SortGroupClause:
-			{
-				SortGroupClause *sgc = (SortGroupClause *) node;
-
-				APP_JUMB(sgc->tleSortGroupRef);
-				APP_JUMB(sgc->eqop);
-				APP_JUMB(sgc->sortop);
-				APP_JUMB(sgc->nulls_first);
-			}
-			break;
-		case T_GroupingSet:
-			{
-				GroupingSet *gsnode = (GroupingSet *) node;
-
-				JumbleExpr(jstate, (Node *) gsnode->content);
-			}
-			break;
-		case T_WindowClause:
-			{
-				WindowClause *wc = (WindowClause *) node;
-
-				APP_JUMB(wc->winref);
-				APP_JUMB(wc->frameOptions);
-				JumbleExpr(jstate, (Node *) wc->partitionClause);
-				JumbleExpr(jstate, (Node *) wc->orderClause);
-				JumbleExpr(jstate, wc->startOffset);
-				JumbleExpr(jstate, wc->endOffset);
-			}
-			break;
-		case T_CommonTableExpr:
-			{
-				CommonTableExpr *cte = (CommonTableExpr *) node;
-
-				/* we store the string name because RTE_CTE RTEs need it */
-				APP_JUMB_STRING(cte->ctename);
-				APP_JUMB(cte->ctematerialized);
-				JumbleQueryInternal(jstate, castNode(Query, cte->ctequery));
-			}
-			break;
-		case T_SetOperationStmt:
-			{
-				SetOperationStmt *setop = (SetOperationStmt *) node;
-
-				APP_JUMB(setop->op);
-				APP_JUMB(setop->all);
-				JumbleExpr(jstate, setop->larg);
-				JumbleExpr(jstate, setop->rarg);
-			}
-			break;
-		case T_RangeTblFunction:
-			{
-				RangeTblFunction *rtfunc = (RangeTblFunction *) node;
-
-				JumbleExpr(jstate, rtfunc->funcexpr);
-			}
-			break;
-		case T_TableFunc:
-			{
-				TableFunc  *tablefunc = (TableFunc *) node;
-
-				JumbleExpr(jstate, tablefunc->docexpr);
-				JumbleExpr(jstate, tablefunc->rowexpr);
-				JumbleExpr(jstate, (Node *) tablefunc->colexprs);
-			}
-			break;
-		case T_TableSampleClause:
-			{
-				TableSampleClause *tsc = (TableSampleClause *) node;
-
-				APP_JUMB(tsc->tsmhandler);
-				JumbleExpr(jstate, (Node *) tsc->args);
-				JumbleExpr(jstate, (Node *) tsc->repeatable);
-			}
-			break;
-		default:
-			/* Only a warning, since we can stumble along anyway */
-			elog(WARNING, "unrecognized node type: %d",
-				 (int) nodeTag(node));
-			break;
-	}
-}
-
 /*
  * Record location of constant within query string of query tree
  * that is currently being walked.
@@ -859,3 +204,155 @@ RecordConstLocation(JumbleState *jstate, int location)
 		jstate->clocations_count++;
 	}
 }
+
+#define JUMBLE_NODE(item) \
+	_jumbleNode(jstate, (Node *) expr->item)
+#define JUMBLE_LOCATION(location) \
+	RecordConstLocation(jstate, expr->location)
+#define JUMBLE_FIELD(item) \
+	AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item))
+#define JUMBLE_FIELD_SINGLE(item) \
+	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
+#define JUMBLE_STRING(str) \
+do { \
+	if (expr->str) \
+		AppendJumble(jstate, (const unsigned char *) (expr->str), strlen(expr->str) + 1); \
+} while(0)
+
+#include "queryjumblefuncs.funcs.c"
+
+static void
+_jumbleNode(JumbleState *jstate, Node *node)
+{
+	Node	   *expr = node;
+
+	if (expr == NULL)
+		return;
+
+	/* Guard against stack overflow due to overly complex expressions */
+	check_stack_depth();
+
+	/*
+	 * We always emit the node's NodeTag, then any additional fields that are
+	 * considered significant, and then we recurse to any child nodes.
+	 */
+	JUMBLE_FIELD(type);
+
+	switch (nodeTag(expr))
+	{
+#include "queryjumblefuncs.switch.c"
+
+		case T_List:
+		case T_IntList:
+		case T_OidList:
+		case T_XidList:
+			_jumbleList(jstate, expr);
+			break;
+
+		case T_RangeTblEntry:
+			_jumbleRangeTblEntry(jstate, expr);
+			break;
+
+		default:
+			/* Only a warning, since we can stumble along anyway */
+			elog(WARNING, "unrecognized node type: %d",
+				 (int) nodeTag(expr));
+			break;
+	}
+
+	/* Special cases */
+	switch (nodeTag(expr))
+	{
+		case T_Param:
+			{
+				Param	   *p = (Param *) node;
+
+				/* Also, track the highest external Param id */
+				if (p->paramkind == PARAM_EXTERN &&
+					p->paramid > jstate->highest_extern_param_id)
+					jstate->highest_extern_param_id = p->paramid;
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+_jumbleList(JumbleState *jstate, Node *node)
+{
+	List	   *expr = (List *) node;
+	ListCell   *l;
+
+	switch (expr->type)
+	{
+		case T_List:
+			foreach(l, expr)
+				_jumbleNode(jstate, lfirst(l));
+			break;
+		case T_IntList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_int(l));
+			break;
+		case T_OidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_oid(l));
+			break;
+		case T_XidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_xid(l));
+			break;
+		default:
+			elog(ERROR, "unrecognized list node type: %d",
+				 (int) expr->type);
+			return;
+	}
+}
+
+static void
+_jumbleRangeTblEntry(JumbleState *jstate, Node *node)
+{
+	RangeTblEntry *expr = (RangeTblEntry *) node;
+
+	JUMBLE_FIELD(rtekind);
+	switch (expr->rtekind)
+	{
+		case RTE_RELATION:
+			JUMBLE_FIELD(relid);
+			JUMBLE_NODE(tablesample);
+			JUMBLE_FIELD(inh);
+			break;
+		case RTE_SUBQUERY:
+			JUMBLE_NODE(subquery);
+			break;
+		case RTE_JOIN:
+			JUMBLE_FIELD(jointype);
+			break;
+		case RTE_FUNCTION:
+			JUMBLE_NODE(functions);
+			break;
+		case RTE_TABLEFUNC:
+			JUMBLE_NODE(tablefunc);
+			break;
+		case RTE_VALUES:
+			JUMBLE_NODE(values_lists);
+			break;
+		case RTE_CTE:
+
+			/*
+			 * Depending on the CTE name here isn't ideal, but it's the only
+			 * info we have to identify the referenced WITH item.
+			 */
+			JUMBLE_STRING(ctename);
+			JUMBLE_FIELD(ctelevelsup);
+			break;
+		case RTE_NAMEDTUPLESTORE:
+			JUMBLE_STRING(enrname);
+			break;
+		case RTE_RESULT:
+			break;
+		default:
+			elog(ERROR, "unrecognized RTE kind: %d", (int) expr->rtekind);
+			break;
+	}
+}
-- 
2.39.0

v4-0004-Add-GUC-utility_query_id.patchtext/x-diff; charset=us-asciiDownload
From ffaa79144ff34eeaa89b55cbced2a182310ea522 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 20 Jan 2023 13:27:29 +0900
Subject: [PATCH v4 4/4] Add GUC utility_query_id

This GUC has two modes to control the computation method of query IDs
for utilities:
- 'string', the default, to hash the string query.
- 'jumble', to use the parsed tree.
---
 src/include/nodes/queryjumble.h               |  7 ++
 src/backend/nodes/queryjumblefuncs.c          | 81 ++++++++++++++-----
 src/backend/utils/misc/guc_tables.c           | 16 ++++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 doc/src/sgml/config.sgml                      | 18 +++++
 .../expected/pg_stat_statements.out           | 31 +++++++
 .../sql/pg_stat_statements.sql                | 17 ++++
 7 files changed, 151 insertions(+), 20 deletions(-)

diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index 204b8f74fd..261aea6bcf 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -59,8 +59,15 @@ enum ComputeQueryIdType
 	COMPUTE_QUERY_ID_REGRESS
 };
 
+enum UtilityQueryIdType
+{
+	UTILITY_QUERY_ID_STRING,
+	UTILITY_QUERY_ID_JUMBLE
+};
+
 /* GUC parameters */
 extern PGDLLIMPORT int compute_query_id;
+extern PGDLLIMPORT int utility_query_id;
 
 
 extern const char *CleanQuerytext(const char *query, int *location, int *len);
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 278150fba0..dd9ab8f353 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -41,12 +41,15 @@
 
 /* GUC parameters */
 int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
+int			utility_query_id = UTILITY_QUERY_ID_STRING;
 
 /* True when compute_query_id is ON, or AUTO and a module requests them */
 bool		query_id_enabled = false;
 
 static void AppendJumble(JumbleState *jstate,
 						 const unsigned char *item, Size size);
+static uint64 compute_utility_query_id(const char *query_text,
+									   int query_location, int query_len);
 static void RecordConstLocation(JumbleState *jstate, int location);
 static void _jumbleNode(JumbleState *jstate, Node *node);
 static void _jumbleList(JumbleState *jstate, Node *node);
@@ -102,29 +105,39 @@ JumbleQuery(Query *query, const char *querytext)
 
 	Assert(IsQueryIdEnabled());
 
-	jstate = (JumbleState *) palloc(sizeof(JumbleState));
+	if (query->utilityStmt &&
+		compute_query_id == UTILITY_QUERY_ID_STRING)
+	{
+		query->queryId = compute_utility_query_id(querytext,
+												  query->stmt_location,
+												  query->stmt_len);
+	}
+	else
+	{
+		jstate = (JumbleState *) palloc(sizeof(JumbleState));
 
-	/* Set up workspace for query jumbling */
-	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-	jstate->jumble_len = 0;
-	jstate->clocations_buf_size = 32;
-	jstate->clocations = (LocationLen *)
-		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-	jstate->clocations_count = 0;
-	jstate->highest_extern_param_id = 0;
+		/* Set up workspace for query jumbling */
+		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+		jstate->jumble_len = 0;
+		jstate->clocations_buf_size = 32;
+		jstate->clocations = (LocationLen *)
+			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+		jstate->clocations_count = 0;
+		jstate->highest_extern_param_id = 0;
 
-	/* Compute query ID and mark the Query node with it */
-	_jumbleNode(jstate, (Node *) query);
-	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-													  jstate->jumble_len,
-													  0));
+		/* Compute query ID and mark the Query node with it */
+		_jumbleNode(jstate, (Node *) query);
+		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+														  jstate->jumble_len,
+														  0));
 
-	/*
-	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-	 * prevent confusion with the utility-statement case.
-	 */
-	if (query->queryId == UINT64CONST(0))
-		query->queryId = UINT64CONST(1);
+		/*
+		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+		 * prevent confusion with the utility-statement case.
+		 */
+		if (query->queryId == UINT64CONST(0))
+			query->queryId = UINT64CONST(1);
+	}
 
 	return jstate;
 }
@@ -142,6 +155,34 @@ EnableQueryId(void)
 		query_id_enabled = true;
 }
 
+/*
+ * Compute a query identifier for the given utility query string.
+ */
+static uint64
+compute_utility_query_id(const char *query_text, int query_location, int query_len)
+{
+	uint64		queryId;
+	const char *sql;
+
+	/*
+	 * Confine our attention to the relevant part of the string, if the query
+	 * is a portion of a multi-statement source string.
+	 */
+	sql = CleanQuerytext(query_text, &query_location, &query_len);
+
+	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
+											   query_len, 0));
+
+	/*
+	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
+	 * 2 instead, queryID 1 is already in use for normal statements.
+	 */
+	if (queryId == UINT64CONST(0))
+		queryId = UINT64CONST(2);
+
+	return queryId;
+}
+
 /*
  * AppendJumble: Append a value that is substantive in a given query to
  * the current jumble.
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f9bfbbbd95..869e8a3a6f 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -294,6 +294,12 @@ static const struct config_enum_entry compute_query_id_options[] = {
 	{NULL, 0, false}
 };
 
+static const struct config_enum_entry utility_query_id_options[] = {
+	{"string", UTILITY_QUERY_ID_STRING, false},
+	{"jumble", UTILITY_QUERY_ID_JUMBLE, false},
+	{NULL, 0, false}
+};
+
 /*
  * Although only "on", "off", and "partition" are documented, we
  * accept all the likely variants of "on" and "off".
@@ -4563,6 +4569,16 @@ struct config_enum ConfigureNamesEnum[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"utility_query_id", PGC_SUSET, STATS_MONITORING,
+			gettext_noop("Controls method computing query ID for utilities."),
+			NULL
+		},
+		&utility_query_id,
+		UTILITY_QUERY_ID_STRING, utility_query_id_options,
+		NULL, NULL, NULL
+	},
+
 	{
 		{"constraint_exclusion", PGC_USERSET, QUERY_TUNING_OTHER,
 			gettext_noop("Enables the planner to use constraints to optimize queries."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..4d43c9d3c4 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -622,6 +622,7 @@
 # - Monitoring -
 
 #compute_query_id = auto
+#utility_query_id = string		# string, jumble
 #log_statement_stats = off
 #log_parser_stats = off
 #log_planner_stats = off
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 89d53f2a64..70c55f1a79 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8203,6 +8203,24 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-utility-query-id" xreflabel="utility_query_id">
+      <term><varname>utility_query_id</varname> (<type>enum</type>)
+      <indexterm>
+       <primary><varname>utility_query_id</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Controls the method used to compute the query identifier of a utility
+        query. Valid values are <literal>string</literal> to use a hash of the
+        query string and <literal>jumble</literal> to compute the query
+        identifier depending on the parsed tree of the utility query (less
+        performant, but allows for more parameterization of the queries
+        involved). The default is <literal>string</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-log-statement-stats">
       <term><varname>log_statement_stats</varname> (<type>boolean</type>)
       <indexterm>
diff --git a/contrib/pg_stat_statements/expected/pg_stat_statements.out b/contrib/pg_stat_statements/expected/pg_stat_statements.out
index 9ac5c87c3a..8bdf8beec3 100644
--- a/contrib/pg_stat_statements/expected/pg_stat_statements.out
+++ b/contrib/pg_stat_statements/expected/pg_stat_statements.out
@@ -554,6 +554,7 @@ DROP TABLE pgss_a, pgss_b CASCADE;
 -- utility commands
 --
 SET pg_stat_statements.track_utility = TRUE;
+SET utility_query_id = 'string';
 SELECT pg_stat_statements_reset();
  pg_stat_statements_reset 
 --------------------------
@@ -592,6 +593,36 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
  SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     0 |    0
 (9 rows)
 
+SELECT pg_stat_statements_reset();
+ pg_stat_statements_reset 
+--------------------------
+ 
+(1 row)
+
+SET utility_query_id = 'jumble';
+-- These queries have a different string, but the same parsing
+-- representation.
+Begin;
+Create Table test_utility_query (a int);
+Drop Table test_utility_query;
+Commit;
+BEGIN;
+CREATE TABLE test_utility_query (a int);
+DROP TABLE test_utility_query;
+COMMIT;
+SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
+                                    query                                     | calls | rows 
+------------------------------------------------------------------------------+-------+------
+ Begin                                                                        |     2 |    0
+ Commit                                                                       |     2 |    0
+ Create Table test_utility_query (a int)                                      |     2 |    0
+ Drop Table test_utility_query                                                |     2 |    0
+ SELECT pg_stat_statements_reset()                                            |     1 |    1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     0 |    0
+ SET utility_query_id = 'jumble'                                              |     1 |    0
+(7 rows)
+
+RESET utility_query_id;
 --
 -- Track the total number of rows retrieved or affected by the utility
 -- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW,
diff --git a/contrib/pg_stat_statements/sql/pg_stat_statements.sql b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
index 8f5c866225..81d663f81c 100644
--- a/contrib/pg_stat_statements/sql/pg_stat_statements.sql
+++ b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
@@ -258,6 +258,7 @@ DROP TABLE pgss_a, pgss_b CASCADE;
 -- utility commands
 --
 SET pg_stat_statements.track_utility = TRUE;
+SET utility_query_id = 'string';
 SELECT pg_stat_statements_reset();
 
 SELECT 1;
@@ -272,6 +273,22 @@ DROP FUNCTION PLUS_TWO(INTEGER);
 
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 
+SELECT pg_stat_statements_reset();
+SET utility_query_id = 'jumble';
+-- These queries have a different string, but the same parsing
+-- representation.
+Begin;
+Create Table test_utility_query (a int);
+Drop Table test_utility_query;
+Commit;
+BEGIN;
+CREATE TABLE test_utility_query (a int);
+DROP TABLE test_utility_query;
+COMMIT;
+
+SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
+RESET utility_query_id;
+
 --
 -- Track the total number of rows retrieved or affected by the utility
 -- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW,
-- 
2.39.0

#14Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Michael Paquier (#13)
Re: Generating code for query jumbling through gen_node_support.pl

On 20.01.23 05:35, Michael Paquier wrote:

On Thu, Jan 19, 2023 at 09:42:03AM +0100, Peter Eisentraut wrote:

I see that in the 0003 patch, most location fields now have an explicit
markup with query_jumble_ignore. I thought we had previously resolved to
consider location fields to be automatically ignored unless explicitly
included (like for the Const node). This appears to invert that? Am I
missing something?

As a result, I have rebased the patch set to use the two-attribute
approach: query_jumble_ignore and query_jumble_location.

Structurally, this looks okay to me now.

In your 0001 patch, most of the comment reformattings for location
fields are no longer needed, so you should undo those.

The 0002 patch looks good.

Those two could be committed with those adjustments, I think.

I'll read the 0003 again more carefully. I haven't studied the new 0004
yet.

#15Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#14)
2 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Fri, Jan 20, 2023 at 11:56:00AM +0100, Peter Eisentraut wrote:

In your 0001 patch, most of the comment reformattings for location fields
are no longer needed, so you should undo those.

The 0002 patch looks good.

Okay, I have gone through these two again and applied what I had.
0001 has been cleaned up of the extra comment moves for the
locations. Now, I have kept a few changes for some of the nodes to
have some consistency with the other fields, in the case where most of
the fields at the end of the structures have to be marked with new
node attributes. This made the style of the header a bit more
elegant, IMV.

I'll read the 0003 again more carefully. I haven't studied the new 0004
yet.

Thanks, again. Rebased version attached.
--
Michael

Attachments:

v5-0003-Support-for-automated-query-jumble-with-all-Nodes.patchtext/x-diff; charset=us-asciiDownload
From 977214a0c7b175efe0deae1847d7aa33ad66c120 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Sat, 21 Jan 2023 12:29:26 +0900
Subject: [PATCH v5 3/4] Support for automated query jumble with all Nodes

This applies query jumbling in a consistent way to all the Nodes,
including DDLs & friends.
---
 src/include/nodes/bitmapset.h         |   2 +-
 src/include/nodes/nodes.h             |   7 +
 src/include/nodes/parsenodes.h        | 126 ++--
 src/include/nodes/primnodes.h         | 269 ++++----
 src/backend/nodes/README              |   1 +
 src/backend/nodes/gen_node_support.pl | 105 +++-
 src/backend/nodes/meson.build         |   2 +-
 src/backend/nodes/queryjumblefuncs.c  | 855 ++++++--------------------
 8 files changed, 509 insertions(+), 858 deletions(-)

diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 0dca6bc5fa..3d2225e1ae 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -50,7 +50,7 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
-	pg_node_attr(custom_copy_equal, special_read_write)
+	pg_node_attr(custom_copy_equal, special_read_write, no_query_jumble)
 
 	NodeTag		type;
 	int			nwords;			/* number of words in array */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 10752e8011..6ecd944a90 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -59,6 +59,8 @@ typedef enum NodeTag
  *
  * - no_copy_equal: Shorthand for both no_copy and no_equal.
  *
+ * - no_query_jumble: Does not support jumble() at all.
+ *
  * - no_read: Does not support nodeRead() at all.
  *
  * - nodetag_only: Does not support copyObject(), equal(), outNode(),
@@ -97,6 +99,11 @@ typedef enum NodeTag
  * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
  *   (Otherwise, compare normally.)
  *
+ * - query_jumble_ignore: Ignore the field for the query jumbling.
+ *
+ * - query_jumble_location: Mark the field as a location to track.  This is
+ *   only allowed for integer fields that include "location" in their name.
+ *
  * - 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
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 89335d95e7..12da5c120e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -116,6 +116,11 @@ typedef uint64 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.
+ *
+ *	  All the fields ignored for the query jumbling are not semantically
+ *	  significant (such as alias names), as is ignored anything that can
+ *	  be deduced from child nodes (else we'd just be double-hashing that
+ *	  piece of information).
  */
 typedef struct Query
 {
@@ -124,45 +129,47 @@ typedef struct Query
 	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
 
 	/* where did I come from? */
-	QuerySource querySource;
+	QuerySource querySource pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * query identifier (can be set by plugins); ignored for equal, as it
-	 * might not be set; also not stored
+	 * might not be set; also not stored.  This is the result of the query
+	 * jumble, hence ignored.
 	 */
-	uint64		queryId pg_node_attr(equal_ignore, read_write_ignore, read_as(0));
+	uint64		queryId pg_node_attr(equal_ignore, query_jumble_ignore, read_write_ignore, read_as(0));
 
 	/* do I set the command result tag? */
-	bool		canSetTag;
+	bool		canSetTag pg_node_attr(query_jumble_ignore);
 
 	Node	   *utilityStmt;	/* non-null if commandType == CMD_UTILITY */
 
 	/*
 	 * rtable index of target relation for INSERT/UPDATE/DELETE/MERGE; 0 for
-	 * SELECT.
+	 * SELECT.  This is ignored in the query jumble as unrelated to the
+	 * compilation of the query ID.
 	 */
-	int			resultRelation;
+	int			resultRelation pg_node_attr(query_jumble_ignore);
 
 	/* has aggregates in tlist or havingQual */
-	bool		hasAggs;
+	bool		hasAggs pg_node_attr(query_jumble_ignore);
 	/* has window functions in tlist */
-	bool		hasWindowFuncs;
+	bool		hasWindowFuncs pg_node_attr(query_jumble_ignore);
 	/* has set-returning functions in tlist */
-	bool		hasTargetSRFs;
+	bool		hasTargetSRFs pg_node_attr(query_jumble_ignore);
 	/* has subquery SubLink */
-	bool		hasSubLinks;
+	bool		hasSubLinks pg_node_attr(query_jumble_ignore);
 	/* distinctClause is from DISTINCT ON */
-	bool		hasDistinctOn;
+	bool		hasDistinctOn pg_node_attr(query_jumble_ignore);
 	/* WITH RECURSIVE was specified */
-	bool		hasRecursive;
+	bool		hasRecursive pg_node_attr(query_jumble_ignore);
 	/* has INSERT/UPDATE/DELETE in WITH */
-	bool		hasModifyingCTE;
+	bool		hasModifyingCTE pg_node_attr(query_jumble_ignore);
 	/* FOR [KEY] UPDATE/SHARE was specified */
-	bool		hasForUpdate;
+	bool		hasForUpdate pg_node_attr(query_jumble_ignore);
 	/* rewriter has applied some RLS policy */
-	bool		hasRowSecurity;
+	bool		hasRowSecurity pg_node_attr(query_jumble_ignore);
 	/* is a RETURN statement */
-	bool		isReturn;
+	bool		isReturn pg_node_attr(query_jumble_ignore);
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
@@ -172,18 +179,18 @@ typedef struct Query
 	 * list of RTEPermissionInfo nodes for the rtable entries having
 	 * perminfoindex > 0
 	 */
-	List	   *rteperminfos;
+	List	   *rteperminfos pg_node_attr(query_jumble_ignore);
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
 	List	   *mergeActionList;	/* list of actions for MERGE (only) */
 	/* whether to use outer join */
-	bool		mergeUseOuterJoin;
+	bool		mergeUseOuterJoin pg_node_attr(query_jumble_ignore);
 
 	List	   *targetList;		/* target list (of TargetEntry) */
 
 	/* OVERRIDING clause */
-	OverridingKind override;
+	OverridingKind override pg_node_attr(query_jumble_ignore);
 
 	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
 
@@ -215,10 +222,10 @@ typedef struct Query
 	 * A list of pg_constraint OIDs that the query depends on to be
 	 * semantically valid
 	 */
-	List	   *constraintDeps;
+	List	   *constraintDeps pg_node_attr(query_jumble_ignore);
 
 	/* a list of WithCheckOption's (added during rewrite) */
-	List	   *withCheckOptions;
+	List	   *withCheckOptions pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * The following two fields identify the portion of the source text string
@@ -229,7 +236,7 @@ typedef struct Query
 	/* start location, or -1 if unknown */
 	int			stmt_location;
 	/* length in bytes; 0 means "rest of string" */
-	int			stmt_len;
+	int			stmt_len pg_node_attr(query_jumble_ignore);
 } Query;
 
 
@@ -1019,7 +1026,7 @@ typedef enum RTEKind
 
 typedef struct RangeTblEntry
 {
-	pg_node_attr(custom_read_write)
+	pg_node_attr(custom_read_write, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1250,6 +1257,8 @@ typedef struct RTEPermissionInfo
  * time.  We do however remember how many columns we thought the type had
  * (including dropped columns!), so that we can successfully ignore any
  * columns added after the query was parsed.
+ *
+ * The query jumbling needs only to track the function expression.
  */
 typedef struct RangeTblFunction
 {
@@ -1257,20 +1266,20 @@ typedef struct RangeTblFunction
 
 	Node	   *funcexpr;		/* expression tree for func call */
 	/* number of columns it contributes to RTE */
-	int			funccolcount;
+	int			funccolcount pg_node_attr(query_jumble_ignore);
 	/* These fields record the contents of a column definition list, if any: */
 	/* column names (list of String) */
-	List	   *funccolnames;
+	List	   *funccolnames pg_node_attr(query_jumble_ignore);
 	/* OID list of column type OIDs */
-	List	   *funccoltypes;
+	List	   *funccoltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of column typmods */
-	List	   *funccoltypmods;
+	List	   *funccoltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *funccolcollations;
+	List	   *funccolcollations pg_node_attr(query_jumble_ignore);
 
 	/* This is set during planning for use by the executor: */
 	/* PARAM_EXEC Param IDs affecting this func */
-	Bitmapset  *funcparams;
+	Bitmapset  *funcparams pg_node_attr(query_jumble_ignore);
 } RangeTblFunction;
 
 /*
@@ -1378,7 +1387,7 @@ typedef struct SortGroupClause
 	Oid			sortop;			/* the ordering operator ('<' op), or 0 */
 	bool		nulls_first;	/* do NULLs come before normal values? */
 	/* can eqop be implemented by hashing? */
-	bool		hashable;
+	bool		hashable pg_node_attr(query_jumble_ignore);
 } SortGroupClause;
 
 /*
@@ -1443,7 +1452,7 @@ typedef enum GroupingSetKind
 typedef struct GroupingSet
 {
 	NodeTag		type;
-	GroupingSetKind kind;
+	GroupingSetKind kind pg_node_attr(query_jumble_ignore);
 	List	   *content;
 	int			location;
 } GroupingSet;
@@ -1464,35 +1473,38 @@ typedef struct GroupingSet
  * When refname isn't null, the partitionClause is always copied from there;
  * the orderClause might or might not be copied (see copiedOrder); the framing
  * options are never copied, per spec.
+ *
+ * The information relevant for the query jumbling is the partition clause
+ * type and its bounds.
  */
 typedef struct WindowClause
 {
 	NodeTag		type;
 	/* window name (NULL in an OVER clause) */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* referenced window name, if any */
-	char	   *refname;
+	char	   *refname pg_node_attr(query_jumble_ignore);
 	List	   *partitionClause;	/* PARTITION BY list */
 	/* ORDER BY list */
-	List	   *orderClause;
+	List	   *orderClause pg_node_attr(query_jumble_ignore);
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
 	/* qual to help short-circuit execution */
-	List	   *runCondition;
+	List	   *runCondition pg_node_attr(query_jumble_ignore);
 	/* in_range function for startOffset */
-	Oid			startInRangeFunc;
+	Oid			startInRangeFunc pg_node_attr(query_jumble_ignore);
 	/* in_range function for endOffset */
-	Oid			endInRangeFunc;
+	Oid			endInRangeFunc pg_node_attr(query_jumble_ignore);
 	/* collation for in_range tests */
-	Oid			inRangeColl;
+	Oid			inRangeColl pg_node_attr(query_jumble_ignore);
 	/* use ASC sort order for in_range tests? */
-	bool		inRangeAsc;
+	bool		inRangeAsc pg_node_attr(query_jumble_ignore);
 	/* nulls sort first for in_range tests? */
-	bool		inRangeNullsFirst;
+	bool		inRangeNullsFirst pg_node_attr(query_jumble_ignore);
 	Index		winref;			/* ID referenced by window functions */
 	/* did we copy orderClause from refname? */
-	bool		copiedOrder;
+	bool		copiedOrder pg_node_attr(query_jumble_ignore);
 } WindowClause;
 
 /*
@@ -1607,26 +1619,26 @@ typedef struct CommonTableExpr
 	CTEMaterialize ctematerialized; /* is this an optimization fence? */
 	/* SelectStmt/InsertStmt/etc before parse analysis, Query afterwards: */
 	Node	   *ctequery;		/* the CTE's subquery */
-	CTESearchClause *search_clause;
-	CTECycleClause *cycle_clause;
+	CTESearchClause *search_clause pg_node_attr(query_jumble_ignore);
+	CTECycleClause *cycle_clause pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 	/* These fields are set during parse analysis: */
 	/* is this CTE actually recursive? */
-	bool		cterecursive;
+	bool		cterecursive pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * Number of RTEs referencing this CTE (excluding internal
-	 * self-references)
+	 * self-references), irrelevant for query jumbling.
 	 */
-	int			cterefcount;
+	int			cterefcount pg_node_attr(query_jumble_ignore);
 	/* list of output column names */
-	List	   *ctecolnames;
+	List	   *ctecolnames pg_node_attr(query_jumble_ignore);
 	/* OID list of output column type OIDs */
-	List	   *ctecoltypes;
+	List	   *ctecoltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of output column typmods */
-	List	   *ctecoltypmods;
+	List	   *ctecoltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *ctecolcollations;
+	List	   *ctecolcollations pg_node_attr(query_jumble_ignore);
 } CommonTableExpr;
 
 /* Convenience macro to get the output tlist of a CTE's query */
@@ -1664,11 +1676,11 @@ typedef struct MergeAction
 	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
 	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
 	/* OVERRIDING clause */
-	OverridingKind override;
+	OverridingKind override pg_node_attr(query_jumble_ignore);
 	Node	   *qual;			/* transformed WHEN conditions */
 	List	   *targetList;		/* the target list (of TargetEntry) */
 	/* target attribute numbers of an UPDATE */
-	List	   *updateColnos;
+	List	   *updateColnos pg_node_attr(query_jumble_ignore);
 } MergeAction;
 
 /*
@@ -1877,15 +1889,15 @@ typedef struct SetOperationStmt
 	Node	   *rarg;			/* right child */
 	/* Eventually add fields for CORRESPONDING spec here */
 
-	/* Fields derived during parse analysis: */
+	/* Fields derived during parse analysis (irrelevant for query jumbling): */
 	/* OID list of output column type OIDs */
-	List	   *colTypes;
+	List	   *colTypes pg_node_attr(query_jumble_ignore);
 	/* integer list of output column typmods */
-	List	   *colTypmods;
+	List	   *colTypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of output column collation OIDs */
-	List	   *colCollations;
+	List	   *colCollations pg_node_attr(query_jumble_ignore);
 	/* a list of SortGroupClause's */
-	List	   *groupClauses;
+	List	   *groupClauses pg_node_attr(query_jumble_ignore);
 	/* groupClauses is NIL if UNION ALL, but must be set otherwise */
 } SetOperationStmt;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3bdde134f4..f81518c2fd 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -99,29 +99,29 @@ typedef struct TableFunc
 {
 	NodeTag		type;
 	/* list of namespace URI expressions */
-	List	   *ns_uris;
+	List	   *ns_uris pg_node_attr(query_jumble_ignore);
 	/* list of namespace names or NULL */
-	List	   *ns_names;
+	List	   *ns_names pg_node_attr(query_jumble_ignore);
 	/* input document expression */
 	Node	   *docexpr;
 	/* row filter expression */
 	Node	   *rowexpr;
 	/* column names (list of String) */
-	List	   *colnames;
+	List	   *colnames pg_node_attr(query_jumble_ignore);
 	/* OID list of column type OIDs */
-	List	   *coltypes;
+	List	   *coltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of column typmods */
-	List	   *coltypmods;
+	List	   *coltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *colcollations;
+	List	   *colcollations pg_node_attr(query_jumble_ignore);
 	/* list of column filter expressions */
 	List	   *colexprs;
 	/* list of column default expressions */
-	List	   *coldefexprs;
+	List	   *coldefexprs pg_node_attr(query_jumble_ignore);
 	/* nullability flag for each output column */
-	Bitmapset  *notnulls;
+	Bitmapset  *notnulls pg_node_attr(query_jumble_ignore);
 	/* counts from 0; -1 if none specified */
-	int			ordinalitycol;
+	int			ordinalitycol pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } TableFunc;
@@ -230,11 +230,11 @@ typedef struct Var
 	AttrNumber	varattno;
 
 	/* pg_type OID for the type of this var */
-	Oid			vartype;
+	Oid			vartype pg_node_attr(query_jumble_ignore);
 	/* pg_attribute typmod value */
-	int32		vartypmod;
+	int32		vartypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			varcollid;
+	Oid			varcollid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * for subquery variables referencing outer relations; 0 in a normal var,
@@ -248,9 +248,9 @@ typedef struct Var
 	 * their varno/varattno match.
 	 */
 	/* syntactic relation index (0 if unknown) */
-	Index		varnosyn pg_node_attr(equal_ignore);
+	Index		varnosyn pg_node_attr(equal_ignore, query_jumble_ignore);
 	/* syntactic attribute number */
-	AttrNumber	varattnosyn pg_node_attr(equal_ignore);
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -263,6 +263,8 @@ typedef struct Var
  * must be in non-extended form (4-byte header, no compression or external
  * references).  This ensures that the Const node is self-contained and makes
  * it more likely that equal() will see logically identical values as equal.
+ *
+ * Only the constant type OID is relevant for the query jumbling.
  */
 typedef struct Const
 {
@@ -272,24 +274,27 @@ typedef struct Const
 	/* pg_type OID of the constant's datatype */
 	Oid			consttype;
 	/* typmod value, if any */
-	int32		consttypmod;
+	int32		consttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			constcollid;
+	Oid			constcollid pg_node_attr(query_jumble_ignore);
 	/* typlen of the constant's datatype */
-	int			constlen;
+	int			constlen pg_node_attr(query_jumble_ignore);
 	/* the constant's value */
-	Datum		constvalue;
+	Datum		constvalue pg_node_attr(query_jumble_ignore);
 	/* whether the constant is null (if true, constvalue is undefined) */
-	bool		constisnull;
+	bool		constisnull pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * Whether this datatype is passed by value.  If true, then all the
 	 * information is stored in the Datum.  If false, then the Datum contains
 	 * a pointer to the information.
 	 */
-	bool		constbyval;
-	/* token location, or -1 if unknown */
-	int			location;
+	bool		constbyval pg_node_attr(query_jumble_ignore);
+	/*
+	 * token location, or -1 if unknown.  All constants are tracked as
+	 * locations in query jumbling, to be marked as parameters.
+	 */
+	int			location pg_node_attr(query_jumble_location);
 } Const;
 
 /*
@@ -327,6 +332,7 @@ typedef enum ParamKind
 	PARAM_MULTIEXPR
 } ParamKind;
 
+/* typmod and collation information are irrelevant for the query jumbling. */
 typedef struct Param
 {
 	Expr		xpr;
@@ -334,9 +340,9 @@ typedef struct Param
 	int			paramid;		/* numeric ID for parameter */
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
 	/* typmod value, if known */
-	int32		paramtypmod;
+	int32		paramtypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			paramcollid;
+	Oid			paramcollid pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } Param;
@@ -389,6 +395,9 @@ typedef struct Param
  * and can share the result.  Aggregates with same 'transno' but different
  * 'aggno' can share the same transition state, only the final function needs
  * to be called separately.
+ *
+ * Information related to collations, transition types and internal states
+ * are irrelevant for the query jumbling.
  */
 typedef struct Aggref
 {
@@ -398,22 +407,22 @@ typedef struct Aggref
 	Oid			aggfnoid;
 
 	/* type Oid of result of the aggregate */
-	Oid			aggtype;
+	Oid			aggtype pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			aggcollid;
+	Oid			aggcollid pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * type Oid of aggregate's transition value; ignored for equal since it
 	 * might not be set yet
 	 */
-	Oid			aggtranstype pg_node_attr(equal_ignore);
+	Oid			aggtranstype pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* type Oids of direct and aggregated args */
-	List	   *aggargtypes;
+	List	   *aggargtypes pg_node_attr(query_jumble_ignore);
 
 	/* direct arguments, if an ordered-set agg */
 	List	   *aggdirectargs;
@@ -431,31 +440,31 @@ typedef struct Aggref
 	Expr	   *aggfilter;
 
 	/* true if argument list was really '*' */
-	bool		aggstar;
+	bool		aggstar pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		aggvariadic;
+	bool		aggvariadic pg_node_attr(query_jumble_ignore);
 
 	/* aggregate kind (see pg_aggregate.h) */
-	char		aggkind;
+	char		aggkind pg_node_attr(query_jumble_ignore);
 
 	/* aggregate input already sorted */
-	bool		aggpresorted pg_node_attr(equal_ignore);
+	bool		aggpresorted pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* > 0 if agg belongs to outer query */
-	Index		agglevelsup;
+	Index		agglevelsup pg_node_attr(query_jumble_ignore);
 
 	/* expected agg-splitting mode of parent Agg */
-	AggSplit	aggsplit;
+	AggSplit	aggsplit pg_node_attr(query_jumble_ignore);
 
 	/* unique ID within the Agg node */
-	int			aggno;
+	int			aggno pg_node_attr(query_jumble_ignore);
 
 	/* unique ID of transition state in the Agg */
-	int			aggtransno;
+	int			aggtransno pg_node_attr(query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -484,19 +493,22 @@ typedef struct Aggref
  *
  * In raw parse output we have only the args list; parse analysis fills in the
  * refs list, and the planner fills in the cols list.
+ *
+ * All the fields used as information for an internal state are irrelevant
+ * for the query jumbling.
  */
 typedef struct GroupingFunc
 {
 	Expr		xpr;
 
 	/* arguments, not evaluated but kept for benefit of EXPLAIN etc. */
-	List	   *args;
+	List	   *args pg_node_attr(query_jumble_ignore);
 
 	/* ressortgrouprefs of arguments */
 	List	   *refs pg_node_attr(equal_ignore);
 
 	/* actual column positions set by planner */
-	List	   *cols pg_node_attr(equal_ignore);
+	List	   *cols pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* same as Aggref.agglevelsup */
 	Index		agglevelsup;
@@ -507,6 +519,9 @@ typedef struct GroupingFunc
 
 /*
  * WindowFunc
+ *
+ * Collation information is irrelevant for the query jumbling, as is the
+ * internal state information of the node like "winstar" and "winagg".
  */
 typedef struct WindowFunc
 {
@@ -514,11 +529,11 @@ typedef struct WindowFunc
 	/* pg_proc Oid of the function */
 	Oid			winfnoid;
 	/* type Oid of result of the window function */
-	Oid			wintype;
+	Oid			wintype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			wincollid;
+	Oid			wincollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the window function */
 	List	   *args;
 	/* FILTER expression, if any */
@@ -526,9 +541,9 @@ typedef struct WindowFunc
 	/* index of associated WindowClause */
 	Index		winref;
 	/* true if argument list was really '*' */
-	bool		winstar;
+	bool		winstar pg_node_attr(query_jumble_ignore);
 	/* is function a simple aggregate? */
-	bool		winagg;
+	bool		winagg pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } WindowFunc;
@@ -567,6 +582,8 @@ typedef struct WindowFunc
  * subscripting logic.  Likewise, reftypmod and refcollid will match the
  * container's properties in a store, but could be different in a fetch.
  *
+ * Any internal state data is ignored for the query jumbling.
+ *
  * Note: for the cases where a container is returned, if refexpr yields a R/W
  * expanded container, then the implementation is allowed to modify that
  * object in-place and return the same object.
@@ -575,15 +592,15 @@ typedef struct SubscriptingRef
 {
 	Expr		xpr;
 	/* type of the container proper */
-	Oid			refcontainertype;
+	Oid			refcontainertype pg_node_attr(query_jumble_ignore);
 	/* the container type's pg_type.typelem */
-	Oid			refelemtype;
+	Oid			refelemtype pg_node_attr(query_jumble_ignore);
 	/* type of the SubscriptingRef's result */
-	Oid			refrestype;
+	Oid			refrestype pg_node_attr(query_jumble_ignore);
 	/* typmod of the result */
-	int32		reftypmod;
+	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
-	Oid			refcollid;
+	Oid			refcollid pg_node_attr(query_jumble_ignore);
 	/* expressions that evaluate to upper container indexes */
 	List	   *refupperindexpr;
 
@@ -634,6 +651,9 @@ typedef enum CoercionForm
 
 /*
  * FuncExpr - expression node for a function call
+ *
+ * Collation information is irrelevant for the query jumbling, only the
+ * arguments and the function OID matter.
  */
 typedef struct FuncExpr
 {
@@ -641,21 +661,21 @@ typedef struct FuncExpr
 	/* PG_PROC OID of the function */
 	Oid			funcid;
 	/* PG_TYPE OID of result value */
-	Oid			funcresulttype;
+	Oid			funcresulttype pg_node_attr(query_jumble_ignore);
 	/* true if function returns set */
-	bool		funcretset;
+	bool		funcretset pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		funcvariadic;
+	bool		funcvariadic pg_node_attr(query_jumble_ignore);
 	/* how to display this function call */
-	CoercionForm funcformat;
+	CoercionForm funcformat pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			funccollid;
+	Oid			funccollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the function */
 	List	   *args;
 	/* token location, or -1 if unknown */
@@ -682,7 +702,7 @@ typedef struct NamedArgExpr
 	/* the argument expression */
 	Expr	   *arg;
 	/* the name */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* argument's number in positional notation */
 	int			argnumber;
 	/* argument name location, or -1 if unknown */
@@ -698,6 +718,9 @@ typedef struct NamedArgExpr
  * 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.
+ *
+ * Internal state information and collation data is irrelevant for the query
+ * jumbling.
  */
 typedef struct OpExpr
 {
@@ -707,19 +730,19 @@ typedef struct OpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of underlying function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_TYPE OID of result value */
-	Oid			opresulttype;
+	Oid			opresulttype pg_node_attr(query_jumble_ignore);
 
 	/* true if operator returns set */
-	bool		opretset;
+	bool		opretset pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			opcollid;
+	Oid			opcollid pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/* arguments to the operator (1 or 2) */
 	List	   *args;
@@ -775,6 +798,10 @@ typedef OpExpr NullIfExpr;
  * 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.
+ *
+ *
+ * OID entries of the internal function types are irrelevant for the query
+ * jumbling, but the operator OID and the arguments are.
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -784,19 +811,19 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of comparison function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_PROC OID of hash func or InvalidOid */
-	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_PROC OID of negator of opfuncid function or InvalidOid.  See above */
-	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* true for ANY, false for ALL */
 	bool		useOr;
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/* the scalar and array operands */
 	List	   *args;
@@ -898,7 +925,7 @@ typedef struct SubLink
 	int			subLinkId;		/* ID (1..n); 0 if not MULTIEXPR */
 	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
 	/* originally specified operator name */
-	List	   *operName;
+	List	   *operName pg_node_attr(query_jumble_ignore);
 	/* subselect as Query* or raw parsetree */
 	Node	   *subselect;
 	int			location;		/* token location, or -1 if unknown */
@@ -1010,11 +1037,11 @@ typedef struct FieldSelect
 	Expr	   *arg;			/* input expression */
 	AttrNumber	fieldnum;		/* attribute number of field to extract */
 	/* type of the field (result type of this node) */
-	Oid			resulttype;
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* output typmod (usually -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation of the field */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 } FieldSelect;
 
 /* ----------------
@@ -1041,9 +1068,9 @@ typedef struct FieldStore
 	Expr	   *arg;			/* input tuple value */
 	List	   *newvals;		/* new value(s) for field(s) */
 	/* integer list of field attnums */
-	List	   *fieldnums;
+	List	   *fieldnums pg_node_attr(query_jumble_ignore);
 	/* type of result (same as type of arg) */
-	Oid			resulttype;
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 } FieldStore;
 
@@ -1066,11 +1093,11 @@ typedef struct RelabelType
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion expression */
 	/* output typmod (usually -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm relabelformat;
+	CoercionForm relabelformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } RelabelType;
 
@@ -1090,9 +1117,9 @@ typedef struct CoerceViaIO
 	Oid			resulttype;		/* output type of coercion */
 	/* output typmod is not stored, but is presumed -1 */
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coerceformat;
+	CoercionForm coerceformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } CoerceViaIO;
 
@@ -1116,11 +1143,11 @@ typedef struct ArrayCoerceExpr
 	Expr	   *elemexpr;		/* expression representing per-element work */
 	Oid			resulttype;		/* output type of coercion (an array type) */
 	/* output typmod (also element typmod) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coerceformat;
+	CoercionForm coerceformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } ArrayCoerceExpr;
 
@@ -1144,7 +1171,7 @@ typedef struct ConvertRowtypeExpr
 	Oid			resulttype;		/* output type (always a composite type) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 	/* how to display this node */
-	CoercionForm convertformat;
+	CoercionForm convertformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } ConvertRowtypeExpr;
 
@@ -1189,9 +1216,9 @@ typedef struct CaseExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			casetype;
+	Oid			casetype pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			casecollid;
+	Oid			casecollid pg_node_attr(query_jumble_ignore);
 	Expr	   *arg;			/* implicit equality comparison argument */
 	List	   *args;			/* the arguments (list of WHEN clauses) */
 	Expr	   *defresult;		/* the default result (ELSE clause) */
@@ -1234,9 +1261,9 @@ typedef struct CaseTestExpr
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 } CaseTestExpr;
 
 /*
@@ -1251,15 +1278,15 @@ typedef struct ArrayExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			array_typeid;
+	Oid			array_typeid pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			array_collid;
+	Oid			array_collid pg_node_attr(query_jumble_ignore);
 	/* common type of array elements */
-	Oid			element_typeid;
+	Oid			element_typeid pg_node_attr(query_jumble_ignore);
 	/* the array elements or sub-arrays */
 	List	   *elements;
 	/* true if elements are sub-arrays */
-	bool		multidims;
+	bool		multidims pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } ArrayExpr;
@@ -1291,7 +1318,7 @@ typedef struct RowExpr
 	List	   *args;			/* the fields */
 
 	/* RECORDOID or a composite type's ID */
-	Oid			row_typeid;
+	Oid			row_typeid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * row_typeid cannot be a domain over composite, only plain composite.  To
@@ -1307,10 +1334,10 @@ typedef struct RowExpr
 	 */
 
 	/* how to display this node */
-	CoercionForm row_format;
+	CoercionForm row_format pg_node_attr(query_jumble_ignore);
 
 	/* list of String, or NIL */
-	List	   *colnames;
+	List	   *colnames pg_node_attr(query_jumble_ignore);
 
 	int			location;		/* token location, or -1 if unknown */
 } RowExpr;
@@ -1347,11 +1374,11 @@ typedef struct RowCompareExpr
 	/* LT LE GE or GT, never EQ or NE */
 	RowCompareType rctype;
 	/* OID list of pairwise comparison ops */
-	List	   *opnos;
+	List	   *opnos pg_node_attr(query_jumble_ignore);
 	/* OID list of containing operator families */
-	List	   *opfamilies;
+	List	   *opfamilies pg_node_attr(query_jumble_ignore);
 	/* OID list of collations for comparisons */
-	List	   *inputcollids;
+	List	   *inputcollids pg_node_attr(query_jumble_ignore);
 	/* the left-hand input arguments */
 	List	   *largs;
 	/* the right-hand input arguments */
@@ -1365,9 +1392,9 @@ typedef struct CoalesceExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			coalescetype;
+	Oid			coalescetype pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			coalescecollid;
+	Oid			coalescecollid pg_node_attr(query_jumble_ignore);
 	/* the arguments */
 	List	   *args;
 	/* token location, or -1 if unknown */
@@ -1387,11 +1414,11 @@ typedef struct MinMaxExpr
 {
 	Expr		xpr;
 	/* common type of arguments and result */
-	Oid			minmaxtype;
+	Oid			minmaxtype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			minmaxcollid;
+	Oid			minmaxcollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* function to execute */
 	MinMaxOp	op;
 	/* the arguments */
@@ -1435,18 +1462,18 @@ typedef struct XmlExpr
 	/* xml function ID */
 	XmlExprOp	op;
 	/* name in xml(NAME foo ...) syntaxes */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* non-XML expressions for xml_attributes */
 	List	   *named_args;
 	/* parallel list of String values */
-	List	   *arg_names;
+	List	   *arg_names pg_node_attr(query_jumble_ignore);
 	/* list of expressions */
 	List	   *args;
 	/* DOCUMENT or CONTENT */
-	XmlOptionType xmloption;
+	XmlOptionType xmloption pg_node_attr(query_jumble_ignore);
 	/* target type/typmod for XMLSERIALIZE */
-	Oid			type;
-	int32		typmod;
+	Oid			type pg_node_attr(query_jumble_ignore);
+	int32		typmod pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } XmlExpr;
@@ -1481,7 +1508,7 @@ typedef struct NullTest
 	Expr	   *arg;			/* input expression */
 	NullTestType nulltesttype;	/* IS NULL, IS NOT NULL */
 	/* T to perform field-by-field null checks */
-	bool		argisrow;
+	bool		argisrow pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } NullTest;
 
@@ -1515,6 +1542,8 @@ typedef struct BooleanTest
  * checked will be determined.  If the value passes, it is returned as the
  * result; if not, an error is raised.  Note that this is equivalent to
  * RelabelType in the scenario where no constraints are applied.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct CoerceToDomain
 {
@@ -1522,11 +1551,11 @@ typedef struct CoerceToDomain
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* domain type ID (result type) */
 	/* output typmod (currently always -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coercionformat;
+	CoercionForm coercionformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } CoerceToDomain;
 
@@ -1545,9 +1574,9 @@ typedef struct CoerceToDomainValue
 	/* type for substituted value */
 	Oid			typeId;
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } CoerceToDomainValue;
@@ -1558,6 +1587,8 @@ typedef struct CoerceToDomainValue
  * This is not an executable expression: it must be replaced by the actual
  * column default expression during rewriting.  But it is convenient to
  * treat it as an expression node during parsing and rewriting.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct SetToDefault
 {
@@ -1565,9 +1596,9 @@ typedef struct SetToDefault
 	/* type for substituted value */
 	Oid			typeId;
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } SetToDefault;
@@ -1685,15 +1716,15 @@ typedef struct TargetEntry
 	/* attribute number (see notes above) */
 	AttrNumber	resno;
 	/* name of the column (could be NULL) */
-	char	   *resname;
+	char	   *resname pg_node_attr(query_jumble_ignore);
 	/* nonzero if referenced by a sort/group clause */
 	Index		ressortgroupref;
 	/* OID of column's source table */
-	Oid			resorigtbl;
+	Oid			resorigtbl pg_node_attr(query_jumble_ignore);
 	/* column's number in source table */
-	AttrNumber	resorigcol;
+	AttrNumber	resorigcol pg_node_attr(query_jumble_ignore);
 	/* set to true to eliminate the attribute from final target list */
-	bool		resjunk;
+	bool		resjunk pg_node_attr(query_jumble_ignore);
 } TargetEntry;
 
 
@@ -1776,13 +1807,13 @@ typedef struct JoinExpr
 	Node	   *larg;			/* left subtree */
 	Node	   *rarg;			/* right subtree */
 	/* USING clause, if any (list of String) */
-	List	   *usingClause;
+	List	   *usingClause pg_node_attr(query_jumble_ignore);
 	/* alias attached to USING clause, if any */
-	Alias	   *join_using_alias;
+	Alias	   *join_using_alias pg_node_attr(query_jumble_ignore);
 	/* qualifiers on join, if any */
 	Node	   *quals;
 	/* user-written alias clause, if any */
-	Alias	   *alias;
+	Alias	   *alias pg_node_attr(query_jumble_ignore);
 	/* RT index assigned for join, or 0 */
 	int			rtindex;
 } JoinExpr;
diff --git a/src/backend/nodes/README b/src/backend/nodes/README
index 489a67eb89..7cf6e3b041 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -51,6 +51,7 @@ FILES IN THIS DIRECTORY (src/backend/nodes/)
 	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
+	queryjumblefuncs.c - compute a node tree for query jumbling (*)
 
     (*) - Most functions in these files are generated by
     gen_node_support.pl and #include'd there.
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index b3c1ead496..27b9e4e630 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -121,6 +121,8 @@ my %node_type_info;
 my @no_copy;
 # node types we don't want equal support for
 my @no_equal;
+# node types we don't want jumble support for
+my @no_query_jumble;
 # node types we don't want read support for
 my @no_read;
 # node types we don't want read/write support for
@@ -155,12 +157,13 @@ 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);
-# Lists are specially treated in all four support files, too.
+# Lists are specially treated in all five support files, too.
 # (Ideally we'd mark List as "special copy/equal" not "no copy/equal".
 # But until there's other use-cases for that, just hot-wire the tests
 # that would need to distinguish.)
 push @no_copy,            qw(List);
 push @no_equal,           qw(List);
+push @no_query_jumble,          qw(List);
 push @special_read_write, qw(List);
 
 # Nodes with custom copy/equal implementations are skipped from
@@ -332,6 +335,10 @@ foreach my $infile (@ARGV)
 							push @no_copy,  $in_struct;
 							push @no_equal, $in_struct;
 						}
+						elsif ($attr eq 'no_query_jumble')
+						{
+							push @no_query_jumble, $in_struct;
+						}
 						elsif ($attr eq 'no_read')
 						{
 							push @no_read, $in_struct;
@@ -457,6 +464,8 @@ foreach my $infile (@ARGV)
 								equal_as_scalar
 								equal_ignore
 								equal_ignore_if_zero
+								query_jumble_ignore
+								query_jumble_location
 								read_write_ignore
 								write_only_relids
 								write_only_nondefault_pathtarget
@@ -1225,6 +1234,100 @@ close $ofs;
 close $rfs;
 
 
+# queryjumblefuncs.c
+
+push @output_files, 'queryjumblefuncs.funcs.c';
+open my $jff, '>', "$output_path/queryjumblefuncs.funcs.c$tmpext" or die $!;
+push @output_files, 'queryjumblefuncs.switch.c';
+open my $jfs, '>', "$output_path/queryjumblefuncs.switch.c$tmpext" or die $!;
+
+printf $jff $header_comment, 'queryjumblefuncs.funcs.c';
+printf $jfs $header_comment, 'queryjumblefuncs.switch.c';
+
+print $jff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @nodetag_only;
+	my $struct_no_query_jumble = (elem $n, @no_query_jumble);
+
+	print $jfs "\t\t\tcase T_${n}:\n"
+	  . "\t\t\t\t_jumble${n}(jstate, expr);\n"
+	  . "\t\t\t\tbreak;\n"
+	  unless $struct_no_query_jumble;
+
+	print $jff "
+static void
+_jumble${n}(JumbleState *jstate, Node *node)
+{
+\t${n} *expr = (${n} *) node;\n
+" unless $struct_no_query_jumble;
+
+	# 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 $query_jumble_ignore = $struct_no_query_jumble;
+		my $query_jumble_location = 0;
+
+		# extract per-field attributes
+		foreach my $a (@a)
+		{
+			if ($a eq 'query_jumble_ignore')
+			{
+				$query_jumble_ignore = 1;
+			}
+			elsif ($a eq 'query_jumble_location')
+			{
+				$query_jumble_location = 1;
+			}
+		}
+
+		# node type
+		if (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+			and elem $1, @node_types)
+		{
+			print $jff "\tJUMBLE_NODE($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			# Track the node's location only if directly requested.
+			if ($query_jumble_location)
+			{
+				print $jff "\tJUMBLE_LOCATION($f);\n"
+				  unless $query_jumble_ignore;
+			}
+		}
+		elsif ($t eq 'char*')
+		{
+			print $jff "\tJUMBLE_STRING($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		else
+		{
+			print $jff "\tJUMBLE_FIELD($f);\n"
+			  unless $query_jumble_ignore;
+		}
+	}
+
+	# Some nodes have no attributes like CheckPointStmt,
+	# so tweak things for empty contents.
+	if (scalar(@{ $node_type_info{$n}->{fields} }) == 0)
+	{
+		print $jff "\t(void) expr;\n"
+		  unless $struct_no_query_jumble;
+	}
+
+	print $jff "}
+" unless $struct_no_query_jumble;
+}
+
+close $jff;
+close $jfs;
+
 # now rename the temporary files to their final names
 foreach my $file (@output_files)
 {
diff --git a/src/backend/nodes/meson.build b/src/backend/nodes/meson.build
index 9230515e7f..31467a12d3 100644
--- a/src/backend/nodes/meson.build
+++ b/src/backend/nodes/meson.build
@@ -10,7 +10,6 @@ backend_sources += files(
   'nodes.c',
   'params.c',
   'print.c',
-  'queryjumblefuncs.c',
   'read.c',
   'tidbitmap.c',
   'value.c',
@@ -21,6 +20,7 @@ backend_sources += files(
 nodefunc_sources = files(
   'copyfuncs.c',
   'equalfuncs.c',
+  'queryjumblefuncs.c',
   'outfuncs.c',
   'readfuncs.c',
 )
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 16084842a3..278150fba0 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -21,7 +21,7 @@
  * tree(s) generated from the query.  The executor can then use this value
  * to blame query costs on the proper queryId.
  *
- * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
@@ -45,15 +45,12 @@ int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
 /* True when compute_query_id is ON, or AUTO and a module requests them */
 bool		query_id_enabled = false;
 
-static uint64 compute_utility_query_id(const char *query_text,
-									   int query_location, int query_len);
 static void AppendJumble(JumbleState *jstate,
 						 const unsigned char *item, Size size);
-static void JumbleQueryInternal(JumbleState *jstate, Query *query);
-static void JumbleRangeTable(JumbleState *jstate, List *rtable);
-static void JumbleRowMarks(JumbleState *jstate, List *rowMarks);
-static void JumbleExpr(JumbleState *jstate, Node *node);
 static void RecordConstLocation(JumbleState *jstate, int location);
+static void _jumbleNode(JumbleState *jstate, Node *node);
+static void _jumbleList(JumbleState *jstate, Node *node);
+static void _jumbleRangeTblEntry(JumbleState *jstate, Node *node);
 
 /*
  * Given a possibly multi-statement source string, confine our attention to the
@@ -105,38 +102,29 @@ JumbleQuery(Query *query, const char *querytext)
 
 	Assert(IsQueryIdEnabled());
 
-	if (query->utilityStmt)
-	{
-		query->queryId = compute_utility_query_id(querytext,
-												  query->stmt_location,
-												  query->stmt_len);
-	}
-	else
-	{
-		jstate = (JumbleState *) palloc(sizeof(JumbleState));
+	jstate = (JumbleState *) palloc(sizeof(JumbleState));
 
-		/* Set up workspace for query jumbling */
-		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-		jstate->jumble_len = 0;
-		jstate->clocations_buf_size = 32;
-		jstate->clocations = (LocationLen *)
-			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-		jstate->clocations_count = 0;
-		jstate->highest_extern_param_id = 0;
+	/* Set up workspace for query jumbling */
+	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+	jstate->jumble_len = 0;
+	jstate->clocations_buf_size = 32;
+	jstate->clocations = (LocationLen *)
+		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+	jstate->clocations_count = 0;
+	jstate->highest_extern_param_id = 0;
 
-		/* Compute query ID and mark the Query node with it */
-		JumbleQueryInternal(jstate, query);
-		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-														  jstate->jumble_len,
-														  0));
+	/* Compute query ID and mark the Query node with it */
+	_jumbleNode(jstate, (Node *) query);
+	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+													  jstate->jumble_len,
+													  0));
 
-		/*
-		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-		 * prevent confusion with the utility-statement case.
-		 */
-		if (query->queryId == UINT64CONST(0))
-			query->queryId = UINT64CONST(1);
-	}
+	/*
+	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+	 * prevent confusion with the utility-statement case.
+	 */
+	if (query->queryId == UINT64CONST(0))
+		query->queryId = UINT64CONST(1);
 
 	return jstate;
 }
@@ -154,34 +142,6 @@ EnableQueryId(void)
 		query_id_enabled = true;
 }
 
-/*
- * Compute a query identifier for the given utility query string.
- */
-static uint64
-compute_utility_query_id(const char *query_text, int query_location, int query_len)
-{
-	uint64		queryId;
-	const char *sql;
-
-	/*
-	 * Confine our attention to the relevant part of the string, if the query
-	 * is a portion of a multi-statement source string.
-	 */
-	sql = CleanQuerytext(query_text, &query_location, &query_len);
-
-	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
-											   query_len, 0));
-
-	/*
-	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
-	 * 2 instead, queryID 1 is already in use for normal statements.
-	 */
-	if (queryId == UINT64CONST(0))
-		queryId = UINT64CONST(2);
-
-	return queryId;
-}
-
 /*
  * AppendJumble: Append a value that is substantive in a given query to
  * the current jumble.
@@ -219,621 +179,6 @@ AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
 	jstate->jumble_len = jumble_len;
 }
 
-/*
- * Wrappers around AppendJumble to encapsulate details of serialization
- * of individual local variable elements.
- */
-#define APP_JUMB(item) \
-	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
-#define APP_JUMB_STRING(str) \
-	AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1)
-
-/*
- * JumbleQueryInternal: Selectively serialize the query tree, appending
- * significant data to the "query jumble" while ignoring nonsignificant data.
- *
- * Rule of thumb for what to include is that we should ignore anything not
- * semantically significant (such as alias names) as well as anything that can
- * be deduced from child nodes (else we'd just be double-hashing that piece
- * of information).
- */
-static void
-JumbleQueryInternal(JumbleState *jstate, Query *query)
-{
-	Assert(IsA(query, Query));
-	Assert(query->utilityStmt == NULL);
-
-	APP_JUMB(query->commandType);
-	/* resultRelation is usually predictable from commandType */
-	JumbleExpr(jstate, (Node *) query->cteList);
-	JumbleRangeTable(jstate, query->rtable);
-	JumbleExpr(jstate, (Node *) query->jointree);
-	JumbleExpr(jstate, (Node *) query->mergeActionList);
-	JumbleExpr(jstate, (Node *) query->targetList);
-	JumbleExpr(jstate, (Node *) query->onConflict);
-	JumbleExpr(jstate, (Node *) query->returningList);
-	JumbleExpr(jstate, (Node *) query->groupClause);
-	APP_JUMB(query->groupDistinct);
-	JumbleExpr(jstate, (Node *) query->groupingSets);
-	JumbleExpr(jstate, query->havingQual);
-	JumbleExpr(jstate, (Node *) query->windowClause);
-	JumbleExpr(jstate, (Node *) query->distinctClause);
-	JumbleExpr(jstate, (Node *) query->sortClause);
-	JumbleExpr(jstate, query->limitOffset);
-	JumbleExpr(jstate, query->limitCount);
-	APP_JUMB(query->limitOption);
-	JumbleRowMarks(jstate, query->rowMarks);
-	JumbleExpr(jstate, query->setOperations);
-}
-
-/*
- * Jumble a range table
- */
-static void
-JumbleRangeTable(JumbleState *jstate, List *rtable)
-{
-	ListCell   *lc;
-
-	foreach(lc, rtable)
-	{
-		RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-		APP_JUMB(rte->rtekind);
-		switch (rte->rtekind)
-		{
-			case RTE_RELATION:
-				APP_JUMB(rte->relid);
-				JumbleExpr(jstate, (Node *) rte->tablesample);
-				APP_JUMB(rte->inh);
-				break;
-			case RTE_SUBQUERY:
-				JumbleQueryInternal(jstate, rte->subquery);
-				break;
-			case RTE_JOIN:
-				APP_JUMB(rte->jointype);
-				break;
-			case RTE_FUNCTION:
-				JumbleExpr(jstate, (Node *) rte->functions);
-				break;
-			case RTE_TABLEFUNC:
-				JumbleExpr(jstate, (Node *) rte->tablefunc);
-				break;
-			case RTE_VALUES:
-				JumbleExpr(jstate, (Node *) rte->values_lists);
-				break;
-			case RTE_CTE:
-
-				/*
-				 * Depending on the CTE name here isn't ideal, but it's the
-				 * only info we have to identify the referenced WITH item.
-				 */
-				APP_JUMB_STRING(rte->ctename);
-				APP_JUMB(rte->ctelevelsup);
-				break;
-			case RTE_NAMEDTUPLESTORE:
-				APP_JUMB_STRING(rte->enrname);
-				break;
-			case RTE_RESULT:
-				break;
-			default:
-				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
-				break;
-		}
-	}
-}
-
-/*
- * Jumble a rowMarks list
- */
-static void
-JumbleRowMarks(JumbleState *jstate, List *rowMarks)
-{
-	ListCell   *lc;
-
-	foreach(lc, rowMarks)
-	{
-		RowMarkClause *rowmark = lfirst_node(RowMarkClause, lc);
-
-		if (!rowmark->pushedDown)
-		{
-			APP_JUMB(rowmark->rti);
-			APP_JUMB(rowmark->strength);
-			APP_JUMB(rowmark->waitPolicy);
-		}
-	}
-}
-
-/*
- * Jumble an expression tree
- *
- * In general this function should handle all the same node types that
- * expression_tree_walker() does, and therefore it's coded to be as parallel
- * to that function as possible.  However, since we are only invoked on
- * queries immediately post-parse-analysis, we need not handle node types
- * that only appear in planning.
- *
- * Note: the reason we don't simply use expression_tree_walker() is that the
- * point of that function is to support tree walkers that don't care about
- * most tree node types, but here we care about all types.  We should complain
- * about any unrecognized node type.
- */
-static void
-JumbleExpr(JumbleState *jstate, Node *node)
-{
-	ListCell   *temp;
-
-	if (node == NULL)
-		return;
-
-	/* Guard against stack overflow due to overly complex expressions */
-	check_stack_depth();
-
-	/*
-	 * We always emit the node's NodeTag, then any additional fields that are
-	 * considered significant, and then we recurse to any child nodes.
-	 */
-	APP_JUMB(node->type);
-
-	switch (nodeTag(node))
-	{
-		case T_Var:
-			{
-				Var		   *var = (Var *) node;
-
-				APP_JUMB(var->varno);
-				APP_JUMB(var->varattno);
-				APP_JUMB(var->varlevelsup);
-			}
-			break;
-		case T_Const:
-			{
-				Const	   *c = (Const *) node;
-
-				/* We jumble only the constant's type, not its value */
-				APP_JUMB(c->consttype);
-				/* Also, record its parse location for query normalization */
-				RecordConstLocation(jstate, c->location);
-			}
-			break;
-		case T_Param:
-			{
-				Param	   *p = (Param *) node;
-
-				APP_JUMB(p->paramkind);
-				APP_JUMB(p->paramid);
-				APP_JUMB(p->paramtype);
-				/* Also, track the highest external Param id */
-				if (p->paramkind == PARAM_EXTERN &&
-					p->paramid > jstate->highest_extern_param_id)
-					jstate->highest_extern_param_id = p->paramid;
-			}
-			break;
-		case T_Aggref:
-			{
-				Aggref	   *expr = (Aggref *) node;
-
-				APP_JUMB(expr->aggfnoid);
-				JumbleExpr(jstate, (Node *) expr->aggdirectargs);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggorder);
-				JumbleExpr(jstate, (Node *) expr->aggdistinct);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_GroupingFunc:
-			{
-				GroupingFunc *grpnode = (GroupingFunc *) node;
-
-				JumbleExpr(jstate, (Node *) grpnode->refs);
-				APP_JUMB(grpnode->agglevelsup);
-			}
-			break;
-		case T_WindowFunc:
-			{
-				WindowFunc *expr = (WindowFunc *) node;
-
-				APP_JUMB(expr->winfnoid);
-				APP_JUMB(expr->winref);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_SubscriptingRef:
-			{
-				SubscriptingRef *sbsref = (SubscriptingRef *) node;
-
-				JumbleExpr(jstate, (Node *) sbsref->refupperindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refassgnexpr);
-			}
-			break;
-		case T_FuncExpr:
-			{
-				FuncExpr   *expr = (FuncExpr *) node;
-
-				APP_JUMB(expr->funcid);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_NamedArgExpr:
-			{
-				NamedArgExpr *nae = (NamedArgExpr *) node;
-
-				APP_JUMB(nae->argnumber);
-				JumbleExpr(jstate, (Node *) nae->arg);
-			}
-			break;
-		case T_OpExpr:
-		case T_DistinctExpr:	/* struct-equivalent to OpExpr */
-		case T_NullIfExpr:		/* struct-equivalent to OpExpr */
-			{
-				OpExpr	   *expr = (OpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_ScalarArrayOpExpr:
-			{
-				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				APP_JUMB(expr->useOr);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_BoolExpr:
-			{
-				BoolExpr   *expr = (BoolExpr *) node;
-
-				APP_JUMB(expr->boolop);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_SubLink:
-			{
-				SubLink    *sublink = (SubLink *) node;
-
-				APP_JUMB(sublink->subLinkType);
-				APP_JUMB(sublink->subLinkId);
-				JumbleExpr(jstate, (Node *) sublink->testexpr);
-				JumbleQueryInternal(jstate, castNode(Query, sublink->subselect));
-			}
-			break;
-		case T_FieldSelect:
-			{
-				FieldSelect *fs = (FieldSelect *) node;
-
-				APP_JUMB(fs->fieldnum);
-				JumbleExpr(jstate, (Node *) fs->arg);
-			}
-			break;
-		case T_FieldStore:
-			{
-				FieldStore *fstore = (FieldStore *) node;
-
-				JumbleExpr(jstate, (Node *) fstore->arg);
-				JumbleExpr(jstate, (Node *) fstore->newvals);
-			}
-			break;
-		case T_RelabelType:
-			{
-				RelabelType *rt = (RelabelType *) node;
-
-				APP_JUMB(rt->resulttype);
-				JumbleExpr(jstate, (Node *) rt->arg);
-			}
-			break;
-		case T_CoerceViaIO:
-			{
-				CoerceViaIO *cio = (CoerceViaIO *) node;
-
-				APP_JUMB(cio->resulttype);
-				JumbleExpr(jstate, (Node *) cio->arg);
-			}
-			break;
-		case T_ArrayCoerceExpr:
-			{
-				ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node;
-
-				APP_JUMB(acexpr->resulttype);
-				JumbleExpr(jstate, (Node *) acexpr->arg);
-				JumbleExpr(jstate, (Node *) acexpr->elemexpr);
-			}
-			break;
-		case T_ConvertRowtypeExpr:
-			{
-				ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node;
-
-				APP_JUMB(crexpr->resulttype);
-				JumbleExpr(jstate, (Node *) crexpr->arg);
-			}
-			break;
-		case T_CollateExpr:
-			{
-				CollateExpr *ce = (CollateExpr *) node;
-
-				APP_JUMB(ce->collOid);
-				JumbleExpr(jstate, (Node *) ce->arg);
-			}
-			break;
-		case T_CaseExpr:
-			{
-				CaseExpr   *caseexpr = (CaseExpr *) node;
-
-				JumbleExpr(jstate, (Node *) caseexpr->arg);
-				foreach(temp, caseexpr->args)
-				{
-					CaseWhen   *when = lfirst_node(CaseWhen, temp);
-
-					JumbleExpr(jstate, (Node *) when->expr);
-					JumbleExpr(jstate, (Node *) when->result);
-				}
-				JumbleExpr(jstate, (Node *) caseexpr->defresult);
-			}
-			break;
-		case T_CaseTestExpr:
-			{
-				CaseTestExpr *ct = (CaseTestExpr *) node;
-
-				APP_JUMB(ct->typeId);
-			}
-			break;
-		case T_ArrayExpr:
-			JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements);
-			break;
-		case T_RowExpr:
-			JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args);
-			break;
-		case T_RowCompareExpr:
-			{
-				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-
-				APP_JUMB(rcexpr->rctype);
-				JumbleExpr(jstate, (Node *) rcexpr->largs);
-				JumbleExpr(jstate, (Node *) rcexpr->rargs);
-			}
-			break;
-		case T_CoalesceExpr:
-			JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args);
-			break;
-		case T_MinMaxExpr:
-			{
-				MinMaxExpr *mmexpr = (MinMaxExpr *) node;
-
-				APP_JUMB(mmexpr->op);
-				JumbleExpr(jstate, (Node *) mmexpr->args);
-			}
-			break;
-		case T_XmlExpr:
-			{
-				XmlExpr    *xexpr = (XmlExpr *) node;
-
-				APP_JUMB(xexpr->op);
-				JumbleExpr(jstate, (Node *) xexpr->named_args);
-				JumbleExpr(jstate, (Node *) xexpr->args);
-			}
-			break;
-		case T_NullTest:
-			{
-				NullTest   *nt = (NullTest *) node;
-
-				APP_JUMB(nt->nulltesttype);
-				JumbleExpr(jstate, (Node *) nt->arg);
-			}
-			break;
-		case T_BooleanTest:
-			{
-				BooleanTest *bt = (BooleanTest *) node;
-
-				APP_JUMB(bt->booltesttype);
-				JumbleExpr(jstate, (Node *) bt->arg);
-			}
-			break;
-		case T_CoerceToDomain:
-			{
-				CoerceToDomain *cd = (CoerceToDomain *) node;
-
-				APP_JUMB(cd->resulttype);
-				JumbleExpr(jstate, (Node *) cd->arg);
-			}
-			break;
-		case T_CoerceToDomainValue:
-			{
-				CoerceToDomainValue *cdv = (CoerceToDomainValue *) node;
-
-				APP_JUMB(cdv->typeId);
-			}
-			break;
-		case T_SetToDefault:
-			{
-				SetToDefault *sd = (SetToDefault *) node;
-
-				APP_JUMB(sd->typeId);
-			}
-			break;
-		case T_CurrentOfExpr:
-			{
-				CurrentOfExpr *ce = (CurrentOfExpr *) node;
-
-				APP_JUMB(ce->cvarno);
-				if (ce->cursor_name)
-					APP_JUMB_STRING(ce->cursor_name);
-				APP_JUMB(ce->cursor_param);
-			}
-			break;
-		case T_NextValueExpr:
-			{
-				NextValueExpr *nve = (NextValueExpr *) node;
-
-				APP_JUMB(nve->seqid);
-				APP_JUMB(nve->typeId);
-			}
-			break;
-		case T_InferenceElem:
-			{
-				InferenceElem *ie = (InferenceElem *) node;
-
-				APP_JUMB(ie->infercollid);
-				APP_JUMB(ie->inferopclass);
-				JumbleExpr(jstate, ie->expr);
-			}
-			break;
-		case T_TargetEntry:
-			{
-				TargetEntry *tle = (TargetEntry *) node;
-
-				APP_JUMB(tle->resno);
-				APP_JUMB(tle->ressortgroupref);
-				JumbleExpr(jstate, (Node *) tle->expr);
-			}
-			break;
-		case T_RangeTblRef:
-			{
-				RangeTblRef *rtr = (RangeTblRef *) node;
-
-				APP_JUMB(rtr->rtindex);
-			}
-			break;
-		case T_JoinExpr:
-			{
-				JoinExpr   *join = (JoinExpr *) node;
-
-				APP_JUMB(join->jointype);
-				APP_JUMB(join->isNatural);
-				APP_JUMB(join->rtindex);
-				JumbleExpr(jstate, join->larg);
-				JumbleExpr(jstate, join->rarg);
-				JumbleExpr(jstate, join->quals);
-			}
-			break;
-		case T_FromExpr:
-			{
-				FromExpr   *from = (FromExpr *) node;
-
-				JumbleExpr(jstate, (Node *) from->fromlist);
-				JumbleExpr(jstate, from->quals);
-			}
-			break;
-		case T_OnConflictExpr:
-			{
-				OnConflictExpr *conf = (OnConflictExpr *) node;
-
-				APP_JUMB(conf->action);
-				JumbleExpr(jstate, (Node *) conf->arbiterElems);
-				JumbleExpr(jstate, conf->arbiterWhere);
-				JumbleExpr(jstate, (Node *) conf->onConflictSet);
-				JumbleExpr(jstate, conf->onConflictWhere);
-				APP_JUMB(conf->constraint);
-				APP_JUMB(conf->exclRelIndex);
-				JumbleExpr(jstate, (Node *) conf->exclRelTlist);
-			}
-			break;
-		case T_MergeAction:
-			{
-				MergeAction *mergeaction = (MergeAction *) node;
-
-				APP_JUMB(mergeaction->matched);
-				APP_JUMB(mergeaction->commandType);
-				JumbleExpr(jstate, mergeaction->qual);
-				JumbleExpr(jstate, (Node *) mergeaction->targetList);
-			}
-			break;
-		case T_List:
-			foreach(temp, (List *) node)
-			{
-				JumbleExpr(jstate, (Node *) lfirst(temp));
-			}
-			break;
-		case T_IntList:
-			foreach(temp, (List *) node)
-			{
-				APP_JUMB(lfirst_int(temp));
-			}
-			break;
-		case T_SortGroupClause:
-			{
-				SortGroupClause *sgc = (SortGroupClause *) node;
-
-				APP_JUMB(sgc->tleSortGroupRef);
-				APP_JUMB(sgc->eqop);
-				APP_JUMB(sgc->sortop);
-				APP_JUMB(sgc->nulls_first);
-			}
-			break;
-		case T_GroupingSet:
-			{
-				GroupingSet *gsnode = (GroupingSet *) node;
-
-				JumbleExpr(jstate, (Node *) gsnode->content);
-			}
-			break;
-		case T_WindowClause:
-			{
-				WindowClause *wc = (WindowClause *) node;
-
-				APP_JUMB(wc->winref);
-				APP_JUMB(wc->frameOptions);
-				JumbleExpr(jstate, (Node *) wc->partitionClause);
-				JumbleExpr(jstate, (Node *) wc->orderClause);
-				JumbleExpr(jstate, wc->startOffset);
-				JumbleExpr(jstate, wc->endOffset);
-			}
-			break;
-		case T_CommonTableExpr:
-			{
-				CommonTableExpr *cte = (CommonTableExpr *) node;
-
-				/* we store the string name because RTE_CTE RTEs need it */
-				APP_JUMB_STRING(cte->ctename);
-				APP_JUMB(cte->ctematerialized);
-				JumbleQueryInternal(jstate, castNode(Query, cte->ctequery));
-			}
-			break;
-		case T_SetOperationStmt:
-			{
-				SetOperationStmt *setop = (SetOperationStmt *) node;
-
-				APP_JUMB(setop->op);
-				APP_JUMB(setop->all);
-				JumbleExpr(jstate, setop->larg);
-				JumbleExpr(jstate, setop->rarg);
-			}
-			break;
-		case T_RangeTblFunction:
-			{
-				RangeTblFunction *rtfunc = (RangeTblFunction *) node;
-
-				JumbleExpr(jstate, rtfunc->funcexpr);
-			}
-			break;
-		case T_TableFunc:
-			{
-				TableFunc  *tablefunc = (TableFunc *) node;
-
-				JumbleExpr(jstate, tablefunc->docexpr);
-				JumbleExpr(jstate, tablefunc->rowexpr);
-				JumbleExpr(jstate, (Node *) tablefunc->colexprs);
-			}
-			break;
-		case T_TableSampleClause:
-			{
-				TableSampleClause *tsc = (TableSampleClause *) node;
-
-				APP_JUMB(tsc->tsmhandler);
-				JumbleExpr(jstate, (Node *) tsc->args);
-				JumbleExpr(jstate, (Node *) tsc->repeatable);
-			}
-			break;
-		default:
-			/* Only a warning, since we can stumble along anyway */
-			elog(WARNING, "unrecognized node type: %d",
-				 (int) nodeTag(node));
-			break;
-	}
-}
-
 /*
  * Record location of constant within query string of query tree
  * that is currently being walked.
@@ -859,3 +204,155 @@ RecordConstLocation(JumbleState *jstate, int location)
 		jstate->clocations_count++;
 	}
 }
+
+#define JUMBLE_NODE(item) \
+	_jumbleNode(jstate, (Node *) expr->item)
+#define JUMBLE_LOCATION(location) \
+	RecordConstLocation(jstate, expr->location)
+#define JUMBLE_FIELD(item) \
+	AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item))
+#define JUMBLE_FIELD_SINGLE(item) \
+	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
+#define JUMBLE_STRING(str) \
+do { \
+	if (expr->str) \
+		AppendJumble(jstate, (const unsigned char *) (expr->str), strlen(expr->str) + 1); \
+} while(0)
+
+#include "queryjumblefuncs.funcs.c"
+
+static void
+_jumbleNode(JumbleState *jstate, Node *node)
+{
+	Node	   *expr = node;
+
+	if (expr == NULL)
+		return;
+
+	/* Guard against stack overflow due to overly complex expressions */
+	check_stack_depth();
+
+	/*
+	 * We always emit the node's NodeTag, then any additional fields that are
+	 * considered significant, and then we recurse to any child nodes.
+	 */
+	JUMBLE_FIELD(type);
+
+	switch (nodeTag(expr))
+	{
+#include "queryjumblefuncs.switch.c"
+
+		case T_List:
+		case T_IntList:
+		case T_OidList:
+		case T_XidList:
+			_jumbleList(jstate, expr);
+			break;
+
+		case T_RangeTblEntry:
+			_jumbleRangeTblEntry(jstate, expr);
+			break;
+
+		default:
+			/* Only a warning, since we can stumble along anyway */
+			elog(WARNING, "unrecognized node type: %d",
+				 (int) nodeTag(expr));
+			break;
+	}
+
+	/* Special cases */
+	switch (nodeTag(expr))
+	{
+		case T_Param:
+			{
+				Param	   *p = (Param *) node;
+
+				/* Also, track the highest external Param id */
+				if (p->paramkind == PARAM_EXTERN &&
+					p->paramid > jstate->highest_extern_param_id)
+					jstate->highest_extern_param_id = p->paramid;
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+_jumbleList(JumbleState *jstate, Node *node)
+{
+	List	   *expr = (List *) node;
+	ListCell   *l;
+
+	switch (expr->type)
+	{
+		case T_List:
+			foreach(l, expr)
+				_jumbleNode(jstate, lfirst(l));
+			break;
+		case T_IntList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_int(l));
+			break;
+		case T_OidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_oid(l));
+			break;
+		case T_XidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_xid(l));
+			break;
+		default:
+			elog(ERROR, "unrecognized list node type: %d",
+				 (int) expr->type);
+			return;
+	}
+}
+
+static void
+_jumbleRangeTblEntry(JumbleState *jstate, Node *node)
+{
+	RangeTblEntry *expr = (RangeTblEntry *) node;
+
+	JUMBLE_FIELD(rtekind);
+	switch (expr->rtekind)
+	{
+		case RTE_RELATION:
+			JUMBLE_FIELD(relid);
+			JUMBLE_NODE(tablesample);
+			JUMBLE_FIELD(inh);
+			break;
+		case RTE_SUBQUERY:
+			JUMBLE_NODE(subquery);
+			break;
+		case RTE_JOIN:
+			JUMBLE_FIELD(jointype);
+			break;
+		case RTE_FUNCTION:
+			JUMBLE_NODE(functions);
+			break;
+		case RTE_TABLEFUNC:
+			JUMBLE_NODE(tablefunc);
+			break;
+		case RTE_VALUES:
+			JUMBLE_NODE(values_lists);
+			break;
+		case RTE_CTE:
+
+			/*
+			 * Depending on the CTE name here isn't ideal, but it's the only
+			 * info we have to identify the referenced WITH item.
+			 */
+			JUMBLE_STRING(ctename);
+			JUMBLE_FIELD(ctelevelsup);
+			break;
+		case RTE_NAMEDTUPLESTORE:
+			JUMBLE_STRING(enrname);
+			break;
+		case RTE_RESULT:
+			break;
+		default:
+			elog(ERROR, "unrecognized RTE kind: %d", (int) expr->rtekind);
+			break;
+	}
+}
-- 
2.39.0

v5-0004-Add-GUC-utility_query_id.patchtext/x-diff; charset=us-asciiDownload
From 700f7895a189fca570d1b0088c844c776636c811 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 20 Jan 2023 13:27:29 +0900
Subject: [PATCH v5 4/4] Add GUC utility_query_id

This GUC has two modes to control the computation method of query IDs
for utilities:
- 'string', the default, to hash the string query.
- 'jumble', to use the parsed tree.
---
 src/include/nodes/queryjumble.h               |  7 ++
 src/backend/nodes/queryjumblefuncs.c          | 81 ++++++++++++++-----
 src/backend/utils/misc/guc_tables.c           | 16 ++++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 doc/src/sgml/config.sgml                      | 18 +++++
 .../expected/pg_stat_statements.out           | 31 +++++++
 .../sql/pg_stat_statements.sql                | 17 ++++
 7 files changed, 151 insertions(+), 20 deletions(-)

diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index 204b8f74fd..261aea6bcf 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -59,8 +59,15 @@ enum ComputeQueryIdType
 	COMPUTE_QUERY_ID_REGRESS
 };
 
+enum UtilityQueryIdType
+{
+	UTILITY_QUERY_ID_STRING,
+	UTILITY_QUERY_ID_JUMBLE
+};
+
 /* GUC parameters */
 extern PGDLLIMPORT int compute_query_id;
+extern PGDLLIMPORT int utility_query_id;
 
 
 extern const char *CleanQuerytext(const char *query, int *location, int *len);
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 278150fba0..dd9ab8f353 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -41,12 +41,15 @@
 
 /* GUC parameters */
 int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
+int			utility_query_id = UTILITY_QUERY_ID_STRING;
 
 /* True when compute_query_id is ON, or AUTO and a module requests them */
 bool		query_id_enabled = false;
 
 static void AppendJumble(JumbleState *jstate,
 						 const unsigned char *item, Size size);
+static uint64 compute_utility_query_id(const char *query_text,
+									   int query_location, int query_len);
 static void RecordConstLocation(JumbleState *jstate, int location);
 static void _jumbleNode(JumbleState *jstate, Node *node);
 static void _jumbleList(JumbleState *jstate, Node *node);
@@ -102,29 +105,39 @@ JumbleQuery(Query *query, const char *querytext)
 
 	Assert(IsQueryIdEnabled());
 
-	jstate = (JumbleState *) palloc(sizeof(JumbleState));
+	if (query->utilityStmt &&
+		compute_query_id == UTILITY_QUERY_ID_STRING)
+	{
+		query->queryId = compute_utility_query_id(querytext,
+												  query->stmt_location,
+												  query->stmt_len);
+	}
+	else
+	{
+		jstate = (JumbleState *) palloc(sizeof(JumbleState));
 
-	/* Set up workspace for query jumbling */
-	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-	jstate->jumble_len = 0;
-	jstate->clocations_buf_size = 32;
-	jstate->clocations = (LocationLen *)
-		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-	jstate->clocations_count = 0;
-	jstate->highest_extern_param_id = 0;
+		/* Set up workspace for query jumbling */
+		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+		jstate->jumble_len = 0;
+		jstate->clocations_buf_size = 32;
+		jstate->clocations = (LocationLen *)
+			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+		jstate->clocations_count = 0;
+		jstate->highest_extern_param_id = 0;
 
-	/* Compute query ID and mark the Query node with it */
-	_jumbleNode(jstate, (Node *) query);
-	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-													  jstate->jumble_len,
-													  0));
+		/* Compute query ID and mark the Query node with it */
+		_jumbleNode(jstate, (Node *) query);
+		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+														  jstate->jumble_len,
+														  0));
 
-	/*
-	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-	 * prevent confusion with the utility-statement case.
-	 */
-	if (query->queryId == UINT64CONST(0))
-		query->queryId = UINT64CONST(1);
+		/*
+		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+		 * prevent confusion with the utility-statement case.
+		 */
+		if (query->queryId == UINT64CONST(0))
+			query->queryId = UINT64CONST(1);
+	}
 
 	return jstate;
 }
@@ -142,6 +155,34 @@ EnableQueryId(void)
 		query_id_enabled = true;
 }
 
+/*
+ * Compute a query identifier for the given utility query string.
+ */
+static uint64
+compute_utility_query_id(const char *query_text, int query_location, int query_len)
+{
+	uint64		queryId;
+	const char *sql;
+
+	/*
+	 * Confine our attention to the relevant part of the string, if the query
+	 * is a portion of a multi-statement source string.
+	 */
+	sql = CleanQuerytext(query_text, &query_location, &query_len);
+
+	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
+											   query_len, 0));
+
+	/*
+	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
+	 * 2 instead, queryID 1 is already in use for normal statements.
+	 */
+	if (queryId == UINT64CONST(0))
+		queryId = UINT64CONST(2);
+
+	return queryId;
+}
+
 /*
  * AppendJumble: Append a value that is substantive in a given query to
  * the current jumble.
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4ac808ed22..97619c4e1d 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -294,6 +294,12 @@ static const struct config_enum_entry compute_query_id_options[] = {
 	{NULL, 0, false}
 };
 
+static const struct config_enum_entry utility_query_id_options[] = {
+	{"string", UTILITY_QUERY_ID_STRING, false},
+	{"jumble", UTILITY_QUERY_ID_JUMBLE, false},
+	{NULL, 0, false}
+};
+
 /*
  * Although only "on", "off", and "partition" are documented, we
  * accept all the likely variants of "on" and "off".
@@ -4574,6 +4580,16 @@ struct config_enum ConfigureNamesEnum[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"utility_query_id", PGC_SUSET, STATS_MONITORING,
+			gettext_noop("Controls method computing query ID for utilities."),
+			NULL
+		},
+		&utility_query_id,
+		UTILITY_QUERY_ID_STRING, utility_query_id_options,
+		NULL, NULL, NULL
+	},
+
 	{
 		{"constraint_exclusion", PGC_USERSET, QUERY_TUNING_OTHER,
 			gettext_noop("Enables the planner to use constraints to optimize queries."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..bbf95af59d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -623,6 +623,7 @@
 # - Monitoring -
 
 #compute_query_id = auto
+#utility_query_id = string		# string, jumble
 #log_statement_stats = off
 #log_parser_stats = off
 #log_planner_stats = off
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index dc9b78b0b7..e1e7a134cf 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8237,6 +8237,24 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-utility-query-id" xreflabel="utility_query_id">
+      <term><varname>utility_query_id</varname> (<type>enum</type>)
+      <indexterm>
+       <primary><varname>utility_query_id</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Controls the method used to compute the query identifier of a utility
+        query. Valid values are <literal>string</literal> to use a hash of the
+        query string and <literal>jumble</literal> to compute the query
+        identifier depending on the parsed tree of the utility query (less
+        performant, but allows for more parameterization of the queries
+        involved). The default is <literal>string</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-log-statement-stats">
       <term><varname>log_statement_stats</varname> (<type>boolean</type>)
       <indexterm>
diff --git a/contrib/pg_stat_statements/expected/pg_stat_statements.out b/contrib/pg_stat_statements/expected/pg_stat_statements.out
index 9ac5c87c3a..8bdf8beec3 100644
--- a/contrib/pg_stat_statements/expected/pg_stat_statements.out
+++ b/contrib/pg_stat_statements/expected/pg_stat_statements.out
@@ -554,6 +554,7 @@ DROP TABLE pgss_a, pgss_b CASCADE;
 -- utility commands
 --
 SET pg_stat_statements.track_utility = TRUE;
+SET utility_query_id = 'string';
 SELECT pg_stat_statements_reset();
  pg_stat_statements_reset 
 --------------------------
@@ -592,6 +593,36 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
  SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     0 |    0
 (9 rows)
 
+SELECT pg_stat_statements_reset();
+ pg_stat_statements_reset 
+--------------------------
+ 
+(1 row)
+
+SET utility_query_id = 'jumble';
+-- These queries have a different string, but the same parsing
+-- representation.
+Begin;
+Create Table test_utility_query (a int);
+Drop Table test_utility_query;
+Commit;
+BEGIN;
+CREATE TABLE test_utility_query (a int);
+DROP TABLE test_utility_query;
+COMMIT;
+SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
+                                    query                                     | calls | rows 
+------------------------------------------------------------------------------+-------+------
+ Begin                                                                        |     2 |    0
+ Commit                                                                       |     2 |    0
+ Create Table test_utility_query (a int)                                      |     2 |    0
+ Drop Table test_utility_query                                                |     2 |    0
+ SELECT pg_stat_statements_reset()                                            |     1 |    1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     0 |    0
+ SET utility_query_id = 'jumble'                                              |     1 |    0
+(7 rows)
+
+RESET utility_query_id;
 --
 -- Track the total number of rows retrieved or affected by the utility
 -- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW,
diff --git a/contrib/pg_stat_statements/sql/pg_stat_statements.sql b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
index 8f5c866225..81d663f81c 100644
--- a/contrib/pg_stat_statements/sql/pg_stat_statements.sql
+++ b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
@@ -258,6 +258,7 @@ DROP TABLE pgss_a, pgss_b CASCADE;
 -- utility commands
 --
 SET pg_stat_statements.track_utility = TRUE;
+SET utility_query_id = 'string';
 SELECT pg_stat_statements_reset();
 
 SELECT 1;
@@ -272,6 +273,22 @@ DROP FUNCTION PLUS_TWO(INTEGER);
 
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 
+SELECT pg_stat_statements_reset();
+SET utility_query_id = 'jumble';
+-- These queries have a different string, but the same parsing
+-- representation.
+Begin;
+Create Table test_utility_query (a int);
+Drop Table test_utility_query;
+Commit;
+BEGIN;
+CREATE TABLE test_utility_query (a int);
+DROP TABLE test_utility_query;
+COMMIT;
+
+SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
+RESET utility_query_id;
+
 --
 -- Track the total number of rows retrieved or affected by the utility
 -- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW,
-- 
2.39.0

#16Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Michael Paquier (#15)
1 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On 21.01.23 04:35, Michael Paquier wrote:

I'll read the 0003 again more carefully. I haven't studied the new 0004
yet.

Thanks, again. Rebased version attached.

A couple of small fixes are attached.

There is something weird in _jumbleNode(). There are two switch
(nodeTag(expr)) statements. Maybe that's intentional, but then it
should be commented better, because now it looks more like an editing
mistake.

The handling of T_RangeTblEntry could be improved. In other contexts we
have for example a custom_copy attribute, which generates the switch
entry but not the function. Something like this could be useful here too.

Otherwise, this looks ok. I haven't checked whether it maintains the
exact behavior from before. What is the test coverage situation for this?

For the 0004 patch, it should be documented why one would want one
behavior or the other. That's totally unclear right now.

I think if we are going to accept 0004, then it might be better to
combine it with 0003. Otherwise, 0004 is just undoing a lot of the code
structure changes in JumbleQuery() that 0003 did.

Attachments:

0001-fixup-Support-for-automated-query-jumble-with-all-No.patchtext/plain; charset=UTF-8; name=0001-fixup-Support-for-automated-query-jumble-with-all-No.patchDownload
From 0597275ef439f070b1bd32ba25ca3581cbc2af30 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 23 Jan 2023 14:16:39 +0100
Subject: [PATCH] fixup! Support for automated query jumble with all Nodes

---
 src/backend/nodes/queryjumblefuncs.c | 2 +-
 src/include/nodes/nodes.h            | 4 ++--
 src/include/nodes/primnodes.h        | 1 -
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 278150fba0..bdb5636b1b 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -21,7 +21,7 @@
  * tree(s) generated from the query.  The executor can then use this value
  * to blame query costs on the proper queryId.
  *
- * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6ecd944a90..e7f92df7cb 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -59,12 +59,12 @@ typedef enum NodeTag
  *
  * - no_copy_equal: Shorthand for both no_copy and no_equal.
  *
- * - no_query_jumble: Does not support jumble() at all.
+ * - no_query_jumble: Does not support JumbleQuery() at all.
  *
  * - no_read: Does not support nodeRead() at all.
  *
  * - nodetag_only: Does not support copyObject(), equal(), outNode(),
- *   or nodeRead().
+ *   or nodeRead(). XXX
  *
  * - special_read_write: Has special treatment in outNode() and nodeRead().
  *
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f81518c2fd..9ea819a6a7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -799,7 +799,6 @@ typedef OpExpr NullIfExpr;
  * filled in right away, so will be ignored for equality if they are not set
  * yet.
  *
- *
  * OID entries of the internal function types are irrelevant for the query
  * jumbling, but the operator OID and the arguments are.
  */
-- 
2.39.1

#17Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#16)
2 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Mon, Jan 23, 2023 at 02:27:13PM +0100, Peter Eisentraut wrote:

A couple of small fixes are attached.

Thanks.

There is something weird in _jumbleNode(). There are two switch
(nodeTag(expr)) statements. Maybe that's intentional, but then it should be
commented better, because now it looks more like an editing mistake.

This one is intentional, so as it is possible to track correctly the
highest param ID found while browsing the nodes. IMO it would be
confusing to add that into gen_node_support.pl. Another thing that
could be done is to switch Param to have a custom implementation, like
RangeTblEntry, though this removes the automation around the creation
of _jumbleParam(). I have clarified the comments around that.

The handling of T_RangeTblEntry could be improved. In other contexts we
have for example a custom_copy attribute, which generates the switch entry
but not the function. Something like this could be useful here too.

Hmm. Okay. Fine by me.

Otherwise, this looks ok. I haven't checked whether it maintains the exact
behavior from before. What is the test coverage situation for this?

0003 taken in isolation has some minimal coverage through
pg_stat_statements, though it turns around 15% with compute_query_id =
auto that would enforce the jumbling path only when pg_stat_statements
uses it. Still, my plan here is to enforce the loading of
pg_stat_statements with compute_query_id = regress and
utility_query_id = jumble (if needed) in a new buildfarm machine,
because that's the cheapest path. An extra possibility is to have
pg_regress kicked in a new TAP test with these settings, but that's
costly and we have already two of these :/ Another possibility is to
plug in that into 027_stream_regress or the pg_upgrade test suite with
new settings :/

Anyway, the regression tests of pg_stat_statements should be extended
a bit to cover more node types by default (Say COPY with DMLs for the
InsertStmt & co) to look at how these are showing up once normalized
using their parsed query, and we don't do much around that now.
Normalizing more DDLs should use this path, as well.

For the 0004 patch, it should be documented why one would want one behavior
or the other. That's totally unclear right now.

I am not 100% sure whether we should have this part at the end, but as
an exit path in case somebody complains about the extra load with the
automated jumbling compared to the hash of the query strings, that can
be used as a backup. Anyway, attached is what would be a
clarification.

I think if we are going to accept 0004, then it might be better to combine
it with 0003. Otherwise, 0004 is just undoing a lot of the code structure
changes in JumbleQuery() that 0003 did.

Makes sense. That would be my intention if 0004 is the most
acceptable and splitting things makes things a bit easier to review.
--
Michael

Attachments:

v6-0003-Support-for-automated-query-jumble-with-all-Nodes.patchtext/x-diff; charset=us-asciiDownload
From b6bb376b9181739e8325f48721ee8774f126be86 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 24 Jan 2023 15:29:06 +0900
Subject: [PATCH v6 3/4] Support for automated query jumble with all Nodes

This applies query jumbling in a consistent way to all the Nodes,
including DDLs & friends.
---
 src/include/nodes/bitmapset.h         |   2 +-
 src/include/nodes/nodes.h             |  13 +-
 src/include/nodes/parsenodes.h        | 126 ++--
 src/include/nodes/primnodes.h         | 268 ++++----
 src/backend/nodes/README              |   1 +
 src/backend/nodes/gen_node_support.pl | 114 +++-
 src/backend/nodes/meson.build         |   2 +-
 src/backend/nodes/queryjumblefuncs.c  | 852 ++++++--------------------
 8 files changed, 519 insertions(+), 859 deletions(-)

diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 0dca6bc5fa..3d2225e1ae 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -50,7 +50,7 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
-	pg_node_attr(custom_copy_equal, special_read_write)
+	pg_node_attr(custom_copy_equal, special_read_write, no_query_jumble)
 
 	NodeTag		type;
 	int			nwords;			/* number of words in array */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 10752e8011..d7a9e38436 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -53,16 +53,20 @@ typedef enum NodeTag
  * - custom_read_write: Has custom implementations in outfuncs.c and
  *   readfuncs.c.
  *
+ * - custom_query_jumble: Has custom implementation in queryjumblefuncs.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_query_jumble: Does not support JumbleQuery() at all.
+ *
  * - no_read: Does not support nodeRead() at all.
  *
- * - nodetag_only: Does not support copyObject(), equal(), outNode(),
- *   or nodeRead().
+ * - nodetag_only: Does not support copyObject(), equal(), jumbleQuery()
+ *   outNode() or nodeRead().
  *
  * - special_read_write: Has special treatment in outNode() and nodeRead().
  *
@@ -97,6 +101,11 @@ typedef enum NodeTag
  * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
  *   (Otherwise, compare normally.)
  *
+ * - query_jumble_ignore: Ignore the field for the query jumbling.
+ *
+ * - query_jumble_location: Mark the field as a location to track.  This is
+ *   only allowed for integer fields that include "location" in their name.
+ *
  * - 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
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 89335d95e7..f99fb5e909 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -116,6 +116,11 @@ typedef uint64 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.
+ *
+ *	  All the fields ignored for the query jumbling are not semantically
+ *	  significant (such as alias names), as is ignored anything that can
+ *	  be deduced from child nodes (else we'd just be double-hashing that
+ *	  piece of information).
  */
 typedef struct Query
 {
@@ -124,45 +129,47 @@ typedef struct Query
 	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
 
 	/* where did I come from? */
-	QuerySource querySource;
+	QuerySource querySource pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * query identifier (can be set by plugins); ignored for equal, as it
-	 * might not be set; also not stored
+	 * might not be set; also not stored.  This is the result of the query
+	 * jumble, hence ignored.
 	 */
-	uint64		queryId pg_node_attr(equal_ignore, read_write_ignore, read_as(0));
+	uint64		queryId pg_node_attr(equal_ignore, query_jumble_ignore, read_write_ignore, read_as(0));
 
 	/* do I set the command result tag? */
-	bool		canSetTag;
+	bool		canSetTag pg_node_attr(query_jumble_ignore);
 
 	Node	   *utilityStmt;	/* non-null if commandType == CMD_UTILITY */
 
 	/*
 	 * rtable index of target relation for INSERT/UPDATE/DELETE/MERGE; 0 for
-	 * SELECT.
+	 * SELECT.  This is ignored in the query jumble as unrelated to the
+	 * compilation of the query ID.
 	 */
-	int			resultRelation;
+	int			resultRelation pg_node_attr(query_jumble_ignore);
 
 	/* has aggregates in tlist or havingQual */
-	bool		hasAggs;
+	bool		hasAggs pg_node_attr(query_jumble_ignore);
 	/* has window functions in tlist */
-	bool		hasWindowFuncs;
+	bool		hasWindowFuncs pg_node_attr(query_jumble_ignore);
 	/* has set-returning functions in tlist */
-	bool		hasTargetSRFs;
+	bool		hasTargetSRFs pg_node_attr(query_jumble_ignore);
 	/* has subquery SubLink */
-	bool		hasSubLinks;
+	bool		hasSubLinks pg_node_attr(query_jumble_ignore);
 	/* distinctClause is from DISTINCT ON */
-	bool		hasDistinctOn;
+	bool		hasDistinctOn pg_node_attr(query_jumble_ignore);
 	/* WITH RECURSIVE was specified */
-	bool		hasRecursive;
+	bool		hasRecursive pg_node_attr(query_jumble_ignore);
 	/* has INSERT/UPDATE/DELETE in WITH */
-	bool		hasModifyingCTE;
+	bool		hasModifyingCTE pg_node_attr(query_jumble_ignore);
 	/* FOR [KEY] UPDATE/SHARE was specified */
-	bool		hasForUpdate;
+	bool		hasForUpdate pg_node_attr(query_jumble_ignore);
 	/* rewriter has applied some RLS policy */
-	bool		hasRowSecurity;
+	bool		hasRowSecurity pg_node_attr(query_jumble_ignore);
 	/* is a RETURN statement */
-	bool		isReturn;
+	bool		isReturn pg_node_attr(query_jumble_ignore);
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
@@ -172,18 +179,18 @@ typedef struct Query
 	 * list of RTEPermissionInfo nodes for the rtable entries having
 	 * perminfoindex > 0
 	 */
-	List	   *rteperminfos;
+	List	   *rteperminfos pg_node_attr(query_jumble_ignore);
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
 	List	   *mergeActionList;	/* list of actions for MERGE (only) */
 	/* whether to use outer join */
-	bool		mergeUseOuterJoin;
+	bool		mergeUseOuterJoin pg_node_attr(query_jumble_ignore);
 
 	List	   *targetList;		/* target list (of TargetEntry) */
 
 	/* OVERRIDING clause */
-	OverridingKind override;
+	OverridingKind override pg_node_attr(query_jumble_ignore);
 
 	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
 
@@ -215,10 +222,10 @@ typedef struct Query
 	 * A list of pg_constraint OIDs that the query depends on to be
 	 * semantically valid
 	 */
-	List	   *constraintDeps;
+	List	   *constraintDeps pg_node_attr(query_jumble_ignore);
 
 	/* a list of WithCheckOption's (added during rewrite) */
-	List	   *withCheckOptions;
+	List	   *withCheckOptions pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * The following two fields identify the portion of the source text string
@@ -229,7 +236,7 @@ typedef struct Query
 	/* start location, or -1 if unknown */
 	int			stmt_location;
 	/* length in bytes; 0 means "rest of string" */
-	int			stmt_len;
+	int			stmt_len pg_node_attr(query_jumble_ignore);
 } Query;
 
 
@@ -1019,7 +1026,7 @@ typedef enum RTEKind
 
 typedef struct RangeTblEntry
 {
-	pg_node_attr(custom_read_write)
+	pg_node_attr(custom_read_write, custom_query_jumble)
 
 	NodeTag		type;
 
@@ -1250,6 +1257,8 @@ typedef struct RTEPermissionInfo
  * time.  We do however remember how many columns we thought the type had
  * (including dropped columns!), so that we can successfully ignore any
  * columns added after the query was parsed.
+ *
+ * The query jumbling needs only to track the function expression.
  */
 typedef struct RangeTblFunction
 {
@@ -1257,20 +1266,20 @@ typedef struct RangeTblFunction
 
 	Node	   *funcexpr;		/* expression tree for func call */
 	/* number of columns it contributes to RTE */
-	int			funccolcount;
+	int			funccolcount pg_node_attr(query_jumble_ignore);
 	/* These fields record the contents of a column definition list, if any: */
 	/* column names (list of String) */
-	List	   *funccolnames;
+	List	   *funccolnames pg_node_attr(query_jumble_ignore);
 	/* OID list of column type OIDs */
-	List	   *funccoltypes;
+	List	   *funccoltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of column typmods */
-	List	   *funccoltypmods;
+	List	   *funccoltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *funccolcollations;
+	List	   *funccolcollations pg_node_attr(query_jumble_ignore);
 
 	/* This is set during planning for use by the executor: */
 	/* PARAM_EXEC Param IDs affecting this func */
-	Bitmapset  *funcparams;
+	Bitmapset  *funcparams pg_node_attr(query_jumble_ignore);
 } RangeTblFunction;
 
 /*
@@ -1378,7 +1387,7 @@ typedef struct SortGroupClause
 	Oid			sortop;			/* the ordering operator ('<' op), or 0 */
 	bool		nulls_first;	/* do NULLs come before normal values? */
 	/* can eqop be implemented by hashing? */
-	bool		hashable;
+	bool		hashable pg_node_attr(query_jumble_ignore);
 } SortGroupClause;
 
 /*
@@ -1443,7 +1452,7 @@ typedef enum GroupingSetKind
 typedef struct GroupingSet
 {
 	NodeTag		type;
-	GroupingSetKind kind;
+	GroupingSetKind kind pg_node_attr(query_jumble_ignore);
 	List	   *content;
 	int			location;
 } GroupingSet;
@@ -1464,35 +1473,38 @@ typedef struct GroupingSet
  * When refname isn't null, the partitionClause is always copied from there;
  * the orderClause might or might not be copied (see copiedOrder); the framing
  * options are never copied, per spec.
+ *
+ * The information relevant for the query jumbling is the partition clause
+ * type and its bounds.
  */
 typedef struct WindowClause
 {
 	NodeTag		type;
 	/* window name (NULL in an OVER clause) */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* referenced window name, if any */
-	char	   *refname;
+	char	   *refname pg_node_attr(query_jumble_ignore);
 	List	   *partitionClause;	/* PARTITION BY list */
 	/* ORDER BY list */
-	List	   *orderClause;
+	List	   *orderClause pg_node_attr(query_jumble_ignore);
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
 	/* qual to help short-circuit execution */
-	List	   *runCondition;
+	List	   *runCondition pg_node_attr(query_jumble_ignore);
 	/* in_range function for startOffset */
-	Oid			startInRangeFunc;
+	Oid			startInRangeFunc pg_node_attr(query_jumble_ignore);
 	/* in_range function for endOffset */
-	Oid			endInRangeFunc;
+	Oid			endInRangeFunc pg_node_attr(query_jumble_ignore);
 	/* collation for in_range tests */
-	Oid			inRangeColl;
+	Oid			inRangeColl pg_node_attr(query_jumble_ignore);
 	/* use ASC sort order for in_range tests? */
-	bool		inRangeAsc;
+	bool		inRangeAsc pg_node_attr(query_jumble_ignore);
 	/* nulls sort first for in_range tests? */
-	bool		inRangeNullsFirst;
+	bool		inRangeNullsFirst pg_node_attr(query_jumble_ignore);
 	Index		winref;			/* ID referenced by window functions */
 	/* did we copy orderClause from refname? */
-	bool		copiedOrder;
+	bool		copiedOrder pg_node_attr(query_jumble_ignore);
 } WindowClause;
 
 /*
@@ -1607,26 +1619,26 @@ typedef struct CommonTableExpr
 	CTEMaterialize ctematerialized; /* is this an optimization fence? */
 	/* SelectStmt/InsertStmt/etc before parse analysis, Query afterwards: */
 	Node	   *ctequery;		/* the CTE's subquery */
-	CTESearchClause *search_clause;
-	CTECycleClause *cycle_clause;
+	CTESearchClause *search_clause pg_node_attr(query_jumble_ignore);
+	CTECycleClause *cycle_clause pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 	/* These fields are set during parse analysis: */
 	/* is this CTE actually recursive? */
-	bool		cterecursive;
+	bool		cterecursive pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * Number of RTEs referencing this CTE (excluding internal
-	 * self-references)
+	 * self-references), irrelevant for query jumbling.
 	 */
-	int			cterefcount;
+	int			cterefcount pg_node_attr(query_jumble_ignore);
 	/* list of output column names */
-	List	   *ctecolnames;
+	List	   *ctecolnames pg_node_attr(query_jumble_ignore);
 	/* OID list of output column type OIDs */
-	List	   *ctecoltypes;
+	List	   *ctecoltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of output column typmods */
-	List	   *ctecoltypmods;
+	List	   *ctecoltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *ctecolcollations;
+	List	   *ctecolcollations pg_node_attr(query_jumble_ignore);
 } CommonTableExpr;
 
 /* Convenience macro to get the output tlist of a CTE's query */
@@ -1664,11 +1676,11 @@ typedef struct MergeAction
 	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
 	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
 	/* OVERRIDING clause */
-	OverridingKind override;
+	OverridingKind override pg_node_attr(query_jumble_ignore);
 	Node	   *qual;			/* transformed WHEN conditions */
 	List	   *targetList;		/* the target list (of TargetEntry) */
 	/* target attribute numbers of an UPDATE */
-	List	   *updateColnos;
+	List	   *updateColnos pg_node_attr(query_jumble_ignore);
 } MergeAction;
 
 /*
@@ -1877,15 +1889,15 @@ typedef struct SetOperationStmt
 	Node	   *rarg;			/* right child */
 	/* Eventually add fields for CORRESPONDING spec here */
 
-	/* Fields derived during parse analysis: */
+	/* Fields derived during parse analysis (irrelevant for query jumbling): */
 	/* OID list of output column type OIDs */
-	List	   *colTypes;
+	List	   *colTypes pg_node_attr(query_jumble_ignore);
 	/* integer list of output column typmods */
-	List	   *colTypmods;
+	List	   *colTypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of output column collation OIDs */
-	List	   *colCollations;
+	List	   *colCollations pg_node_attr(query_jumble_ignore);
 	/* a list of SortGroupClause's */
-	List	   *groupClauses;
+	List	   *groupClauses pg_node_attr(query_jumble_ignore);
 	/* groupClauses is NIL if UNION ALL, but must be set otherwise */
 } SetOperationStmt;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index dec7d5c775..8d5b68a0bc 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -96,29 +96,29 @@ typedef struct TableFunc
 {
 	NodeTag		type;
 	/* list of namespace URI expressions */
-	List	   *ns_uris;
+	List	   *ns_uris pg_node_attr(query_jumble_ignore);
 	/* list of namespace names or NULL */
-	List	   *ns_names;
+	List	   *ns_names pg_node_attr(query_jumble_ignore);
 	/* input document expression */
 	Node	   *docexpr;
 	/* row filter expression */
 	Node	   *rowexpr;
 	/* column names (list of String) */
-	List	   *colnames;
+	List	   *colnames pg_node_attr(query_jumble_ignore);
 	/* OID list of column type OIDs */
-	List	   *coltypes;
+	List	   *coltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of column typmods */
-	List	   *coltypmods;
+	List	   *coltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *colcollations;
+	List	   *colcollations pg_node_attr(query_jumble_ignore);
 	/* list of column filter expressions */
 	List	   *colexprs;
 	/* list of column default expressions */
-	List	   *coldefexprs;
+	List	   *coldefexprs pg_node_attr(query_jumble_ignore);
 	/* nullability flag for each output column */
-	Bitmapset  *notnulls;
+	Bitmapset  *notnulls pg_node_attr(query_jumble_ignore);
 	/* counts from 0; -1 if none specified */
-	int			ordinalitycol;
+	int			ordinalitycol pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } TableFunc;
@@ -227,11 +227,11 @@ typedef struct Var
 	AttrNumber	varattno;
 
 	/* pg_type OID for the type of this var */
-	Oid			vartype;
+	Oid			vartype pg_node_attr(query_jumble_ignore);
 	/* pg_attribute typmod value */
-	int32		vartypmod;
+	int32		vartypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			varcollid;
+	Oid			varcollid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * for subquery variables referencing outer relations; 0 in a normal var,
@@ -245,9 +245,9 @@ typedef struct Var
 	 * their varno/varattno match.
 	 */
 	/* syntactic relation index (0 if unknown) */
-	Index		varnosyn pg_node_attr(equal_ignore);
+	Index		varnosyn pg_node_attr(equal_ignore, query_jumble_ignore);
 	/* syntactic attribute number */
-	AttrNumber	varattnosyn pg_node_attr(equal_ignore);
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -260,6 +260,8 @@ typedef struct Var
  * must be in non-extended form (4-byte header, no compression or external
  * references).  This ensures that the Const node is self-contained and makes
  * it more likely that equal() will see logically identical values as equal.
+ *
+ * Only the constant type OID is relevant for the query jumbling.
  */
 typedef struct Const
 {
@@ -269,24 +271,27 @@ typedef struct Const
 	/* pg_type OID of the constant's datatype */
 	Oid			consttype;
 	/* typmod value, if any */
-	int32		consttypmod;
+	int32		consttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			constcollid;
+	Oid			constcollid pg_node_attr(query_jumble_ignore);
 	/* typlen of the constant's datatype */
-	int			constlen;
+	int			constlen pg_node_attr(query_jumble_ignore);
 	/* the constant's value */
-	Datum		constvalue;
+	Datum		constvalue pg_node_attr(query_jumble_ignore);
 	/* whether the constant is null (if true, constvalue is undefined) */
-	bool		constisnull;
+	bool		constisnull pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * Whether this datatype is passed by value.  If true, then all the
 	 * information is stored in the Datum.  If false, then the Datum contains
 	 * a pointer to the information.
 	 */
-	bool		constbyval;
-	/* token location, or -1 if unknown */
-	int			location;
+	bool		constbyval pg_node_attr(query_jumble_ignore);
+	/*
+	 * token location, or -1 if unknown.  All constants are tracked as
+	 * locations in query jumbling, to be marked as parameters.
+	 */
+	int			location pg_node_attr(query_jumble_location);
 } Const;
 
 /*
@@ -324,6 +329,7 @@ typedef enum ParamKind
 	PARAM_MULTIEXPR
 } ParamKind;
 
+/* typmod and collation information are irrelevant for the query jumbling. */
 typedef struct Param
 {
 	Expr		xpr;
@@ -331,9 +337,9 @@ typedef struct Param
 	int			paramid;		/* numeric ID for parameter */
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
 	/* typmod value, if known */
-	int32		paramtypmod;
+	int32		paramtypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			paramcollid;
+	Oid			paramcollid pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } Param;
@@ -386,6 +392,9 @@ typedef struct Param
  * and can share the result.  Aggregates with same 'transno' but different
  * 'aggno' can share the same transition state, only the final function needs
  * to be called separately.
+ *
+ * Information related to collations, transition types and internal states
+ * are irrelevant for the query jumbling.
  */
 typedef struct Aggref
 {
@@ -395,22 +404,22 @@ typedef struct Aggref
 	Oid			aggfnoid;
 
 	/* type Oid of result of the aggregate */
-	Oid			aggtype;
+	Oid			aggtype pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			aggcollid;
+	Oid			aggcollid pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * type Oid of aggregate's transition value; ignored for equal since it
 	 * might not be set yet
 	 */
-	Oid			aggtranstype pg_node_attr(equal_ignore);
+	Oid			aggtranstype pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* type Oids of direct and aggregated args */
-	List	   *aggargtypes;
+	List	   *aggargtypes pg_node_attr(query_jumble_ignore);
 
 	/* direct arguments, if an ordered-set agg */
 	List	   *aggdirectargs;
@@ -428,31 +437,31 @@ typedef struct Aggref
 	Expr	   *aggfilter;
 
 	/* true if argument list was really '*' */
-	bool		aggstar;
+	bool		aggstar pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		aggvariadic;
+	bool		aggvariadic pg_node_attr(query_jumble_ignore);
 
 	/* aggregate kind (see pg_aggregate.h) */
-	char		aggkind;
+	char		aggkind pg_node_attr(query_jumble_ignore);
 
 	/* aggregate input already sorted */
-	bool		aggpresorted pg_node_attr(equal_ignore);
+	bool		aggpresorted pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* > 0 if agg belongs to outer query */
-	Index		agglevelsup;
+	Index		agglevelsup pg_node_attr(query_jumble_ignore);
 
 	/* expected agg-splitting mode of parent Agg */
-	AggSplit	aggsplit;
+	AggSplit	aggsplit pg_node_attr(query_jumble_ignore);
 
 	/* unique ID within the Agg node */
-	int			aggno;
+	int			aggno pg_node_attr(query_jumble_ignore);
 
 	/* unique ID of transition state in the Agg */
-	int			aggtransno;
+	int			aggtransno pg_node_attr(query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -481,19 +490,22 @@ typedef struct Aggref
  *
  * In raw parse output we have only the args list; parse analysis fills in the
  * refs list, and the planner fills in the cols list.
+ *
+ * All the fields used as information for an internal state are irrelevant
+ * for the query jumbling.
  */
 typedef struct GroupingFunc
 {
 	Expr		xpr;
 
 	/* arguments, not evaluated but kept for benefit of EXPLAIN etc. */
-	List	   *args;
+	List	   *args pg_node_attr(query_jumble_ignore);
 
 	/* ressortgrouprefs of arguments */
 	List	   *refs pg_node_attr(equal_ignore);
 
 	/* actual column positions set by planner */
-	List	   *cols pg_node_attr(equal_ignore);
+	List	   *cols pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* same as Aggref.agglevelsup */
 	Index		agglevelsup;
@@ -504,6 +516,9 @@ typedef struct GroupingFunc
 
 /*
  * WindowFunc
+ *
+ * Collation information is irrelevant for the query jumbling, as is the
+ * internal state information of the node like "winstar" and "winagg".
  */
 typedef struct WindowFunc
 {
@@ -511,11 +526,11 @@ typedef struct WindowFunc
 	/* pg_proc Oid of the function */
 	Oid			winfnoid;
 	/* type Oid of result of the window function */
-	Oid			wintype;
+	Oid			wintype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			wincollid;
+	Oid			wincollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the window function */
 	List	   *args;
 	/* FILTER expression, if any */
@@ -523,9 +538,9 @@ typedef struct WindowFunc
 	/* index of associated WindowClause */
 	Index		winref;
 	/* true if argument list was really '*' */
-	bool		winstar;
+	bool		winstar pg_node_attr(query_jumble_ignore);
 	/* is function a simple aggregate? */
-	bool		winagg;
+	bool		winagg pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } WindowFunc;
@@ -564,6 +579,8 @@ typedef struct WindowFunc
  * subscripting logic.  Likewise, reftypmod and refcollid will match the
  * container's properties in a store, but could be different in a fetch.
  *
+ * Any internal state data is ignored for the query jumbling.
+ *
  * Note: for the cases where a container is returned, if refexpr yields a R/W
  * expanded container, then the implementation is allowed to modify that
  * object in-place and return the same object.
@@ -572,15 +589,15 @@ typedef struct SubscriptingRef
 {
 	Expr		xpr;
 	/* type of the container proper */
-	Oid			refcontainertype;
+	Oid			refcontainertype pg_node_attr(query_jumble_ignore);
 	/* the container type's pg_type.typelem */
-	Oid			refelemtype;
+	Oid			refelemtype pg_node_attr(query_jumble_ignore);
 	/* type of the SubscriptingRef's result */
-	Oid			refrestype;
+	Oid			refrestype pg_node_attr(query_jumble_ignore);
 	/* typmod of the result */
-	int32		reftypmod;
+	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
-	Oid			refcollid;
+	Oid			refcollid pg_node_attr(query_jumble_ignore);
 	/* expressions that evaluate to upper container indexes */
 	List	   *refupperindexpr;
 
@@ -631,6 +648,9 @@ typedef enum CoercionForm
 
 /*
  * FuncExpr - expression node for a function call
+ *
+ * Collation information is irrelevant for the query jumbling, only the
+ * arguments and the function OID matter.
  */
 typedef struct FuncExpr
 {
@@ -638,21 +658,21 @@ typedef struct FuncExpr
 	/* PG_PROC OID of the function */
 	Oid			funcid;
 	/* PG_TYPE OID of result value */
-	Oid			funcresulttype;
+	Oid			funcresulttype pg_node_attr(query_jumble_ignore);
 	/* true if function returns set */
-	bool		funcretset;
+	bool		funcretset pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		funcvariadic;
+	bool		funcvariadic pg_node_attr(query_jumble_ignore);
 	/* how to display this function call */
-	CoercionForm funcformat;
+	CoercionForm funcformat pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			funccollid;
+	Oid			funccollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the function */
 	List	   *args;
 	/* token location, or -1 if unknown */
@@ -679,7 +699,7 @@ typedef struct NamedArgExpr
 	/* the argument expression */
 	Expr	   *arg;
 	/* the name */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* argument's number in positional notation */
 	int			argnumber;
 	/* argument name location, or -1 if unknown */
@@ -695,6 +715,9 @@ typedef struct NamedArgExpr
  * 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.
+ *
+ * Internal state information and collation data is irrelevant for the query
+ * jumbling.
  */
 typedef struct OpExpr
 {
@@ -704,19 +727,19 @@ typedef struct OpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of underlying function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_TYPE OID of result value */
-	Oid			opresulttype;
+	Oid			opresulttype pg_node_attr(query_jumble_ignore);
 
 	/* true if operator returns set */
-	bool		opretset;
+	bool		opretset pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			opcollid;
+	Oid			opcollid pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/* arguments to the operator (1 or 2) */
 	List	   *args;
@@ -772,6 +795,9 @@ typedef OpExpr NullIfExpr;
  * 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.
+ *
+ * OID entries of the internal function types are irrelevant for the query
+ * jumbling, but the operator OID and the arguments are.
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -781,19 +807,19 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of comparison function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_PROC OID of hash func or InvalidOid */
-	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_PROC OID of negator of opfuncid function or InvalidOid.  See above */
-	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* true for ANY, false for ALL */
 	bool		useOr;
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/* the scalar and array operands */
 	List	   *args;
@@ -895,7 +921,7 @@ typedef struct SubLink
 	int			subLinkId;		/* ID (1..n); 0 if not MULTIEXPR */
 	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
 	/* originally specified operator name */
-	List	   *operName;
+	List	   *operName pg_node_attr(query_jumble_ignore);
 	/* subselect as Query* or raw parsetree */
 	Node	   *subselect;
 	int			location;		/* token location, or -1 if unknown */
@@ -1007,11 +1033,11 @@ typedef struct FieldSelect
 	Expr	   *arg;			/* input expression */
 	AttrNumber	fieldnum;		/* attribute number of field to extract */
 	/* type of the field (result type of this node) */
-	Oid			resulttype;
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* output typmod (usually -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation of the field */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 } FieldSelect;
 
 /* ----------------
@@ -1038,9 +1064,9 @@ typedef struct FieldStore
 	Expr	   *arg;			/* input tuple value */
 	List	   *newvals;		/* new value(s) for field(s) */
 	/* integer list of field attnums */
-	List	   *fieldnums;
+	List	   *fieldnums pg_node_attr(query_jumble_ignore);
 	/* type of result (same as type of arg) */
-	Oid			resulttype;
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 } FieldStore;
 
@@ -1063,11 +1089,11 @@ typedef struct RelabelType
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion expression */
 	/* output typmod (usually -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm relabelformat;
+	CoercionForm relabelformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } RelabelType;
 
@@ -1087,9 +1113,9 @@ typedef struct CoerceViaIO
 	Oid			resulttype;		/* output type of coercion */
 	/* output typmod is not stored, but is presumed -1 */
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coerceformat;
+	CoercionForm coerceformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } CoerceViaIO;
 
@@ -1113,11 +1139,11 @@ typedef struct ArrayCoerceExpr
 	Expr	   *elemexpr;		/* expression representing per-element work */
 	Oid			resulttype;		/* output type of coercion (an array type) */
 	/* output typmod (also element typmod) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coerceformat;
+	CoercionForm coerceformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } ArrayCoerceExpr;
 
@@ -1141,7 +1167,7 @@ typedef struct ConvertRowtypeExpr
 	Oid			resulttype;		/* output type (always a composite type) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 	/* how to display this node */
-	CoercionForm convertformat;
+	CoercionForm convertformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } ConvertRowtypeExpr;
 
@@ -1186,9 +1212,9 @@ typedef struct CaseExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			casetype;
+	Oid			casetype pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			casecollid;
+	Oid			casecollid pg_node_attr(query_jumble_ignore);
 	Expr	   *arg;			/* implicit equality comparison argument */
 	List	   *args;			/* the arguments (list of WHEN clauses) */
 	Expr	   *defresult;		/* the default result (ELSE clause) */
@@ -1231,9 +1257,9 @@ typedef struct CaseTestExpr
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 } CaseTestExpr;
 
 /*
@@ -1248,15 +1274,15 @@ typedef struct ArrayExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			array_typeid;
+	Oid			array_typeid pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			array_collid;
+	Oid			array_collid pg_node_attr(query_jumble_ignore);
 	/* common type of array elements */
-	Oid			element_typeid;
+	Oid			element_typeid pg_node_attr(query_jumble_ignore);
 	/* the array elements or sub-arrays */
 	List	   *elements;
 	/* true if elements are sub-arrays */
-	bool		multidims;
+	bool		multidims pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } ArrayExpr;
@@ -1288,7 +1314,7 @@ typedef struct RowExpr
 	List	   *args;			/* the fields */
 
 	/* RECORDOID or a composite type's ID */
-	Oid			row_typeid;
+	Oid			row_typeid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * row_typeid cannot be a domain over composite, only plain composite.  To
@@ -1304,10 +1330,10 @@ typedef struct RowExpr
 	 */
 
 	/* how to display this node */
-	CoercionForm row_format;
+	CoercionForm row_format pg_node_attr(query_jumble_ignore);
 
 	/* list of String, or NIL */
-	List	   *colnames;
+	List	   *colnames pg_node_attr(query_jumble_ignore);
 
 	int			location;		/* token location, or -1 if unknown */
 } RowExpr;
@@ -1344,11 +1370,11 @@ typedef struct RowCompareExpr
 	/* LT LE GE or GT, never EQ or NE */
 	RowCompareType rctype;
 	/* OID list of pairwise comparison ops */
-	List	   *opnos;
+	List	   *opnos pg_node_attr(query_jumble_ignore);
 	/* OID list of containing operator families */
-	List	   *opfamilies;
+	List	   *opfamilies pg_node_attr(query_jumble_ignore);
 	/* OID list of collations for comparisons */
-	List	   *inputcollids;
+	List	   *inputcollids pg_node_attr(query_jumble_ignore);
 	/* the left-hand input arguments */
 	List	   *largs;
 	/* the right-hand input arguments */
@@ -1362,9 +1388,9 @@ typedef struct CoalesceExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			coalescetype;
+	Oid			coalescetype pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			coalescecollid;
+	Oid			coalescecollid pg_node_attr(query_jumble_ignore);
 	/* the arguments */
 	List	   *args;
 	/* token location, or -1 if unknown */
@@ -1384,11 +1410,11 @@ typedef struct MinMaxExpr
 {
 	Expr		xpr;
 	/* common type of arguments and result */
-	Oid			minmaxtype;
+	Oid			minmaxtype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			minmaxcollid;
+	Oid			minmaxcollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* function to execute */
 	MinMaxOp	op;
 	/* the arguments */
@@ -1432,18 +1458,18 @@ typedef struct XmlExpr
 	/* xml function ID */
 	XmlExprOp	op;
 	/* name in xml(NAME foo ...) syntaxes */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* non-XML expressions for xml_attributes */
 	List	   *named_args;
 	/* parallel list of String values */
-	List	   *arg_names;
+	List	   *arg_names pg_node_attr(query_jumble_ignore);
 	/* list of expressions */
 	List	   *args;
 	/* DOCUMENT or CONTENT */
-	XmlOptionType xmloption;
+	XmlOptionType xmloption pg_node_attr(query_jumble_ignore);
 	/* target type/typmod for XMLSERIALIZE */
-	Oid			type;
-	int32		typmod;
+	Oid			type pg_node_attr(query_jumble_ignore);
+	int32		typmod pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } XmlExpr;
@@ -1478,7 +1504,7 @@ typedef struct NullTest
 	Expr	   *arg;			/* input expression */
 	NullTestType nulltesttype;	/* IS NULL, IS NOT NULL */
 	/* T to perform field-by-field null checks */
-	bool		argisrow;
+	bool		argisrow pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } NullTest;
 
@@ -1512,6 +1538,8 @@ typedef struct BooleanTest
  * checked will be determined.  If the value passes, it is returned as the
  * result; if not, an error is raised.  Note that this is equivalent to
  * RelabelType in the scenario where no constraints are applied.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct CoerceToDomain
 {
@@ -1519,11 +1547,11 @@ typedef struct CoerceToDomain
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* domain type ID (result type) */
 	/* output typmod (currently always -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coercionformat;
+	CoercionForm coercionformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } CoerceToDomain;
 
@@ -1542,9 +1570,9 @@ typedef struct CoerceToDomainValue
 	/* type for substituted value */
 	Oid			typeId;
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } CoerceToDomainValue;
@@ -1555,6 +1583,8 @@ typedef struct CoerceToDomainValue
  * This is not an executable expression: it must be replaced by the actual
  * column default expression during rewriting.  But it is convenient to
  * treat it as an expression node during parsing and rewriting.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct SetToDefault
 {
@@ -1562,9 +1592,9 @@ typedef struct SetToDefault
 	/* type for substituted value */
 	Oid			typeId;
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } SetToDefault;
@@ -1682,15 +1712,15 @@ typedef struct TargetEntry
 	/* attribute number (see notes above) */
 	AttrNumber	resno;
 	/* name of the column (could be NULL) */
-	char	   *resname;
+	char	   *resname pg_node_attr(query_jumble_ignore);
 	/* nonzero if referenced by a sort/group clause */
 	Index		ressortgroupref;
 	/* OID of column's source table */
-	Oid			resorigtbl;
+	Oid			resorigtbl pg_node_attr(query_jumble_ignore);
 	/* column's number in source table */
-	AttrNumber	resorigcol;
+	AttrNumber	resorigcol pg_node_attr(query_jumble_ignore);
 	/* set to true to eliminate the attribute from final target list */
-	bool		resjunk;
+	bool		resjunk pg_node_attr(query_jumble_ignore);
 } TargetEntry;
 
 
@@ -1773,13 +1803,13 @@ typedef struct JoinExpr
 	Node	   *larg;			/* left subtree */
 	Node	   *rarg;			/* right subtree */
 	/* USING clause, if any (list of String) */
-	List	   *usingClause;
+	List	   *usingClause pg_node_attr(query_jumble_ignore);
 	/* alias attached to USING clause, if any */
-	Alias	   *join_using_alias;
+	Alias	   *join_using_alias pg_node_attr(query_jumble_ignore);
 	/* qualifiers on join, if any */
 	Node	   *quals;
 	/* user-written alias clause, if any */
-	Alias	   *alias;
+	Alias	   *alias pg_node_attr(query_jumble_ignore);
 	/* RT index assigned for join, or 0 */
 	int			rtindex;
 } JoinExpr;
diff --git a/src/backend/nodes/README b/src/backend/nodes/README
index 489a67eb89..7cf6e3b041 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -51,6 +51,7 @@ FILES IN THIS DIRECTORY (src/backend/nodes/)
 	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
+	queryjumblefuncs.c - compute a node tree for query jumbling (*)
 
     (*) - Most functions in these files are generated by
     gen_node_support.pl and #include'd there.
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index b3c1ead496..471333dc80 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -121,6 +121,8 @@ my %node_type_info;
 my @no_copy;
 # node types we don't want equal support for
 my @no_equal;
+# node types we don't want jumble support for
+my @no_query_jumble;
 # node types we don't want read support for
 my @no_read;
 # node types we don't want read/write support for
@@ -155,12 +157,13 @@ 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);
-# Lists are specially treated in all four support files, too.
+# Lists are specially treated in all five support files, too.
 # (Ideally we'd mark List as "special copy/equal" not "no copy/equal".
 # But until there's other use-cases for that, just hot-wire the tests
 # that would need to distinguish.)
 push @no_copy,            qw(List);
 push @no_equal,           qw(List);
+push @no_query_jumble,          qw(List);
 push @special_read_write, qw(List);
 
 # Nodes with custom copy/equal implementations are skipped from
@@ -170,6 +173,9 @@ my @custom_copy_equal;
 # Similarly for custom read/write implementations.
 my @custom_read_write;
 
+# Similarly for custom query jumble implementation.
+my @custom_query_jumble;
+
 # Track node types with manually assigned NodeTag numbers.
 my %manual_nodetag_number;
 
@@ -319,6 +325,10 @@ foreach my $infile (@ARGV)
 						{
 							push @custom_read_write, $in_struct;
 						}
+						elsif ($attr eq 'custom_query_jumble')
+						{
+							push @custom_query_jumble, $in_struct;
+						}
 						elsif ($attr eq 'no_copy')
 						{
 							push @no_copy, $in_struct;
@@ -332,6 +342,10 @@ foreach my $infile (@ARGV)
 							push @no_copy,  $in_struct;
 							push @no_equal, $in_struct;
 						}
+						elsif ($attr eq 'no_query_jumble')
+						{
+							push @no_query_jumble, $in_struct;
+						}
 						elsif ($attr eq 'no_read')
 						{
 							push @no_read, $in_struct;
@@ -457,6 +471,8 @@ foreach my $infile (@ARGV)
 								equal_as_scalar
 								equal_ignore
 								equal_ignore_if_zero
+								query_jumble_ignore
+								query_jumble_location
 								read_write_ignore
 								write_only_relids
 								write_only_nondefault_pathtarget
@@ -1225,6 +1241,102 @@ close $ofs;
 close $rfs;
 
 
+# queryjumblefuncs.c
+
+push @output_files, 'queryjumblefuncs.funcs.c';
+open my $jff, '>', "$output_path/queryjumblefuncs.funcs.c$tmpext" or die $!;
+push @output_files, 'queryjumblefuncs.switch.c';
+open my $jfs, '>', "$output_path/queryjumblefuncs.switch.c$tmpext" or die $!;
+
+printf $jff $header_comment, 'queryjumblefuncs.funcs.c';
+printf $jfs $header_comment, 'queryjumblefuncs.switch.c';
+
+print $jff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @nodetag_only;
+	my $struct_no_query_jumble = (elem $n, @no_query_jumble);
+
+	print $jfs "\t\t\tcase T_${n}:\n"
+	  . "\t\t\t\t_jumble${n}(jstate, expr);\n"
+	  . "\t\t\t\tbreak;\n"
+	  unless $struct_no_query_jumble;
+
+	next if elem $n, @custom_query_jumble;
+
+	print $jff "
+static void
+_jumble${n}(JumbleState *jstate, Node *node)
+{
+\t${n} *expr = (${n} *) node;\n
+" unless $struct_no_query_jumble;
+
+	# 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 $query_jumble_ignore = $struct_no_query_jumble;
+		my $query_jumble_location = 0;
+
+		# extract per-field attributes
+		foreach my $a (@a)
+		{
+			if ($a eq 'query_jumble_ignore')
+			{
+				$query_jumble_ignore = 1;
+			}
+			elsif ($a eq 'query_jumble_location')
+			{
+				$query_jumble_location = 1;
+			}
+		}
+
+		# node type
+		if (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+			and elem $1, @node_types)
+		{
+			print $jff "\tJUMBLE_NODE($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			# Track the node's location only if directly requested.
+			if ($query_jumble_location)
+			{
+				print $jff "\tJUMBLE_LOCATION($f);\n"
+				  unless $query_jumble_ignore;
+			}
+		}
+		elsif ($t eq 'char*')
+		{
+			print $jff "\tJUMBLE_STRING($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		else
+		{
+			print $jff "\tJUMBLE_FIELD($f);\n"
+			  unless $query_jumble_ignore;
+		}
+	}
+
+	# Some nodes have no attributes like CheckPointStmt,
+	# so tweak things for empty contents.
+	if (scalar(@{ $node_type_info{$n}->{fields} }) == 0)
+	{
+		print $jff "\t(void) expr;\n"
+		  unless $struct_no_query_jumble;
+	}
+
+	print $jff "}
+" unless $struct_no_query_jumble;
+}
+
+close $jff;
+close $jfs;
+
 # now rename the temporary files to their final names
 foreach my $file (@output_files)
 {
diff --git a/src/backend/nodes/meson.build b/src/backend/nodes/meson.build
index 9230515e7f..31467a12d3 100644
--- a/src/backend/nodes/meson.build
+++ b/src/backend/nodes/meson.build
@@ -10,7 +10,6 @@ backend_sources += files(
   'nodes.c',
   'params.c',
   'print.c',
-  'queryjumblefuncs.c',
   'read.c',
   'tidbitmap.c',
   'value.c',
@@ -21,6 +20,7 @@ backend_sources += files(
 nodefunc_sources = files(
   'copyfuncs.c',
   'equalfuncs.c',
+  'queryjumblefuncs.c',
   'outfuncs.c',
   'readfuncs.c',
 )
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 16084842a3..16fdf7164a 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -45,15 +45,12 @@ int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
 /* True when compute_query_id is ON, or AUTO and a module requests them */
 bool		query_id_enabled = false;
 
-static uint64 compute_utility_query_id(const char *query_text,
-									   int query_location, int query_len);
 static void AppendJumble(JumbleState *jstate,
 						 const unsigned char *item, Size size);
-static void JumbleQueryInternal(JumbleState *jstate, Query *query);
-static void JumbleRangeTable(JumbleState *jstate, List *rtable);
-static void JumbleRowMarks(JumbleState *jstate, List *rowMarks);
-static void JumbleExpr(JumbleState *jstate, Node *node);
 static void RecordConstLocation(JumbleState *jstate, int location);
+static void _jumbleNode(JumbleState *jstate, Node *node);
+static void _jumbleList(JumbleState *jstate, Node *node);
+static void _jumbleRangeTblEntry(JumbleState *jstate, Node *node);
 
 /*
  * Given a possibly multi-statement source string, confine our attention to the
@@ -105,38 +102,29 @@ JumbleQuery(Query *query, const char *querytext)
 
 	Assert(IsQueryIdEnabled());
 
-	if (query->utilityStmt)
-	{
-		query->queryId = compute_utility_query_id(querytext,
-												  query->stmt_location,
-												  query->stmt_len);
-	}
-	else
-	{
-		jstate = (JumbleState *) palloc(sizeof(JumbleState));
+	jstate = (JumbleState *) palloc(sizeof(JumbleState));
 
-		/* Set up workspace for query jumbling */
-		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-		jstate->jumble_len = 0;
-		jstate->clocations_buf_size = 32;
-		jstate->clocations = (LocationLen *)
-			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-		jstate->clocations_count = 0;
-		jstate->highest_extern_param_id = 0;
+	/* Set up workspace for query jumbling */
+	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+	jstate->jumble_len = 0;
+	jstate->clocations_buf_size = 32;
+	jstate->clocations = (LocationLen *)
+		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+	jstate->clocations_count = 0;
+	jstate->highest_extern_param_id = 0;
 
-		/* Compute query ID and mark the Query node with it */
-		JumbleQueryInternal(jstate, query);
-		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-														  jstate->jumble_len,
-														  0));
+	/* Compute query ID and mark the Query node with it */
+	_jumbleNode(jstate, (Node *) query);
+	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+													  jstate->jumble_len,
+													  0));
 
-		/*
-		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-		 * prevent confusion with the utility-statement case.
-		 */
-		if (query->queryId == UINT64CONST(0))
-			query->queryId = UINT64CONST(1);
-	}
+	/*
+	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+	 * prevent confusion with the utility-statement case.
+	 */
+	if (query->queryId == UINT64CONST(0))
+		query->queryId = UINT64CONST(1);
 
 	return jstate;
 }
@@ -154,34 +142,6 @@ EnableQueryId(void)
 		query_id_enabled = true;
 }
 
-/*
- * Compute a query identifier for the given utility query string.
- */
-static uint64
-compute_utility_query_id(const char *query_text, int query_location, int query_len)
-{
-	uint64		queryId;
-	const char *sql;
-
-	/*
-	 * Confine our attention to the relevant part of the string, if the query
-	 * is a portion of a multi-statement source string.
-	 */
-	sql = CleanQuerytext(query_text, &query_location, &query_len);
-
-	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
-											   query_len, 0));
-
-	/*
-	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
-	 * 2 instead, queryID 1 is already in use for normal statements.
-	 */
-	if (queryId == UINT64CONST(0))
-		queryId = UINT64CONST(2);
-
-	return queryId;
-}
-
 /*
  * AppendJumble: Append a value that is substantive in a given query to
  * the current jumble.
@@ -219,621 +179,6 @@ AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
 	jstate->jumble_len = jumble_len;
 }
 
-/*
- * Wrappers around AppendJumble to encapsulate details of serialization
- * of individual local variable elements.
- */
-#define APP_JUMB(item) \
-	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
-#define APP_JUMB_STRING(str) \
-	AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1)
-
-/*
- * JumbleQueryInternal: Selectively serialize the query tree, appending
- * significant data to the "query jumble" while ignoring nonsignificant data.
- *
- * Rule of thumb for what to include is that we should ignore anything not
- * semantically significant (such as alias names) as well as anything that can
- * be deduced from child nodes (else we'd just be double-hashing that piece
- * of information).
- */
-static void
-JumbleQueryInternal(JumbleState *jstate, Query *query)
-{
-	Assert(IsA(query, Query));
-	Assert(query->utilityStmt == NULL);
-
-	APP_JUMB(query->commandType);
-	/* resultRelation is usually predictable from commandType */
-	JumbleExpr(jstate, (Node *) query->cteList);
-	JumbleRangeTable(jstate, query->rtable);
-	JumbleExpr(jstate, (Node *) query->jointree);
-	JumbleExpr(jstate, (Node *) query->mergeActionList);
-	JumbleExpr(jstate, (Node *) query->targetList);
-	JumbleExpr(jstate, (Node *) query->onConflict);
-	JumbleExpr(jstate, (Node *) query->returningList);
-	JumbleExpr(jstate, (Node *) query->groupClause);
-	APP_JUMB(query->groupDistinct);
-	JumbleExpr(jstate, (Node *) query->groupingSets);
-	JumbleExpr(jstate, query->havingQual);
-	JumbleExpr(jstate, (Node *) query->windowClause);
-	JumbleExpr(jstate, (Node *) query->distinctClause);
-	JumbleExpr(jstate, (Node *) query->sortClause);
-	JumbleExpr(jstate, query->limitOffset);
-	JumbleExpr(jstate, query->limitCount);
-	APP_JUMB(query->limitOption);
-	JumbleRowMarks(jstate, query->rowMarks);
-	JumbleExpr(jstate, query->setOperations);
-}
-
-/*
- * Jumble a range table
- */
-static void
-JumbleRangeTable(JumbleState *jstate, List *rtable)
-{
-	ListCell   *lc;
-
-	foreach(lc, rtable)
-	{
-		RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-		APP_JUMB(rte->rtekind);
-		switch (rte->rtekind)
-		{
-			case RTE_RELATION:
-				APP_JUMB(rte->relid);
-				JumbleExpr(jstate, (Node *) rte->tablesample);
-				APP_JUMB(rte->inh);
-				break;
-			case RTE_SUBQUERY:
-				JumbleQueryInternal(jstate, rte->subquery);
-				break;
-			case RTE_JOIN:
-				APP_JUMB(rte->jointype);
-				break;
-			case RTE_FUNCTION:
-				JumbleExpr(jstate, (Node *) rte->functions);
-				break;
-			case RTE_TABLEFUNC:
-				JumbleExpr(jstate, (Node *) rte->tablefunc);
-				break;
-			case RTE_VALUES:
-				JumbleExpr(jstate, (Node *) rte->values_lists);
-				break;
-			case RTE_CTE:
-
-				/*
-				 * Depending on the CTE name here isn't ideal, but it's the
-				 * only info we have to identify the referenced WITH item.
-				 */
-				APP_JUMB_STRING(rte->ctename);
-				APP_JUMB(rte->ctelevelsup);
-				break;
-			case RTE_NAMEDTUPLESTORE:
-				APP_JUMB_STRING(rte->enrname);
-				break;
-			case RTE_RESULT:
-				break;
-			default:
-				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
-				break;
-		}
-	}
-}
-
-/*
- * Jumble a rowMarks list
- */
-static void
-JumbleRowMarks(JumbleState *jstate, List *rowMarks)
-{
-	ListCell   *lc;
-
-	foreach(lc, rowMarks)
-	{
-		RowMarkClause *rowmark = lfirst_node(RowMarkClause, lc);
-
-		if (!rowmark->pushedDown)
-		{
-			APP_JUMB(rowmark->rti);
-			APP_JUMB(rowmark->strength);
-			APP_JUMB(rowmark->waitPolicy);
-		}
-	}
-}
-
-/*
- * Jumble an expression tree
- *
- * In general this function should handle all the same node types that
- * expression_tree_walker() does, and therefore it's coded to be as parallel
- * to that function as possible.  However, since we are only invoked on
- * queries immediately post-parse-analysis, we need not handle node types
- * that only appear in planning.
- *
- * Note: the reason we don't simply use expression_tree_walker() is that the
- * point of that function is to support tree walkers that don't care about
- * most tree node types, but here we care about all types.  We should complain
- * about any unrecognized node type.
- */
-static void
-JumbleExpr(JumbleState *jstate, Node *node)
-{
-	ListCell   *temp;
-
-	if (node == NULL)
-		return;
-
-	/* Guard against stack overflow due to overly complex expressions */
-	check_stack_depth();
-
-	/*
-	 * We always emit the node's NodeTag, then any additional fields that are
-	 * considered significant, and then we recurse to any child nodes.
-	 */
-	APP_JUMB(node->type);
-
-	switch (nodeTag(node))
-	{
-		case T_Var:
-			{
-				Var		   *var = (Var *) node;
-
-				APP_JUMB(var->varno);
-				APP_JUMB(var->varattno);
-				APP_JUMB(var->varlevelsup);
-			}
-			break;
-		case T_Const:
-			{
-				Const	   *c = (Const *) node;
-
-				/* We jumble only the constant's type, not its value */
-				APP_JUMB(c->consttype);
-				/* Also, record its parse location for query normalization */
-				RecordConstLocation(jstate, c->location);
-			}
-			break;
-		case T_Param:
-			{
-				Param	   *p = (Param *) node;
-
-				APP_JUMB(p->paramkind);
-				APP_JUMB(p->paramid);
-				APP_JUMB(p->paramtype);
-				/* Also, track the highest external Param id */
-				if (p->paramkind == PARAM_EXTERN &&
-					p->paramid > jstate->highest_extern_param_id)
-					jstate->highest_extern_param_id = p->paramid;
-			}
-			break;
-		case T_Aggref:
-			{
-				Aggref	   *expr = (Aggref *) node;
-
-				APP_JUMB(expr->aggfnoid);
-				JumbleExpr(jstate, (Node *) expr->aggdirectargs);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggorder);
-				JumbleExpr(jstate, (Node *) expr->aggdistinct);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_GroupingFunc:
-			{
-				GroupingFunc *grpnode = (GroupingFunc *) node;
-
-				JumbleExpr(jstate, (Node *) grpnode->refs);
-				APP_JUMB(grpnode->agglevelsup);
-			}
-			break;
-		case T_WindowFunc:
-			{
-				WindowFunc *expr = (WindowFunc *) node;
-
-				APP_JUMB(expr->winfnoid);
-				APP_JUMB(expr->winref);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_SubscriptingRef:
-			{
-				SubscriptingRef *sbsref = (SubscriptingRef *) node;
-
-				JumbleExpr(jstate, (Node *) sbsref->refupperindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refassgnexpr);
-			}
-			break;
-		case T_FuncExpr:
-			{
-				FuncExpr   *expr = (FuncExpr *) node;
-
-				APP_JUMB(expr->funcid);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_NamedArgExpr:
-			{
-				NamedArgExpr *nae = (NamedArgExpr *) node;
-
-				APP_JUMB(nae->argnumber);
-				JumbleExpr(jstate, (Node *) nae->arg);
-			}
-			break;
-		case T_OpExpr:
-		case T_DistinctExpr:	/* struct-equivalent to OpExpr */
-		case T_NullIfExpr:		/* struct-equivalent to OpExpr */
-			{
-				OpExpr	   *expr = (OpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_ScalarArrayOpExpr:
-			{
-				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				APP_JUMB(expr->useOr);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_BoolExpr:
-			{
-				BoolExpr   *expr = (BoolExpr *) node;
-
-				APP_JUMB(expr->boolop);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_SubLink:
-			{
-				SubLink    *sublink = (SubLink *) node;
-
-				APP_JUMB(sublink->subLinkType);
-				APP_JUMB(sublink->subLinkId);
-				JumbleExpr(jstate, (Node *) sublink->testexpr);
-				JumbleQueryInternal(jstate, castNode(Query, sublink->subselect));
-			}
-			break;
-		case T_FieldSelect:
-			{
-				FieldSelect *fs = (FieldSelect *) node;
-
-				APP_JUMB(fs->fieldnum);
-				JumbleExpr(jstate, (Node *) fs->arg);
-			}
-			break;
-		case T_FieldStore:
-			{
-				FieldStore *fstore = (FieldStore *) node;
-
-				JumbleExpr(jstate, (Node *) fstore->arg);
-				JumbleExpr(jstate, (Node *) fstore->newvals);
-			}
-			break;
-		case T_RelabelType:
-			{
-				RelabelType *rt = (RelabelType *) node;
-
-				APP_JUMB(rt->resulttype);
-				JumbleExpr(jstate, (Node *) rt->arg);
-			}
-			break;
-		case T_CoerceViaIO:
-			{
-				CoerceViaIO *cio = (CoerceViaIO *) node;
-
-				APP_JUMB(cio->resulttype);
-				JumbleExpr(jstate, (Node *) cio->arg);
-			}
-			break;
-		case T_ArrayCoerceExpr:
-			{
-				ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node;
-
-				APP_JUMB(acexpr->resulttype);
-				JumbleExpr(jstate, (Node *) acexpr->arg);
-				JumbleExpr(jstate, (Node *) acexpr->elemexpr);
-			}
-			break;
-		case T_ConvertRowtypeExpr:
-			{
-				ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node;
-
-				APP_JUMB(crexpr->resulttype);
-				JumbleExpr(jstate, (Node *) crexpr->arg);
-			}
-			break;
-		case T_CollateExpr:
-			{
-				CollateExpr *ce = (CollateExpr *) node;
-
-				APP_JUMB(ce->collOid);
-				JumbleExpr(jstate, (Node *) ce->arg);
-			}
-			break;
-		case T_CaseExpr:
-			{
-				CaseExpr   *caseexpr = (CaseExpr *) node;
-
-				JumbleExpr(jstate, (Node *) caseexpr->arg);
-				foreach(temp, caseexpr->args)
-				{
-					CaseWhen   *when = lfirst_node(CaseWhen, temp);
-
-					JumbleExpr(jstate, (Node *) when->expr);
-					JumbleExpr(jstate, (Node *) when->result);
-				}
-				JumbleExpr(jstate, (Node *) caseexpr->defresult);
-			}
-			break;
-		case T_CaseTestExpr:
-			{
-				CaseTestExpr *ct = (CaseTestExpr *) node;
-
-				APP_JUMB(ct->typeId);
-			}
-			break;
-		case T_ArrayExpr:
-			JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements);
-			break;
-		case T_RowExpr:
-			JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args);
-			break;
-		case T_RowCompareExpr:
-			{
-				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-
-				APP_JUMB(rcexpr->rctype);
-				JumbleExpr(jstate, (Node *) rcexpr->largs);
-				JumbleExpr(jstate, (Node *) rcexpr->rargs);
-			}
-			break;
-		case T_CoalesceExpr:
-			JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args);
-			break;
-		case T_MinMaxExpr:
-			{
-				MinMaxExpr *mmexpr = (MinMaxExpr *) node;
-
-				APP_JUMB(mmexpr->op);
-				JumbleExpr(jstate, (Node *) mmexpr->args);
-			}
-			break;
-		case T_XmlExpr:
-			{
-				XmlExpr    *xexpr = (XmlExpr *) node;
-
-				APP_JUMB(xexpr->op);
-				JumbleExpr(jstate, (Node *) xexpr->named_args);
-				JumbleExpr(jstate, (Node *) xexpr->args);
-			}
-			break;
-		case T_NullTest:
-			{
-				NullTest   *nt = (NullTest *) node;
-
-				APP_JUMB(nt->nulltesttype);
-				JumbleExpr(jstate, (Node *) nt->arg);
-			}
-			break;
-		case T_BooleanTest:
-			{
-				BooleanTest *bt = (BooleanTest *) node;
-
-				APP_JUMB(bt->booltesttype);
-				JumbleExpr(jstate, (Node *) bt->arg);
-			}
-			break;
-		case T_CoerceToDomain:
-			{
-				CoerceToDomain *cd = (CoerceToDomain *) node;
-
-				APP_JUMB(cd->resulttype);
-				JumbleExpr(jstate, (Node *) cd->arg);
-			}
-			break;
-		case T_CoerceToDomainValue:
-			{
-				CoerceToDomainValue *cdv = (CoerceToDomainValue *) node;
-
-				APP_JUMB(cdv->typeId);
-			}
-			break;
-		case T_SetToDefault:
-			{
-				SetToDefault *sd = (SetToDefault *) node;
-
-				APP_JUMB(sd->typeId);
-			}
-			break;
-		case T_CurrentOfExpr:
-			{
-				CurrentOfExpr *ce = (CurrentOfExpr *) node;
-
-				APP_JUMB(ce->cvarno);
-				if (ce->cursor_name)
-					APP_JUMB_STRING(ce->cursor_name);
-				APP_JUMB(ce->cursor_param);
-			}
-			break;
-		case T_NextValueExpr:
-			{
-				NextValueExpr *nve = (NextValueExpr *) node;
-
-				APP_JUMB(nve->seqid);
-				APP_JUMB(nve->typeId);
-			}
-			break;
-		case T_InferenceElem:
-			{
-				InferenceElem *ie = (InferenceElem *) node;
-
-				APP_JUMB(ie->infercollid);
-				APP_JUMB(ie->inferopclass);
-				JumbleExpr(jstate, ie->expr);
-			}
-			break;
-		case T_TargetEntry:
-			{
-				TargetEntry *tle = (TargetEntry *) node;
-
-				APP_JUMB(tle->resno);
-				APP_JUMB(tle->ressortgroupref);
-				JumbleExpr(jstate, (Node *) tle->expr);
-			}
-			break;
-		case T_RangeTblRef:
-			{
-				RangeTblRef *rtr = (RangeTblRef *) node;
-
-				APP_JUMB(rtr->rtindex);
-			}
-			break;
-		case T_JoinExpr:
-			{
-				JoinExpr   *join = (JoinExpr *) node;
-
-				APP_JUMB(join->jointype);
-				APP_JUMB(join->isNatural);
-				APP_JUMB(join->rtindex);
-				JumbleExpr(jstate, join->larg);
-				JumbleExpr(jstate, join->rarg);
-				JumbleExpr(jstate, join->quals);
-			}
-			break;
-		case T_FromExpr:
-			{
-				FromExpr   *from = (FromExpr *) node;
-
-				JumbleExpr(jstate, (Node *) from->fromlist);
-				JumbleExpr(jstate, from->quals);
-			}
-			break;
-		case T_OnConflictExpr:
-			{
-				OnConflictExpr *conf = (OnConflictExpr *) node;
-
-				APP_JUMB(conf->action);
-				JumbleExpr(jstate, (Node *) conf->arbiterElems);
-				JumbleExpr(jstate, conf->arbiterWhere);
-				JumbleExpr(jstate, (Node *) conf->onConflictSet);
-				JumbleExpr(jstate, conf->onConflictWhere);
-				APP_JUMB(conf->constraint);
-				APP_JUMB(conf->exclRelIndex);
-				JumbleExpr(jstate, (Node *) conf->exclRelTlist);
-			}
-			break;
-		case T_MergeAction:
-			{
-				MergeAction *mergeaction = (MergeAction *) node;
-
-				APP_JUMB(mergeaction->matched);
-				APP_JUMB(mergeaction->commandType);
-				JumbleExpr(jstate, mergeaction->qual);
-				JumbleExpr(jstate, (Node *) mergeaction->targetList);
-			}
-			break;
-		case T_List:
-			foreach(temp, (List *) node)
-			{
-				JumbleExpr(jstate, (Node *) lfirst(temp));
-			}
-			break;
-		case T_IntList:
-			foreach(temp, (List *) node)
-			{
-				APP_JUMB(lfirst_int(temp));
-			}
-			break;
-		case T_SortGroupClause:
-			{
-				SortGroupClause *sgc = (SortGroupClause *) node;
-
-				APP_JUMB(sgc->tleSortGroupRef);
-				APP_JUMB(sgc->eqop);
-				APP_JUMB(sgc->sortop);
-				APP_JUMB(sgc->nulls_first);
-			}
-			break;
-		case T_GroupingSet:
-			{
-				GroupingSet *gsnode = (GroupingSet *) node;
-
-				JumbleExpr(jstate, (Node *) gsnode->content);
-			}
-			break;
-		case T_WindowClause:
-			{
-				WindowClause *wc = (WindowClause *) node;
-
-				APP_JUMB(wc->winref);
-				APP_JUMB(wc->frameOptions);
-				JumbleExpr(jstate, (Node *) wc->partitionClause);
-				JumbleExpr(jstate, (Node *) wc->orderClause);
-				JumbleExpr(jstate, wc->startOffset);
-				JumbleExpr(jstate, wc->endOffset);
-			}
-			break;
-		case T_CommonTableExpr:
-			{
-				CommonTableExpr *cte = (CommonTableExpr *) node;
-
-				/* we store the string name because RTE_CTE RTEs need it */
-				APP_JUMB_STRING(cte->ctename);
-				APP_JUMB(cte->ctematerialized);
-				JumbleQueryInternal(jstate, castNode(Query, cte->ctequery));
-			}
-			break;
-		case T_SetOperationStmt:
-			{
-				SetOperationStmt *setop = (SetOperationStmt *) node;
-
-				APP_JUMB(setop->op);
-				APP_JUMB(setop->all);
-				JumbleExpr(jstate, setop->larg);
-				JumbleExpr(jstate, setop->rarg);
-			}
-			break;
-		case T_RangeTblFunction:
-			{
-				RangeTblFunction *rtfunc = (RangeTblFunction *) node;
-
-				JumbleExpr(jstate, rtfunc->funcexpr);
-			}
-			break;
-		case T_TableFunc:
-			{
-				TableFunc  *tablefunc = (TableFunc *) node;
-
-				JumbleExpr(jstate, tablefunc->docexpr);
-				JumbleExpr(jstate, tablefunc->rowexpr);
-				JumbleExpr(jstate, (Node *) tablefunc->colexprs);
-			}
-			break;
-		case T_TableSampleClause:
-			{
-				TableSampleClause *tsc = (TableSampleClause *) node;
-
-				APP_JUMB(tsc->tsmhandler);
-				JumbleExpr(jstate, (Node *) tsc->args);
-				JumbleExpr(jstate, (Node *) tsc->repeatable);
-			}
-			break;
-		default:
-			/* Only a warning, since we can stumble along anyway */
-			elog(WARNING, "unrecognized node type: %d",
-				 (int) nodeTag(node));
-			break;
-	}
-}
-
 /*
  * Record location of constant within query string of query tree
  * that is currently being walked.
@@ -859,3 +204,154 @@ RecordConstLocation(JumbleState *jstate, int location)
 		jstate->clocations_count++;
 	}
 }
+
+#define JUMBLE_NODE(item) \
+	_jumbleNode(jstate, (Node *) expr->item)
+#define JUMBLE_LOCATION(location) \
+	RecordConstLocation(jstate, expr->location)
+#define JUMBLE_FIELD(item) \
+	AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item))
+#define JUMBLE_FIELD_SINGLE(item) \
+	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
+#define JUMBLE_STRING(str) \
+do { \
+	if (expr->str) \
+		AppendJumble(jstate, (const unsigned char *) (expr->str), strlen(expr->str) + 1); \
+} while(0)
+
+#include "queryjumblefuncs.funcs.c"
+
+static void
+_jumbleNode(JumbleState *jstate, Node *node)
+{
+	Node	   *expr = node;
+
+	if (expr == NULL)
+		return;
+
+	/* Guard against stack overflow due to overly complex expressions */
+	check_stack_depth();
+
+	/*
+	 * We always emit the node's NodeTag, then any additional fields that are
+	 * considered significant, and then we recurse to any child nodes.
+	 */
+	JUMBLE_FIELD(type);
+
+	switch (nodeTag(expr))
+	{
+#include "queryjumblefuncs.switch.c"
+
+		case T_List:
+		case T_IntList:
+		case T_OidList:
+		case T_XidList:
+			_jumbleList(jstate, expr);
+			break;
+
+		default:
+			/* Only a warning, since we can stumble along anyway */
+			elog(WARNING, "unrecognized node type: %d",
+				 (int) nodeTag(expr));
+			break;
+	}
+
+	/* Special cases to handle outside the automated code */
+	switch (nodeTag(expr))
+	{
+		case T_Param:
+			{
+				Param	   *p = (Param *) node;
+
+				/*
+				 * Update the highest Param id seen, in order to start
+				 * normalization correctly.
+				 */
+				if (p->paramkind == PARAM_EXTERN &&
+					p->paramid > jstate->highest_extern_param_id)
+					jstate->highest_extern_param_id = p->paramid;
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+_jumbleList(JumbleState *jstate, Node *node)
+{
+	List	   *expr = (List *) node;
+	ListCell   *l;
+
+	switch (expr->type)
+	{
+		case T_List:
+			foreach(l, expr)
+				_jumbleNode(jstate, lfirst(l));
+			break;
+		case T_IntList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_int(l));
+			break;
+		case T_OidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_oid(l));
+			break;
+		case T_XidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_xid(l));
+			break;
+		default:
+			elog(ERROR, "unrecognized list node type: %d",
+				 (int) expr->type);
+			return;
+	}
+}
+
+static void
+_jumbleRangeTblEntry(JumbleState *jstate, Node *node)
+{
+	RangeTblEntry *expr = (RangeTblEntry *) node;
+
+	JUMBLE_FIELD(rtekind);
+	switch (expr->rtekind)
+	{
+		case RTE_RELATION:
+			JUMBLE_FIELD(relid);
+			JUMBLE_NODE(tablesample);
+			JUMBLE_FIELD(inh);
+			break;
+		case RTE_SUBQUERY:
+			JUMBLE_NODE(subquery);
+			break;
+		case RTE_JOIN:
+			JUMBLE_FIELD(jointype);
+			break;
+		case RTE_FUNCTION:
+			JUMBLE_NODE(functions);
+			break;
+		case RTE_TABLEFUNC:
+			JUMBLE_NODE(tablefunc);
+			break;
+		case RTE_VALUES:
+			JUMBLE_NODE(values_lists);
+			break;
+		case RTE_CTE:
+
+			/*
+			 * Depending on the CTE name here isn't ideal, but it's the only
+			 * info we have to identify the referenced WITH item.
+			 */
+			JUMBLE_STRING(ctename);
+			JUMBLE_FIELD(ctelevelsup);
+			break;
+		case RTE_NAMEDTUPLESTORE:
+			JUMBLE_STRING(enrname);
+			break;
+		case RTE_RESULT:
+			break;
+		default:
+			elog(ERROR, "unrecognized RTE kind: %d", (int) expr->rtekind);
+			break;
+	}
+}
-- 
2.39.0

v6-0004-Add-GUC-utility_query_id.patchtext/x-diff; charset=us-asciiDownload
From 6fe42d23f1ba3d648184852b32caf7db71020b71 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 24 Jan 2023 15:52:37 +0900
Subject: [PATCH v6 4/4] Add GUC utility_query_id

This GUC has two modes to control the computation method of query IDs
for utilities:
- 'string', the default, to hash the string query.
- 'jumble', to use the parsed tree.
---
 src/include/nodes/queryjumble.h               |  7 ++
 src/backend/nodes/queryjumblefuncs.c          | 81 ++++++++++++++-----
 src/backend/utils/misc/guc_tables.c           | 16 ++++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 doc/src/sgml/config.sgml                      | 31 +++++++
 .../expected/pg_stat_statements.out           | 31 +++++++
 .../sql/pg_stat_statements.sql                | 17 ++++
 7 files changed, 164 insertions(+), 20 deletions(-)

diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index 204b8f74fd..261aea6bcf 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -59,8 +59,15 @@ enum ComputeQueryIdType
 	COMPUTE_QUERY_ID_REGRESS
 };
 
+enum UtilityQueryIdType
+{
+	UTILITY_QUERY_ID_STRING,
+	UTILITY_QUERY_ID_JUMBLE
+};
+
 /* GUC parameters */
 extern PGDLLIMPORT int compute_query_id;
+extern PGDLLIMPORT int utility_query_id;
 
 
 extern const char *CleanQuerytext(const char *query, int *location, int *len);
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 16fdf7164a..b8738fba08 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -41,12 +41,15 @@
 
 /* GUC parameters */
 int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
+int			utility_query_id = UTILITY_QUERY_ID_STRING;
 
 /* True when compute_query_id is ON, or AUTO and a module requests them */
 bool		query_id_enabled = false;
 
 static void AppendJumble(JumbleState *jstate,
 						 const unsigned char *item, Size size);
+static uint64 compute_utility_query_id(const char *query_text,
+									   int query_location, int query_len);
 static void RecordConstLocation(JumbleState *jstate, int location);
 static void _jumbleNode(JumbleState *jstate, Node *node);
 static void _jumbleList(JumbleState *jstate, Node *node);
@@ -102,29 +105,39 @@ JumbleQuery(Query *query, const char *querytext)
 
 	Assert(IsQueryIdEnabled());
 
-	jstate = (JumbleState *) palloc(sizeof(JumbleState));
+	if (query->utilityStmt &&
+		compute_query_id == UTILITY_QUERY_ID_STRING)
+	{
+		query->queryId = compute_utility_query_id(querytext,
+												  query->stmt_location,
+												  query->stmt_len);
+	}
+	else
+	{
+		jstate = (JumbleState *) palloc(sizeof(JumbleState));
 
-	/* Set up workspace for query jumbling */
-	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-	jstate->jumble_len = 0;
-	jstate->clocations_buf_size = 32;
-	jstate->clocations = (LocationLen *)
-		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-	jstate->clocations_count = 0;
-	jstate->highest_extern_param_id = 0;
+		/* Set up workspace for query jumbling */
+		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+		jstate->jumble_len = 0;
+		jstate->clocations_buf_size = 32;
+		jstate->clocations = (LocationLen *)
+			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+		jstate->clocations_count = 0;
+		jstate->highest_extern_param_id = 0;
 
-	/* Compute query ID and mark the Query node with it */
-	_jumbleNode(jstate, (Node *) query);
-	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-													  jstate->jumble_len,
-													  0));
+		/* Compute query ID and mark the Query node with it */
+		_jumbleNode(jstate, (Node *) query);
+		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+														  jstate->jumble_len,
+														  0));
 
-	/*
-	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-	 * prevent confusion with the utility-statement case.
-	 */
-	if (query->queryId == UINT64CONST(0))
-		query->queryId = UINT64CONST(1);
+		/*
+		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+		 * prevent confusion with the utility-statement case.
+		 */
+		if (query->queryId == UINT64CONST(0))
+			query->queryId = UINT64CONST(1);
+	}
 
 	return jstate;
 }
@@ -142,6 +155,34 @@ EnableQueryId(void)
 		query_id_enabled = true;
 }
 
+/*
+ * Compute a query identifier for the given utility query string.
+ */
+static uint64
+compute_utility_query_id(const char *query_text, int query_location, int query_len)
+{
+	uint64		queryId;
+	const char *sql;
+
+	/*
+	 * Confine our attention to the relevant part of the string, if the query
+	 * is a portion of a multi-statement source string.
+	 */
+	sql = CleanQuerytext(query_text, &query_location, &query_len);
+
+	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
+											   query_len, 0));
+
+	/*
+	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
+	 * 2 instead, queryID 1 is already in use for normal statements.
+	 */
+	if (queryId == UINT64CONST(0))
+		queryId = UINT64CONST(2);
+
+	return queryId;
+}
+
 /*
  * AppendJumble: Append a value that is substantive in a given query to
  * the current jumble.
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4ac808ed22..97619c4e1d 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -294,6 +294,12 @@ static const struct config_enum_entry compute_query_id_options[] = {
 	{NULL, 0, false}
 };
 
+static const struct config_enum_entry utility_query_id_options[] = {
+	{"string", UTILITY_QUERY_ID_STRING, false},
+	{"jumble", UTILITY_QUERY_ID_JUMBLE, false},
+	{NULL, 0, false}
+};
+
 /*
  * Although only "on", "off", and "partition" are documented, we
  * accept all the likely variants of "on" and "off".
@@ -4574,6 +4580,16 @@ struct config_enum ConfigureNamesEnum[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"utility_query_id", PGC_SUSET, STATS_MONITORING,
+			gettext_noop("Controls method computing query ID for utilities."),
+			NULL
+		},
+		&utility_query_id,
+		UTILITY_QUERY_ID_STRING, utility_query_id_options,
+		NULL, NULL, NULL
+	},
+
 	{
 		{"constraint_exclusion", PGC_USERSET, QUERY_TUNING_OTHER,
 			gettext_noop("Enables the planner to use constraints to optimize queries."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..bbf95af59d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -623,6 +623,7 @@
 # - Monitoring -
 
 #compute_query_id = auto
+#utility_query_id = string		# string, jumble
 #log_statement_stats = off
 #log_parser_stats = off
 #log_planner_stats = off
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f985afc009..4ccd148471 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8241,6 +8241,37 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-utility-query-id" xreflabel="utility_query_id">
+      <term><varname>utility_query_id</varname> (<type>enum</type>)
+      <indexterm>
+       <primary><varname>utility_query_id</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Controls the method used to compute the query identifier of a utility
+        query. Valid values are <literal>string</literal> to use a hash of the
+        query string and <literal>jumble</literal> to compute the query
+        identifier depending on the parsed tree of the utility query.
+        The default is <literal>string</literal>.
+       </para>
+       <para>
+        <literal>jumble</literal> is more costly than <literal>string</literal>
+        as the computation of the query identifier walks through the
+        post-parse-analysis representation of the queries for utility queries.
+        However, <literal>jumble</literal> is able to apply normalization
+        to the queries computed, meaning that queries written differently
+        but having the same query representation may be able to use the same
+        identifier.
+        For example, <literal>BEGIN;</literal> and <literal>begin;</literal>
+        will have the same query identifier under <literal>jumble</literal> as
+        both queries have the same query representation. The query identifier
+        would be different under <literal>string</literal>, because the query
+        strings are different.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-log-statement-stats">
       <term><varname>log_statement_stats</varname> (<type>boolean</type>)
       <indexterm>
diff --git a/contrib/pg_stat_statements/expected/pg_stat_statements.out b/contrib/pg_stat_statements/expected/pg_stat_statements.out
index 9ac5c87c3a..8bdf8beec3 100644
--- a/contrib/pg_stat_statements/expected/pg_stat_statements.out
+++ b/contrib/pg_stat_statements/expected/pg_stat_statements.out
@@ -554,6 +554,7 @@ DROP TABLE pgss_a, pgss_b CASCADE;
 -- utility commands
 --
 SET pg_stat_statements.track_utility = TRUE;
+SET utility_query_id = 'string';
 SELECT pg_stat_statements_reset();
  pg_stat_statements_reset 
 --------------------------
@@ -592,6 +593,36 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
  SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     0 |    0
 (9 rows)
 
+SELECT pg_stat_statements_reset();
+ pg_stat_statements_reset 
+--------------------------
+ 
+(1 row)
+
+SET utility_query_id = 'jumble';
+-- These queries have a different string, but the same parsing
+-- representation.
+Begin;
+Create Table test_utility_query (a int);
+Drop Table test_utility_query;
+Commit;
+BEGIN;
+CREATE TABLE test_utility_query (a int);
+DROP TABLE test_utility_query;
+COMMIT;
+SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
+                                    query                                     | calls | rows 
+------------------------------------------------------------------------------+-------+------
+ Begin                                                                        |     2 |    0
+ Commit                                                                       |     2 |    0
+ Create Table test_utility_query (a int)                                      |     2 |    0
+ Drop Table test_utility_query                                                |     2 |    0
+ SELECT pg_stat_statements_reset()                                            |     1 |    1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     0 |    0
+ SET utility_query_id = 'jumble'                                              |     1 |    0
+(7 rows)
+
+RESET utility_query_id;
 --
 -- Track the total number of rows retrieved or affected by the utility
 -- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW,
diff --git a/contrib/pg_stat_statements/sql/pg_stat_statements.sql b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
index 8f5c866225..81d663f81c 100644
--- a/contrib/pg_stat_statements/sql/pg_stat_statements.sql
+++ b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
@@ -258,6 +258,7 @@ DROP TABLE pgss_a, pgss_b CASCADE;
 -- utility commands
 --
 SET pg_stat_statements.track_utility = TRUE;
+SET utility_query_id = 'string';
 SELECT pg_stat_statements_reset();
 
 SELECT 1;
@@ -272,6 +273,22 @@ DROP FUNCTION PLUS_TWO(INTEGER);
 
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 
+SELECT pg_stat_statements_reset();
+SET utility_query_id = 'jumble';
+-- These queries have a different string, but the same parsing
+-- representation.
+Begin;
+Create Table test_utility_query (a int);
+Drop Table test_utility_query;
+Commit;
+BEGIN;
+CREATE TABLE test_utility_query (a int);
+DROP TABLE test_utility_query;
+COMMIT;
+
+SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
+RESET utility_query_id;
+
 --
 -- Track the total number of rows retrieved or affected by the utility
 -- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW,
-- 
2.39.0

#18Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#17)
2 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Tue, Jan 24, 2023 at 03:57:56PM +0900, Michael Paquier wrote:

Makes sense. That would be my intention if 0004 is the most
acceptable and splitting things makes things a bit easier to review.

There was a silly mistake in 0004 where the jumbling code relied on
compute_query_id rather than utility_query_id, so fixed and rebased as
of v7 attached.
--
Michael

Attachments:

v7-0003-Support-for-automated-query-jumble-with-all-Nodes.patchtext/x-diff; charset=us-asciiDownload
From e1a35c02927ed38b04b1f628c460ef05f706621f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 24 Jan 2023 15:29:06 +0900
Subject: [PATCH v7 3/4] Support for automated query jumble with all Nodes

This applies query jumbling in a consistent way to all the Nodes,
including DDLs & friends.
---
 src/include/nodes/bitmapset.h         |   2 +-
 src/include/nodes/nodes.h             |  13 +-
 src/include/nodes/parsenodes.h        | 126 ++--
 src/include/nodes/primnodes.h         | 268 ++++----
 src/backend/nodes/README              |   1 +
 src/backend/nodes/gen_node_support.pl | 114 +++-
 src/backend/nodes/meson.build         |   2 +-
 src/backend/nodes/queryjumblefuncs.c  | 852 ++++++--------------------
 8 files changed, 519 insertions(+), 859 deletions(-)

diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 0dca6bc5fa..3d2225e1ae 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -50,7 +50,7 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
-	pg_node_attr(custom_copy_equal, special_read_write)
+	pg_node_attr(custom_copy_equal, special_read_write, no_query_jumble)
 
 	NodeTag		type;
 	int			nwords;			/* number of words in array */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 10752e8011..d7a9e38436 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -53,16 +53,20 @@ typedef enum NodeTag
  * - custom_read_write: Has custom implementations in outfuncs.c and
  *   readfuncs.c.
  *
+ * - custom_query_jumble: Has custom implementation in queryjumblefuncs.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_query_jumble: Does not support JumbleQuery() at all.
+ *
  * - no_read: Does not support nodeRead() at all.
  *
- * - nodetag_only: Does not support copyObject(), equal(), outNode(),
- *   or nodeRead().
+ * - nodetag_only: Does not support copyObject(), equal(), jumbleQuery()
+ *   outNode() or nodeRead().
  *
  * - special_read_write: Has special treatment in outNode() and nodeRead().
  *
@@ -97,6 +101,11 @@ typedef enum NodeTag
  * - equal_ignore_if_zero: Ignore the field for equality if it is zero.
  *   (Otherwise, compare normally.)
  *
+ * - query_jumble_ignore: Ignore the field for the query jumbling.
+ *
+ * - query_jumble_location: Mark the field as a location to track.  This is
+ *   only allowed for integer fields that include "location" in their name.
+ *
  * - 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
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 89335d95e7..f99fb5e909 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -116,6 +116,11 @@ typedef uint64 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.
+ *
+ *	  All the fields ignored for the query jumbling are not semantically
+ *	  significant (such as alias names), as is ignored anything that can
+ *	  be deduced from child nodes (else we'd just be double-hashing that
+ *	  piece of information).
  */
 typedef struct Query
 {
@@ -124,45 +129,47 @@ typedef struct Query
 	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
 
 	/* where did I come from? */
-	QuerySource querySource;
+	QuerySource querySource pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * query identifier (can be set by plugins); ignored for equal, as it
-	 * might not be set; also not stored
+	 * might not be set; also not stored.  This is the result of the query
+	 * jumble, hence ignored.
 	 */
-	uint64		queryId pg_node_attr(equal_ignore, read_write_ignore, read_as(0));
+	uint64		queryId pg_node_attr(equal_ignore, query_jumble_ignore, read_write_ignore, read_as(0));
 
 	/* do I set the command result tag? */
-	bool		canSetTag;
+	bool		canSetTag pg_node_attr(query_jumble_ignore);
 
 	Node	   *utilityStmt;	/* non-null if commandType == CMD_UTILITY */
 
 	/*
 	 * rtable index of target relation for INSERT/UPDATE/DELETE/MERGE; 0 for
-	 * SELECT.
+	 * SELECT.  This is ignored in the query jumble as unrelated to the
+	 * compilation of the query ID.
 	 */
-	int			resultRelation;
+	int			resultRelation pg_node_attr(query_jumble_ignore);
 
 	/* has aggregates in tlist or havingQual */
-	bool		hasAggs;
+	bool		hasAggs pg_node_attr(query_jumble_ignore);
 	/* has window functions in tlist */
-	bool		hasWindowFuncs;
+	bool		hasWindowFuncs pg_node_attr(query_jumble_ignore);
 	/* has set-returning functions in tlist */
-	bool		hasTargetSRFs;
+	bool		hasTargetSRFs pg_node_attr(query_jumble_ignore);
 	/* has subquery SubLink */
-	bool		hasSubLinks;
+	bool		hasSubLinks pg_node_attr(query_jumble_ignore);
 	/* distinctClause is from DISTINCT ON */
-	bool		hasDistinctOn;
+	bool		hasDistinctOn pg_node_attr(query_jumble_ignore);
 	/* WITH RECURSIVE was specified */
-	bool		hasRecursive;
+	bool		hasRecursive pg_node_attr(query_jumble_ignore);
 	/* has INSERT/UPDATE/DELETE in WITH */
-	bool		hasModifyingCTE;
+	bool		hasModifyingCTE pg_node_attr(query_jumble_ignore);
 	/* FOR [KEY] UPDATE/SHARE was specified */
-	bool		hasForUpdate;
+	bool		hasForUpdate pg_node_attr(query_jumble_ignore);
 	/* rewriter has applied some RLS policy */
-	bool		hasRowSecurity;
+	bool		hasRowSecurity pg_node_attr(query_jumble_ignore);
 	/* is a RETURN statement */
-	bool		isReturn;
+	bool		isReturn pg_node_attr(query_jumble_ignore);
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
@@ -172,18 +179,18 @@ typedef struct Query
 	 * list of RTEPermissionInfo nodes for the rtable entries having
 	 * perminfoindex > 0
 	 */
-	List	   *rteperminfos;
+	List	   *rteperminfos pg_node_attr(query_jumble_ignore);
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
 	List	   *mergeActionList;	/* list of actions for MERGE (only) */
 	/* whether to use outer join */
-	bool		mergeUseOuterJoin;
+	bool		mergeUseOuterJoin pg_node_attr(query_jumble_ignore);
 
 	List	   *targetList;		/* target list (of TargetEntry) */
 
 	/* OVERRIDING clause */
-	OverridingKind override;
+	OverridingKind override pg_node_attr(query_jumble_ignore);
 
 	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
 
@@ -215,10 +222,10 @@ typedef struct Query
 	 * A list of pg_constraint OIDs that the query depends on to be
 	 * semantically valid
 	 */
-	List	   *constraintDeps;
+	List	   *constraintDeps pg_node_attr(query_jumble_ignore);
 
 	/* a list of WithCheckOption's (added during rewrite) */
-	List	   *withCheckOptions;
+	List	   *withCheckOptions pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * The following two fields identify the portion of the source text string
@@ -229,7 +236,7 @@ typedef struct Query
 	/* start location, or -1 if unknown */
 	int			stmt_location;
 	/* length in bytes; 0 means "rest of string" */
-	int			stmt_len;
+	int			stmt_len pg_node_attr(query_jumble_ignore);
 } Query;
 
 
@@ -1019,7 +1026,7 @@ typedef enum RTEKind
 
 typedef struct RangeTblEntry
 {
-	pg_node_attr(custom_read_write)
+	pg_node_attr(custom_read_write, custom_query_jumble)
 
 	NodeTag		type;
 
@@ -1250,6 +1257,8 @@ typedef struct RTEPermissionInfo
  * time.  We do however remember how many columns we thought the type had
  * (including dropped columns!), so that we can successfully ignore any
  * columns added after the query was parsed.
+ *
+ * The query jumbling needs only to track the function expression.
  */
 typedef struct RangeTblFunction
 {
@@ -1257,20 +1266,20 @@ typedef struct RangeTblFunction
 
 	Node	   *funcexpr;		/* expression tree for func call */
 	/* number of columns it contributes to RTE */
-	int			funccolcount;
+	int			funccolcount pg_node_attr(query_jumble_ignore);
 	/* These fields record the contents of a column definition list, if any: */
 	/* column names (list of String) */
-	List	   *funccolnames;
+	List	   *funccolnames pg_node_attr(query_jumble_ignore);
 	/* OID list of column type OIDs */
-	List	   *funccoltypes;
+	List	   *funccoltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of column typmods */
-	List	   *funccoltypmods;
+	List	   *funccoltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *funccolcollations;
+	List	   *funccolcollations pg_node_attr(query_jumble_ignore);
 
 	/* This is set during planning for use by the executor: */
 	/* PARAM_EXEC Param IDs affecting this func */
-	Bitmapset  *funcparams;
+	Bitmapset  *funcparams pg_node_attr(query_jumble_ignore);
 } RangeTblFunction;
 
 /*
@@ -1378,7 +1387,7 @@ typedef struct SortGroupClause
 	Oid			sortop;			/* the ordering operator ('<' op), or 0 */
 	bool		nulls_first;	/* do NULLs come before normal values? */
 	/* can eqop be implemented by hashing? */
-	bool		hashable;
+	bool		hashable pg_node_attr(query_jumble_ignore);
 } SortGroupClause;
 
 /*
@@ -1443,7 +1452,7 @@ typedef enum GroupingSetKind
 typedef struct GroupingSet
 {
 	NodeTag		type;
-	GroupingSetKind kind;
+	GroupingSetKind kind pg_node_attr(query_jumble_ignore);
 	List	   *content;
 	int			location;
 } GroupingSet;
@@ -1464,35 +1473,38 @@ typedef struct GroupingSet
  * When refname isn't null, the partitionClause is always copied from there;
  * the orderClause might or might not be copied (see copiedOrder); the framing
  * options are never copied, per spec.
+ *
+ * The information relevant for the query jumbling is the partition clause
+ * type and its bounds.
  */
 typedef struct WindowClause
 {
 	NodeTag		type;
 	/* window name (NULL in an OVER clause) */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* referenced window name, if any */
-	char	   *refname;
+	char	   *refname pg_node_attr(query_jumble_ignore);
 	List	   *partitionClause;	/* PARTITION BY list */
 	/* ORDER BY list */
-	List	   *orderClause;
+	List	   *orderClause pg_node_attr(query_jumble_ignore);
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
 	/* qual to help short-circuit execution */
-	List	   *runCondition;
+	List	   *runCondition pg_node_attr(query_jumble_ignore);
 	/* in_range function for startOffset */
-	Oid			startInRangeFunc;
+	Oid			startInRangeFunc pg_node_attr(query_jumble_ignore);
 	/* in_range function for endOffset */
-	Oid			endInRangeFunc;
+	Oid			endInRangeFunc pg_node_attr(query_jumble_ignore);
 	/* collation for in_range tests */
-	Oid			inRangeColl;
+	Oid			inRangeColl pg_node_attr(query_jumble_ignore);
 	/* use ASC sort order for in_range tests? */
-	bool		inRangeAsc;
+	bool		inRangeAsc pg_node_attr(query_jumble_ignore);
 	/* nulls sort first for in_range tests? */
-	bool		inRangeNullsFirst;
+	bool		inRangeNullsFirst pg_node_attr(query_jumble_ignore);
 	Index		winref;			/* ID referenced by window functions */
 	/* did we copy orderClause from refname? */
-	bool		copiedOrder;
+	bool		copiedOrder pg_node_attr(query_jumble_ignore);
 } WindowClause;
 
 /*
@@ -1607,26 +1619,26 @@ typedef struct CommonTableExpr
 	CTEMaterialize ctematerialized; /* is this an optimization fence? */
 	/* SelectStmt/InsertStmt/etc before parse analysis, Query afterwards: */
 	Node	   *ctequery;		/* the CTE's subquery */
-	CTESearchClause *search_clause;
-	CTECycleClause *cycle_clause;
+	CTESearchClause *search_clause pg_node_attr(query_jumble_ignore);
+	CTECycleClause *cycle_clause pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 	/* These fields are set during parse analysis: */
 	/* is this CTE actually recursive? */
-	bool		cterecursive;
+	bool		cterecursive pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * Number of RTEs referencing this CTE (excluding internal
-	 * self-references)
+	 * self-references), irrelevant for query jumbling.
 	 */
-	int			cterefcount;
+	int			cterefcount pg_node_attr(query_jumble_ignore);
 	/* list of output column names */
-	List	   *ctecolnames;
+	List	   *ctecolnames pg_node_attr(query_jumble_ignore);
 	/* OID list of output column type OIDs */
-	List	   *ctecoltypes;
+	List	   *ctecoltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of output column typmods */
-	List	   *ctecoltypmods;
+	List	   *ctecoltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *ctecolcollations;
+	List	   *ctecolcollations pg_node_attr(query_jumble_ignore);
 } CommonTableExpr;
 
 /* Convenience macro to get the output tlist of a CTE's query */
@@ -1664,11 +1676,11 @@ typedef struct MergeAction
 	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
 	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
 	/* OVERRIDING clause */
-	OverridingKind override;
+	OverridingKind override pg_node_attr(query_jumble_ignore);
 	Node	   *qual;			/* transformed WHEN conditions */
 	List	   *targetList;		/* the target list (of TargetEntry) */
 	/* target attribute numbers of an UPDATE */
-	List	   *updateColnos;
+	List	   *updateColnos pg_node_attr(query_jumble_ignore);
 } MergeAction;
 
 /*
@@ -1877,15 +1889,15 @@ typedef struct SetOperationStmt
 	Node	   *rarg;			/* right child */
 	/* Eventually add fields for CORRESPONDING spec here */
 
-	/* Fields derived during parse analysis: */
+	/* Fields derived during parse analysis (irrelevant for query jumbling): */
 	/* OID list of output column type OIDs */
-	List	   *colTypes;
+	List	   *colTypes pg_node_attr(query_jumble_ignore);
 	/* integer list of output column typmods */
-	List	   *colTypmods;
+	List	   *colTypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of output column collation OIDs */
-	List	   *colCollations;
+	List	   *colCollations pg_node_attr(query_jumble_ignore);
 	/* a list of SortGroupClause's */
-	List	   *groupClauses;
+	List	   *groupClauses pg_node_attr(query_jumble_ignore);
 	/* groupClauses is NIL if UNION ALL, but must be set otherwise */
 } SetOperationStmt;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index dec7d5c775..8d5b68a0bc 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -96,29 +96,29 @@ typedef struct TableFunc
 {
 	NodeTag		type;
 	/* list of namespace URI expressions */
-	List	   *ns_uris;
+	List	   *ns_uris pg_node_attr(query_jumble_ignore);
 	/* list of namespace names or NULL */
-	List	   *ns_names;
+	List	   *ns_names pg_node_attr(query_jumble_ignore);
 	/* input document expression */
 	Node	   *docexpr;
 	/* row filter expression */
 	Node	   *rowexpr;
 	/* column names (list of String) */
-	List	   *colnames;
+	List	   *colnames pg_node_attr(query_jumble_ignore);
 	/* OID list of column type OIDs */
-	List	   *coltypes;
+	List	   *coltypes pg_node_attr(query_jumble_ignore);
 	/* integer list of column typmods */
-	List	   *coltypmods;
+	List	   *coltypmods pg_node_attr(query_jumble_ignore);
 	/* OID list of column collation OIDs */
-	List	   *colcollations;
+	List	   *colcollations pg_node_attr(query_jumble_ignore);
 	/* list of column filter expressions */
 	List	   *colexprs;
 	/* list of column default expressions */
-	List	   *coldefexprs;
+	List	   *coldefexprs pg_node_attr(query_jumble_ignore);
 	/* nullability flag for each output column */
-	Bitmapset  *notnulls;
+	Bitmapset  *notnulls pg_node_attr(query_jumble_ignore);
 	/* counts from 0; -1 if none specified */
-	int			ordinalitycol;
+	int			ordinalitycol pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } TableFunc;
@@ -227,11 +227,11 @@ typedef struct Var
 	AttrNumber	varattno;
 
 	/* pg_type OID for the type of this var */
-	Oid			vartype;
+	Oid			vartype pg_node_attr(query_jumble_ignore);
 	/* pg_attribute typmod value */
-	int32		vartypmod;
+	int32		vartypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			varcollid;
+	Oid			varcollid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * for subquery variables referencing outer relations; 0 in a normal var,
@@ -245,9 +245,9 @@ typedef struct Var
 	 * their varno/varattno match.
 	 */
 	/* syntactic relation index (0 if unknown) */
-	Index		varnosyn pg_node_attr(equal_ignore);
+	Index		varnosyn pg_node_attr(equal_ignore, query_jumble_ignore);
 	/* syntactic attribute number */
-	AttrNumber	varattnosyn pg_node_attr(equal_ignore);
+	AttrNumber	varattnosyn pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -260,6 +260,8 @@ typedef struct Var
  * must be in non-extended form (4-byte header, no compression or external
  * references).  This ensures that the Const node is self-contained and makes
  * it more likely that equal() will see logically identical values as equal.
+ *
+ * Only the constant type OID is relevant for the query jumbling.
  */
 typedef struct Const
 {
@@ -269,24 +271,27 @@ typedef struct Const
 	/* pg_type OID of the constant's datatype */
 	Oid			consttype;
 	/* typmod value, if any */
-	int32		consttypmod;
+	int32		consttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			constcollid;
+	Oid			constcollid pg_node_attr(query_jumble_ignore);
 	/* typlen of the constant's datatype */
-	int			constlen;
+	int			constlen pg_node_attr(query_jumble_ignore);
 	/* the constant's value */
-	Datum		constvalue;
+	Datum		constvalue pg_node_attr(query_jumble_ignore);
 	/* whether the constant is null (if true, constvalue is undefined) */
-	bool		constisnull;
+	bool		constisnull pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * Whether this datatype is passed by value.  If true, then all the
 	 * information is stored in the Datum.  If false, then the Datum contains
 	 * a pointer to the information.
 	 */
-	bool		constbyval;
-	/* token location, or -1 if unknown */
-	int			location;
+	bool		constbyval pg_node_attr(query_jumble_ignore);
+	/*
+	 * token location, or -1 if unknown.  All constants are tracked as
+	 * locations in query jumbling, to be marked as parameters.
+	 */
+	int			location pg_node_attr(query_jumble_location);
 } Const;
 
 /*
@@ -324,6 +329,7 @@ typedef enum ParamKind
 	PARAM_MULTIEXPR
 } ParamKind;
 
+/* typmod and collation information are irrelevant for the query jumbling. */
 typedef struct Param
 {
 	Expr		xpr;
@@ -331,9 +337,9 @@ typedef struct Param
 	int			paramid;		/* numeric ID for parameter */
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
 	/* typmod value, if known */
-	int32		paramtypmod;
+	int32		paramtypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			paramcollid;
+	Oid			paramcollid pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } Param;
@@ -386,6 +392,9 @@ typedef struct Param
  * and can share the result.  Aggregates with same 'transno' but different
  * 'aggno' can share the same transition state, only the final function needs
  * to be called separately.
+ *
+ * Information related to collations, transition types and internal states
+ * are irrelevant for the query jumbling.
  */
 typedef struct Aggref
 {
@@ -395,22 +404,22 @@ typedef struct Aggref
 	Oid			aggfnoid;
 
 	/* type Oid of result of the aggregate */
-	Oid			aggtype;
+	Oid			aggtype pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			aggcollid;
+	Oid			aggcollid pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * type Oid of aggregate's transition value; ignored for equal since it
 	 * might not be set yet
 	 */
-	Oid			aggtranstype pg_node_attr(equal_ignore);
+	Oid			aggtranstype pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* type Oids of direct and aggregated args */
-	List	   *aggargtypes;
+	List	   *aggargtypes pg_node_attr(query_jumble_ignore);
 
 	/* direct arguments, if an ordered-set agg */
 	List	   *aggdirectargs;
@@ -428,31 +437,31 @@ typedef struct Aggref
 	Expr	   *aggfilter;
 
 	/* true if argument list was really '*' */
-	bool		aggstar;
+	bool		aggstar pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		aggvariadic;
+	bool		aggvariadic pg_node_attr(query_jumble_ignore);
 
 	/* aggregate kind (see pg_aggregate.h) */
-	char		aggkind;
+	char		aggkind pg_node_attr(query_jumble_ignore);
 
 	/* aggregate input already sorted */
-	bool		aggpresorted pg_node_attr(equal_ignore);
+	bool		aggpresorted pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* > 0 if agg belongs to outer query */
-	Index		agglevelsup;
+	Index		agglevelsup pg_node_attr(query_jumble_ignore);
 
 	/* expected agg-splitting mode of parent Agg */
-	AggSplit	aggsplit;
+	AggSplit	aggsplit pg_node_attr(query_jumble_ignore);
 
 	/* unique ID within the Agg node */
-	int			aggno;
+	int			aggno pg_node_attr(query_jumble_ignore);
 
 	/* unique ID of transition state in the Agg */
-	int			aggtransno;
+	int			aggtransno pg_node_attr(query_jumble_ignore);
 
 	/* token location, or -1 if unknown */
 	int			location;
@@ -481,19 +490,22 @@ typedef struct Aggref
  *
  * In raw parse output we have only the args list; parse analysis fills in the
  * refs list, and the planner fills in the cols list.
+ *
+ * All the fields used as information for an internal state are irrelevant
+ * for the query jumbling.
  */
 typedef struct GroupingFunc
 {
 	Expr		xpr;
 
 	/* arguments, not evaluated but kept for benefit of EXPLAIN etc. */
-	List	   *args;
+	List	   *args pg_node_attr(query_jumble_ignore);
 
 	/* ressortgrouprefs of arguments */
 	List	   *refs pg_node_attr(equal_ignore);
 
 	/* actual column positions set by planner */
-	List	   *cols pg_node_attr(equal_ignore);
+	List	   *cols pg_node_attr(equal_ignore, query_jumble_ignore);
 
 	/* same as Aggref.agglevelsup */
 	Index		agglevelsup;
@@ -504,6 +516,9 @@ typedef struct GroupingFunc
 
 /*
  * WindowFunc
+ *
+ * Collation information is irrelevant for the query jumbling, as is the
+ * internal state information of the node like "winstar" and "winagg".
  */
 typedef struct WindowFunc
 {
@@ -511,11 +526,11 @@ typedef struct WindowFunc
 	/* pg_proc Oid of the function */
 	Oid			winfnoid;
 	/* type Oid of result of the window function */
-	Oid			wintype;
+	Oid			wintype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			wincollid;
+	Oid			wincollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the window function */
 	List	   *args;
 	/* FILTER expression, if any */
@@ -523,9 +538,9 @@ typedef struct WindowFunc
 	/* index of associated WindowClause */
 	Index		winref;
 	/* true if argument list was really '*' */
-	bool		winstar;
+	bool		winstar pg_node_attr(query_jumble_ignore);
 	/* is function a simple aggregate? */
-	bool		winagg;
+	bool		winagg pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } WindowFunc;
@@ -564,6 +579,8 @@ typedef struct WindowFunc
  * subscripting logic.  Likewise, reftypmod and refcollid will match the
  * container's properties in a store, but could be different in a fetch.
  *
+ * Any internal state data is ignored for the query jumbling.
+ *
  * Note: for the cases where a container is returned, if refexpr yields a R/W
  * expanded container, then the implementation is allowed to modify that
  * object in-place and return the same object.
@@ -572,15 +589,15 @@ typedef struct SubscriptingRef
 {
 	Expr		xpr;
 	/* type of the container proper */
-	Oid			refcontainertype;
+	Oid			refcontainertype pg_node_attr(query_jumble_ignore);
 	/* the container type's pg_type.typelem */
-	Oid			refelemtype;
+	Oid			refelemtype pg_node_attr(query_jumble_ignore);
 	/* type of the SubscriptingRef's result */
-	Oid			refrestype;
+	Oid			refrestype pg_node_attr(query_jumble_ignore);
 	/* typmod of the result */
-	int32		reftypmod;
+	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
-	Oid			refcollid;
+	Oid			refcollid pg_node_attr(query_jumble_ignore);
 	/* expressions that evaluate to upper container indexes */
 	List	   *refupperindexpr;
 
@@ -631,6 +648,9 @@ typedef enum CoercionForm
 
 /*
  * FuncExpr - expression node for a function call
+ *
+ * Collation information is irrelevant for the query jumbling, only the
+ * arguments and the function OID matter.
  */
 typedef struct FuncExpr
 {
@@ -638,21 +658,21 @@ typedef struct FuncExpr
 	/* PG_PROC OID of the function */
 	Oid			funcid;
 	/* PG_TYPE OID of result value */
-	Oid			funcresulttype;
+	Oid			funcresulttype pg_node_attr(query_jumble_ignore);
 	/* true if function returns set */
-	bool		funcretset;
+	bool		funcretset pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * true if variadic arguments have been combined into an array last
 	 * argument
 	 */
-	bool		funcvariadic;
+	bool		funcvariadic pg_node_attr(query_jumble_ignore);
 	/* how to display this function call */
-	CoercionForm funcformat;
+	CoercionForm funcformat pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			funccollid;
+	Oid			funccollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* arguments to the function */
 	List	   *args;
 	/* token location, or -1 if unknown */
@@ -679,7 +699,7 @@ typedef struct NamedArgExpr
 	/* the argument expression */
 	Expr	   *arg;
 	/* the name */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* argument's number in positional notation */
 	int			argnumber;
 	/* argument name location, or -1 if unknown */
@@ -695,6 +715,9 @@ typedef struct NamedArgExpr
  * 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.
+ *
+ * Internal state information and collation data is irrelevant for the query
+ * jumbling.
  */
 typedef struct OpExpr
 {
@@ -704,19 +727,19 @@ typedef struct OpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of underlying function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_TYPE OID of result value */
-	Oid			opresulttype;
+	Oid			opresulttype pg_node_attr(query_jumble_ignore);
 
 	/* true if operator returns set */
-	bool		opretset;
+	bool		opretset pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation of result */
-	Oid			opcollid;
+	Oid			opcollid pg_node_attr(query_jumble_ignore);
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/* arguments to the operator (1 or 2) */
 	List	   *args;
@@ -772,6 +795,9 @@ typedef OpExpr NullIfExpr;
  * 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.
+ *
+ * OID entries of the internal function types are irrelevant for the query
+ * jumbling, but the operator OID and the arguments are.
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -781,19 +807,19 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;
 
 	/* PG_PROC OID of comparison function */
-	Oid			opfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			opfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_PROC OID of hash func or InvalidOid */
-	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			hashfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* PG_PROC OID of negator of opfuncid function or InvalidOid.  See above */
-	Oid			negfuncid pg_node_attr(equal_ignore_if_zero);
+	Oid			negfuncid pg_node_attr(equal_ignore_if_zero, query_jumble_ignore);
 
 	/* true for ANY, false for ALL */
 	bool		useOr;
 
 	/* OID of collation that operator should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 
 	/* the scalar and array operands */
 	List	   *args;
@@ -895,7 +921,7 @@ typedef struct SubLink
 	int			subLinkId;		/* ID (1..n); 0 if not MULTIEXPR */
 	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
 	/* originally specified operator name */
-	List	   *operName;
+	List	   *operName pg_node_attr(query_jumble_ignore);
 	/* subselect as Query* or raw parsetree */
 	Node	   *subselect;
 	int			location;		/* token location, or -1 if unknown */
@@ -1007,11 +1033,11 @@ typedef struct FieldSelect
 	Expr	   *arg;			/* input expression */
 	AttrNumber	fieldnum;		/* attribute number of field to extract */
 	/* type of the field (result type of this node) */
-	Oid			resulttype;
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* output typmod (usually -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation of the field */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 } FieldSelect;
 
 /* ----------------
@@ -1038,9 +1064,9 @@ typedef struct FieldStore
 	Expr	   *arg;			/* input tuple value */
 	List	   *newvals;		/* new value(s) for field(s) */
 	/* integer list of field attnums */
-	List	   *fieldnums;
+	List	   *fieldnums pg_node_attr(query_jumble_ignore);
 	/* type of result (same as type of arg) */
-	Oid			resulttype;
+	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 } FieldStore;
 
@@ -1063,11 +1089,11 @@ typedef struct RelabelType
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion expression */
 	/* output typmod (usually -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm relabelformat;
+	CoercionForm relabelformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } RelabelType;
 
@@ -1087,9 +1113,9 @@ typedef struct CoerceViaIO
 	Oid			resulttype;		/* output type of coercion */
 	/* output typmod is not stored, but is presumed -1 */
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coerceformat;
+	CoercionForm coerceformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } CoerceViaIO;
 
@@ -1113,11 +1139,11 @@ typedef struct ArrayCoerceExpr
 	Expr	   *elemexpr;		/* expression representing per-element work */
 	Oid			resulttype;		/* output type of coercion (an array type) */
 	/* output typmod (also element typmod) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coerceformat;
+	CoercionForm coerceformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } ArrayCoerceExpr;
 
@@ -1141,7 +1167,7 @@ typedef struct ConvertRowtypeExpr
 	Oid			resulttype;		/* output type (always a composite type) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
 	/* how to display this node */
-	CoercionForm convertformat;
+	CoercionForm convertformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } ConvertRowtypeExpr;
 
@@ -1186,9 +1212,9 @@ typedef struct CaseExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			casetype;
+	Oid			casetype pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			casecollid;
+	Oid			casecollid pg_node_attr(query_jumble_ignore);
 	Expr	   *arg;			/* implicit equality comparison argument */
 	List	   *args;			/* the arguments (list of WHEN clauses) */
 	Expr	   *defresult;		/* the default result (ELSE clause) */
@@ -1231,9 +1257,9 @@ typedef struct CaseTestExpr
 	Expr		xpr;
 	Oid			typeId;			/* type for substituted value */
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 } CaseTestExpr;
 
 /*
@@ -1248,15 +1274,15 @@ typedef struct ArrayExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			array_typeid;
+	Oid			array_typeid pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			array_collid;
+	Oid			array_collid pg_node_attr(query_jumble_ignore);
 	/* common type of array elements */
-	Oid			element_typeid;
+	Oid			element_typeid pg_node_attr(query_jumble_ignore);
 	/* the array elements or sub-arrays */
 	List	   *elements;
 	/* true if elements are sub-arrays */
-	bool		multidims;
+	bool		multidims pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } ArrayExpr;
@@ -1288,7 +1314,7 @@ typedef struct RowExpr
 	List	   *args;			/* the fields */
 
 	/* RECORDOID or a composite type's ID */
-	Oid			row_typeid;
+	Oid			row_typeid pg_node_attr(query_jumble_ignore);
 
 	/*
 	 * row_typeid cannot be a domain over composite, only plain composite.  To
@@ -1304,10 +1330,10 @@ typedef struct RowExpr
 	 */
 
 	/* how to display this node */
-	CoercionForm row_format;
+	CoercionForm row_format pg_node_attr(query_jumble_ignore);
 
 	/* list of String, or NIL */
-	List	   *colnames;
+	List	   *colnames pg_node_attr(query_jumble_ignore);
 
 	int			location;		/* token location, or -1 if unknown */
 } RowExpr;
@@ -1344,11 +1370,11 @@ typedef struct RowCompareExpr
 	/* LT LE GE or GT, never EQ or NE */
 	RowCompareType rctype;
 	/* OID list of pairwise comparison ops */
-	List	   *opnos;
+	List	   *opnos pg_node_attr(query_jumble_ignore);
 	/* OID list of containing operator families */
-	List	   *opfamilies;
+	List	   *opfamilies pg_node_attr(query_jumble_ignore);
 	/* OID list of collations for comparisons */
-	List	   *inputcollids;
+	List	   *inputcollids pg_node_attr(query_jumble_ignore);
 	/* the left-hand input arguments */
 	List	   *largs;
 	/* the right-hand input arguments */
@@ -1362,9 +1388,9 @@ typedef struct CoalesceExpr
 {
 	Expr		xpr;
 	/* type of expression result */
-	Oid			coalescetype;
+	Oid			coalescetype pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			coalescecollid;
+	Oid			coalescecollid pg_node_attr(query_jumble_ignore);
 	/* the arguments */
 	List	   *args;
 	/* token location, or -1 if unknown */
@@ -1384,11 +1410,11 @@ typedef struct MinMaxExpr
 {
 	Expr		xpr;
 	/* common type of arguments and result */
-	Oid			minmaxtype;
+	Oid			minmaxtype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
-	Oid			minmaxcollid;
+	Oid			minmaxcollid pg_node_attr(query_jumble_ignore);
 	/* OID of collation that function should use */
-	Oid			inputcollid;
+	Oid			inputcollid pg_node_attr(query_jumble_ignore);
 	/* function to execute */
 	MinMaxOp	op;
 	/* the arguments */
@@ -1432,18 +1458,18 @@ typedef struct XmlExpr
 	/* xml function ID */
 	XmlExprOp	op;
 	/* name in xml(NAME foo ...) syntaxes */
-	char	   *name;
+	char	   *name pg_node_attr(query_jumble_ignore);
 	/* non-XML expressions for xml_attributes */
 	List	   *named_args;
 	/* parallel list of String values */
-	List	   *arg_names;
+	List	   *arg_names pg_node_attr(query_jumble_ignore);
 	/* list of expressions */
 	List	   *args;
 	/* DOCUMENT or CONTENT */
-	XmlOptionType xmloption;
+	XmlOptionType xmloption pg_node_attr(query_jumble_ignore);
 	/* target type/typmod for XMLSERIALIZE */
-	Oid			type;
-	int32		typmod;
+	Oid			type pg_node_attr(query_jumble_ignore);
+	int32		typmod pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } XmlExpr;
@@ -1478,7 +1504,7 @@ typedef struct NullTest
 	Expr	   *arg;			/* input expression */
 	NullTestType nulltesttype;	/* IS NULL, IS NOT NULL */
 	/* T to perform field-by-field null checks */
-	bool		argisrow;
+	bool		argisrow pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } NullTest;
 
@@ -1512,6 +1538,8 @@ typedef struct BooleanTest
  * checked will be determined.  If the value passes, it is returned as the
  * result; if not, an error is raised.  Note that this is equivalent to
  * RelabelType in the scenario where no constraints are applied.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct CoerceToDomain
 {
@@ -1519,11 +1547,11 @@ typedef struct CoerceToDomain
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* domain type ID (result type) */
 	/* output typmod (currently always -1) */
-	int32		resulttypmod;
+	int32		resulttypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
-	Oid			resultcollid;
+	Oid			resultcollid pg_node_attr(query_jumble_ignore);
 	/* how to display this node */
-	CoercionForm coercionformat;
+	CoercionForm coercionformat pg_node_attr(query_jumble_ignore);
 	int			location;		/* token location, or -1 if unknown */
 } CoerceToDomain;
 
@@ -1542,9 +1570,9 @@ typedef struct CoerceToDomainValue
 	/* type for substituted value */
 	Oid			typeId;
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } CoerceToDomainValue;
@@ -1555,6 +1583,8 @@ typedef struct CoerceToDomainValue
  * This is not an executable expression: it must be replaced by the actual
  * column default expression during rewriting.  But it is convenient to
  * treat it as an expression node during parsing and rewriting.
+ *
+ * typemod and collation are irrelevant for the query jumbling.
  */
 typedef struct SetToDefault
 {
@@ -1562,9 +1592,9 @@ typedef struct SetToDefault
 	/* type for substituted value */
 	Oid			typeId;
 	/* typemod for substituted value */
-	int32		typeMod;
+	int32		typeMod pg_node_attr(query_jumble_ignore);
 	/* collation for the substituted value */
-	Oid			collation;
+	Oid			collation pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	int			location;
 } SetToDefault;
@@ -1682,15 +1712,15 @@ typedef struct TargetEntry
 	/* attribute number (see notes above) */
 	AttrNumber	resno;
 	/* name of the column (could be NULL) */
-	char	   *resname;
+	char	   *resname pg_node_attr(query_jumble_ignore);
 	/* nonzero if referenced by a sort/group clause */
 	Index		ressortgroupref;
 	/* OID of column's source table */
-	Oid			resorigtbl;
+	Oid			resorigtbl pg_node_attr(query_jumble_ignore);
 	/* column's number in source table */
-	AttrNumber	resorigcol;
+	AttrNumber	resorigcol pg_node_attr(query_jumble_ignore);
 	/* set to true to eliminate the attribute from final target list */
-	bool		resjunk;
+	bool		resjunk pg_node_attr(query_jumble_ignore);
 } TargetEntry;
 
 
@@ -1773,13 +1803,13 @@ typedef struct JoinExpr
 	Node	   *larg;			/* left subtree */
 	Node	   *rarg;			/* right subtree */
 	/* USING clause, if any (list of String) */
-	List	   *usingClause;
+	List	   *usingClause pg_node_attr(query_jumble_ignore);
 	/* alias attached to USING clause, if any */
-	Alias	   *join_using_alias;
+	Alias	   *join_using_alias pg_node_attr(query_jumble_ignore);
 	/* qualifiers on join, if any */
 	Node	   *quals;
 	/* user-written alias clause, if any */
-	Alias	   *alias;
+	Alias	   *alias pg_node_attr(query_jumble_ignore);
 	/* RT index assigned for join, or 0 */
 	int			rtindex;
 } JoinExpr;
diff --git a/src/backend/nodes/README b/src/backend/nodes/README
index 489a67eb89..7cf6e3b041 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -51,6 +51,7 @@ FILES IN THIS DIRECTORY (src/backend/nodes/)
 	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
+	queryjumblefuncs.c - compute a node tree for query jumbling (*)
 
     (*) - Most functions in these files are generated by
     gen_node_support.pl and #include'd there.
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index b3c1ead496..471333dc80 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -121,6 +121,8 @@ my %node_type_info;
 my @no_copy;
 # node types we don't want equal support for
 my @no_equal;
+# node types we don't want jumble support for
+my @no_query_jumble;
 # node types we don't want read support for
 my @no_read;
 # node types we don't want read/write support for
@@ -155,12 +157,13 @@ 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);
-# Lists are specially treated in all four support files, too.
+# Lists are specially treated in all five support files, too.
 # (Ideally we'd mark List as "special copy/equal" not "no copy/equal".
 # But until there's other use-cases for that, just hot-wire the tests
 # that would need to distinguish.)
 push @no_copy,            qw(List);
 push @no_equal,           qw(List);
+push @no_query_jumble,          qw(List);
 push @special_read_write, qw(List);
 
 # Nodes with custom copy/equal implementations are skipped from
@@ -170,6 +173,9 @@ my @custom_copy_equal;
 # Similarly for custom read/write implementations.
 my @custom_read_write;
 
+# Similarly for custom query jumble implementation.
+my @custom_query_jumble;
+
 # Track node types with manually assigned NodeTag numbers.
 my %manual_nodetag_number;
 
@@ -319,6 +325,10 @@ foreach my $infile (@ARGV)
 						{
 							push @custom_read_write, $in_struct;
 						}
+						elsif ($attr eq 'custom_query_jumble')
+						{
+							push @custom_query_jumble, $in_struct;
+						}
 						elsif ($attr eq 'no_copy')
 						{
 							push @no_copy, $in_struct;
@@ -332,6 +342,10 @@ foreach my $infile (@ARGV)
 							push @no_copy,  $in_struct;
 							push @no_equal, $in_struct;
 						}
+						elsif ($attr eq 'no_query_jumble')
+						{
+							push @no_query_jumble, $in_struct;
+						}
 						elsif ($attr eq 'no_read')
 						{
 							push @no_read, $in_struct;
@@ -457,6 +471,8 @@ foreach my $infile (@ARGV)
 								equal_as_scalar
 								equal_ignore
 								equal_ignore_if_zero
+								query_jumble_ignore
+								query_jumble_location
 								read_write_ignore
 								write_only_relids
 								write_only_nondefault_pathtarget
@@ -1225,6 +1241,102 @@ close $ofs;
 close $rfs;
 
 
+# queryjumblefuncs.c
+
+push @output_files, 'queryjumblefuncs.funcs.c';
+open my $jff, '>', "$output_path/queryjumblefuncs.funcs.c$tmpext" or die $!;
+push @output_files, 'queryjumblefuncs.switch.c';
+open my $jfs, '>', "$output_path/queryjumblefuncs.switch.c$tmpext" or die $!;
+
+printf $jff $header_comment, 'queryjumblefuncs.funcs.c';
+printf $jfs $header_comment, 'queryjumblefuncs.switch.c';
+
+print $jff $node_includes;
+
+foreach my $n (@node_types)
+{
+	next if elem $n, @abstract_types;
+	next if elem $n, @nodetag_only;
+	my $struct_no_query_jumble = (elem $n, @no_query_jumble);
+
+	print $jfs "\t\t\tcase T_${n}:\n"
+	  . "\t\t\t\t_jumble${n}(jstate, expr);\n"
+	  . "\t\t\t\tbreak;\n"
+	  unless $struct_no_query_jumble;
+
+	next if elem $n, @custom_query_jumble;
+
+	print $jff "
+static void
+_jumble${n}(JumbleState *jstate, Node *node)
+{
+\t${n} *expr = (${n} *) node;\n
+" unless $struct_no_query_jumble;
+
+	# 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 $query_jumble_ignore = $struct_no_query_jumble;
+		my $query_jumble_location = 0;
+
+		# extract per-field attributes
+		foreach my $a (@a)
+		{
+			if ($a eq 'query_jumble_ignore')
+			{
+				$query_jumble_ignore = 1;
+			}
+			elsif ($a eq 'query_jumble_location')
+			{
+				$query_jumble_location = 1;
+			}
+		}
+
+		# node type
+		if (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+			and elem $1, @node_types)
+		{
+			print $jff "\tJUMBLE_NODE($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		elsif ($t eq 'int' && $f =~ 'location$')
+		{
+			# Track the node's location only if directly requested.
+			if ($query_jumble_location)
+			{
+				print $jff "\tJUMBLE_LOCATION($f);\n"
+				  unless $query_jumble_ignore;
+			}
+		}
+		elsif ($t eq 'char*')
+		{
+			print $jff "\tJUMBLE_STRING($f);\n"
+			  unless $query_jumble_ignore;
+		}
+		else
+		{
+			print $jff "\tJUMBLE_FIELD($f);\n"
+			  unless $query_jumble_ignore;
+		}
+	}
+
+	# Some nodes have no attributes like CheckPointStmt,
+	# so tweak things for empty contents.
+	if (scalar(@{ $node_type_info{$n}->{fields} }) == 0)
+	{
+		print $jff "\t(void) expr;\n"
+		  unless $struct_no_query_jumble;
+	}
+
+	print $jff "}
+" unless $struct_no_query_jumble;
+}
+
+close $jff;
+close $jfs;
+
 # now rename the temporary files to their final names
 foreach my $file (@output_files)
 {
diff --git a/src/backend/nodes/meson.build b/src/backend/nodes/meson.build
index 9230515e7f..31467a12d3 100644
--- a/src/backend/nodes/meson.build
+++ b/src/backend/nodes/meson.build
@@ -10,7 +10,6 @@ backend_sources += files(
   'nodes.c',
   'params.c',
   'print.c',
-  'queryjumblefuncs.c',
   'read.c',
   'tidbitmap.c',
   'value.c',
@@ -21,6 +20,7 @@ backend_sources += files(
 nodefunc_sources = files(
   'copyfuncs.c',
   'equalfuncs.c',
+  'queryjumblefuncs.c',
   'outfuncs.c',
   'readfuncs.c',
 )
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 16084842a3..16fdf7164a 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -45,15 +45,12 @@ int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
 /* True when compute_query_id is ON, or AUTO and a module requests them */
 bool		query_id_enabled = false;
 
-static uint64 compute_utility_query_id(const char *query_text,
-									   int query_location, int query_len);
 static void AppendJumble(JumbleState *jstate,
 						 const unsigned char *item, Size size);
-static void JumbleQueryInternal(JumbleState *jstate, Query *query);
-static void JumbleRangeTable(JumbleState *jstate, List *rtable);
-static void JumbleRowMarks(JumbleState *jstate, List *rowMarks);
-static void JumbleExpr(JumbleState *jstate, Node *node);
 static void RecordConstLocation(JumbleState *jstate, int location);
+static void _jumbleNode(JumbleState *jstate, Node *node);
+static void _jumbleList(JumbleState *jstate, Node *node);
+static void _jumbleRangeTblEntry(JumbleState *jstate, Node *node);
 
 /*
  * Given a possibly multi-statement source string, confine our attention to the
@@ -105,38 +102,29 @@ JumbleQuery(Query *query, const char *querytext)
 
 	Assert(IsQueryIdEnabled());
 
-	if (query->utilityStmt)
-	{
-		query->queryId = compute_utility_query_id(querytext,
-												  query->stmt_location,
-												  query->stmt_len);
-	}
-	else
-	{
-		jstate = (JumbleState *) palloc(sizeof(JumbleState));
+	jstate = (JumbleState *) palloc(sizeof(JumbleState));
 
-		/* Set up workspace for query jumbling */
-		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-		jstate->jumble_len = 0;
-		jstate->clocations_buf_size = 32;
-		jstate->clocations = (LocationLen *)
-			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-		jstate->clocations_count = 0;
-		jstate->highest_extern_param_id = 0;
+	/* Set up workspace for query jumbling */
+	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+	jstate->jumble_len = 0;
+	jstate->clocations_buf_size = 32;
+	jstate->clocations = (LocationLen *)
+		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+	jstate->clocations_count = 0;
+	jstate->highest_extern_param_id = 0;
 
-		/* Compute query ID and mark the Query node with it */
-		JumbleQueryInternal(jstate, query);
-		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-														  jstate->jumble_len,
-														  0));
+	/* Compute query ID and mark the Query node with it */
+	_jumbleNode(jstate, (Node *) query);
+	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+													  jstate->jumble_len,
+													  0));
 
-		/*
-		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-		 * prevent confusion with the utility-statement case.
-		 */
-		if (query->queryId == UINT64CONST(0))
-			query->queryId = UINT64CONST(1);
-	}
+	/*
+	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+	 * prevent confusion with the utility-statement case.
+	 */
+	if (query->queryId == UINT64CONST(0))
+		query->queryId = UINT64CONST(1);
 
 	return jstate;
 }
@@ -154,34 +142,6 @@ EnableQueryId(void)
 		query_id_enabled = true;
 }
 
-/*
- * Compute a query identifier for the given utility query string.
- */
-static uint64
-compute_utility_query_id(const char *query_text, int query_location, int query_len)
-{
-	uint64		queryId;
-	const char *sql;
-
-	/*
-	 * Confine our attention to the relevant part of the string, if the query
-	 * is a portion of a multi-statement source string.
-	 */
-	sql = CleanQuerytext(query_text, &query_location, &query_len);
-
-	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
-											   query_len, 0));
-
-	/*
-	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
-	 * 2 instead, queryID 1 is already in use for normal statements.
-	 */
-	if (queryId == UINT64CONST(0))
-		queryId = UINT64CONST(2);
-
-	return queryId;
-}
-
 /*
  * AppendJumble: Append a value that is substantive in a given query to
  * the current jumble.
@@ -219,621 +179,6 @@ AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
 	jstate->jumble_len = jumble_len;
 }
 
-/*
- * Wrappers around AppendJumble to encapsulate details of serialization
- * of individual local variable elements.
- */
-#define APP_JUMB(item) \
-	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
-#define APP_JUMB_STRING(str) \
-	AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1)
-
-/*
- * JumbleQueryInternal: Selectively serialize the query tree, appending
- * significant data to the "query jumble" while ignoring nonsignificant data.
- *
- * Rule of thumb for what to include is that we should ignore anything not
- * semantically significant (such as alias names) as well as anything that can
- * be deduced from child nodes (else we'd just be double-hashing that piece
- * of information).
- */
-static void
-JumbleQueryInternal(JumbleState *jstate, Query *query)
-{
-	Assert(IsA(query, Query));
-	Assert(query->utilityStmt == NULL);
-
-	APP_JUMB(query->commandType);
-	/* resultRelation is usually predictable from commandType */
-	JumbleExpr(jstate, (Node *) query->cteList);
-	JumbleRangeTable(jstate, query->rtable);
-	JumbleExpr(jstate, (Node *) query->jointree);
-	JumbleExpr(jstate, (Node *) query->mergeActionList);
-	JumbleExpr(jstate, (Node *) query->targetList);
-	JumbleExpr(jstate, (Node *) query->onConflict);
-	JumbleExpr(jstate, (Node *) query->returningList);
-	JumbleExpr(jstate, (Node *) query->groupClause);
-	APP_JUMB(query->groupDistinct);
-	JumbleExpr(jstate, (Node *) query->groupingSets);
-	JumbleExpr(jstate, query->havingQual);
-	JumbleExpr(jstate, (Node *) query->windowClause);
-	JumbleExpr(jstate, (Node *) query->distinctClause);
-	JumbleExpr(jstate, (Node *) query->sortClause);
-	JumbleExpr(jstate, query->limitOffset);
-	JumbleExpr(jstate, query->limitCount);
-	APP_JUMB(query->limitOption);
-	JumbleRowMarks(jstate, query->rowMarks);
-	JumbleExpr(jstate, query->setOperations);
-}
-
-/*
- * Jumble a range table
- */
-static void
-JumbleRangeTable(JumbleState *jstate, List *rtable)
-{
-	ListCell   *lc;
-
-	foreach(lc, rtable)
-	{
-		RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-		APP_JUMB(rte->rtekind);
-		switch (rte->rtekind)
-		{
-			case RTE_RELATION:
-				APP_JUMB(rte->relid);
-				JumbleExpr(jstate, (Node *) rte->tablesample);
-				APP_JUMB(rte->inh);
-				break;
-			case RTE_SUBQUERY:
-				JumbleQueryInternal(jstate, rte->subquery);
-				break;
-			case RTE_JOIN:
-				APP_JUMB(rte->jointype);
-				break;
-			case RTE_FUNCTION:
-				JumbleExpr(jstate, (Node *) rte->functions);
-				break;
-			case RTE_TABLEFUNC:
-				JumbleExpr(jstate, (Node *) rte->tablefunc);
-				break;
-			case RTE_VALUES:
-				JumbleExpr(jstate, (Node *) rte->values_lists);
-				break;
-			case RTE_CTE:
-
-				/*
-				 * Depending on the CTE name here isn't ideal, but it's the
-				 * only info we have to identify the referenced WITH item.
-				 */
-				APP_JUMB_STRING(rte->ctename);
-				APP_JUMB(rte->ctelevelsup);
-				break;
-			case RTE_NAMEDTUPLESTORE:
-				APP_JUMB_STRING(rte->enrname);
-				break;
-			case RTE_RESULT:
-				break;
-			default:
-				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
-				break;
-		}
-	}
-}
-
-/*
- * Jumble a rowMarks list
- */
-static void
-JumbleRowMarks(JumbleState *jstate, List *rowMarks)
-{
-	ListCell   *lc;
-
-	foreach(lc, rowMarks)
-	{
-		RowMarkClause *rowmark = lfirst_node(RowMarkClause, lc);
-
-		if (!rowmark->pushedDown)
-		{
-			APP_JUMB(rowmark->rti);
-			APP_JUMB(rowmark->strength);
-			APP_JUMB(rowmark->waitPolicy);
-		}
-	}
-}
-
-/*
- * Jumble an expression tree
- *
- * In general this function should handle all the same node types that
- * expression_tree_walker() does, and therefore it's coded to be as parallel
- * to that function as possible.  However, since we are only invoked on
- * queries immediately post-parse-analysis, we need not handle node types
- * that only appear in planning.
- *
- * Note: the reason we don't simply use expression_tree_walker() is that the
- * point of that function is to support tree walkers that don't care about
- * most tree node types, but here we care about all types.  We should complain
- * about any unrecognized node type.
- */
-static void
-JumbleExpr(JumbleState *jstate, Node *node)
-{
-	ListCell   *temp;
-
-	if (node == NULL)
-		return;
-
-	/* Guard against stack overflow due to overly complex expressions */
-	check_stack_depth();
-
-	/*
-	 * We always emit the node's NodeTag, then any additional fields that are
-	 * considered significant, and then we recurse to any child nodes.
-	 */
-	APP_JUMB(node->type);
-
-	switch (nodeTag(node))
-	{
-		case T_Var:
-			{
-				Var		   *var = (Var *) node;
-
-				APP_JUMB(var->varno);
-				APP_JUMB(var->varattno);
-				APP_JUMB(var->varlevelsup);
-			}
-			break;
-		case T_Const:
-			{
-				Const	   *c = (Const *) node;
-
-				/* We jumble only the constant's type, not its value */
-				APP_JUMB(c->consttype);
-				/* Also, record its parse location for query normalization */
-				RecordConstLocation(jstate, c->location);
-			}
-			break;
-		case T_Param:
-			{
-				Param	   *p = (Param *) node;
-
-				APP_JUMB(p->paramkind);
-				APP_JUMB(p->paramid);
-				APP_JUMB(p->paramtype);
-				/* Also, track the highest external Param id */
-				if (p->paramkind == PARAM_EXTERN &&
-					p->paramid > jstate->highest_extern_param_id)
-					jstate->highest_extern_param_id = p->paramid;
-			}
-			break;
-		case T_Aggref:
-			{
-				Aggref	   *expr = (Aggref *) node;
-
-				APP_JUMB(expr->aggfnoid);
-				JumbleExpr(jstate, (Node *) expr->aggdirectargs);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggorder);
-				JumbleExpr(jstate, (Node *) expr->aggdistinct);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_GroupingFunc:
-			{
-				GroupingFunc *grpnode = (GroupingFunc *) node;
-
-				JumbleExpr(jstate, (Node *) grpnode->refs);
-				APP_JUMB(grpnode->agglevelsup);
-			}
-			break;
-		case T_WindowFunc:
-			{
-				WindowFunc *expr = (WindowFunc *) node;
-
-				APP_JUMB(expr->winfnoid);
-				APP_JUMB(expr->winref);
-				JumbleExpr(jstate, (Node *) expr->args);
-				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
-			break;
-		case T_SubscriptingRef:
-			{
-				SubscriptingRef *sbsref = (SubscriptingRef *) node;
-
-				JumbleExpr(jstate, (Node *) sbsref->refupperindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refexpr);
-				JumbleExpr(jstate, (Node *) sbsref->refassgnexpr);
-			}
-			break;
-		case T_FuncExpr:
-			{
-				FuncExpr   *expr = (FuncExpr *) node;
-
-				APP_JUMB(expr->funcid);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_NamedArgExpr:
-			{
-				NamedArgExpr *nae = (NamedArgExpr *) node;
-
-				APP_JUMB(nae->argnumber);
-				JumbleExpr(jstate, (Node *) nae->arg);
-			}
-			break;
-		case T_OpExpr:
-		case T_DistinctExpr:	/* struct-equivalent to OpExpr */
-		case T_NullIfExpr:		/* struct-equivalent to OpExpr */
-			{
-				OpExpr	   *expr = (OpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_ScalarArrayOpExpr:
-			{
-				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
-
-				APP_JUMB(expr->opno);
-				APP_JUMB(expr->useOr);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_BoolExpr:
-			{
-				BoolExpr   *expr = (BoolExpr *) node;
-
-				APP_JUMB(expr->boolop);
-				JumbleExpr(jstate, (Node *) expr->args);
-			}
-			break;
-		case T_SubLink:
-			{
-				SubLink    *sublink = (SubLink *) node;
-
-				APP_JUMB(sublink->subLinkType);
-				APP_JUMB(sublink->subLinkId);
-				JumbleExpr(jstate, (Node *) sublink->testexpr);
-				JumbleQueryInternal(jstate, castNode(Query, sublink->subselect));
-			}
-			break;
-		case T_FieldSelect:
-			{
-				FieldSelect *fs = (FieldSelect *) node;
-
-				APP_JUMB(fs->fieldnum);
-				JumbleExpr(jstate, (Node *) fs->arg);
-			}
-			break;
-		case T_FieldStore:
-			{
-				FieldStore *fstore = (FieldStore *) node;
-
-				JumbleExpr(jstate, (Node *) fstore->arg);
-				JumbleExpr(jstate, (Node *) fstore->newvals);
-			}
-			break;
-		case T_RelabelType:
-			{
-				RelabelType *rt = (RelabelType *) node;
-
-				APP_JUMB(rt->resulttype);
-				JumbleExpr(jstate, (Node *) rt->arg);
-			}
-			break;
-		case T_CoerceViaIO:
-			{
-				CoerceViaIO *cio = (CoerceViaIO *) node;
-
-				APP_JUMB(cio->resulttype);
-				JumbleExpr(jstate, (Node *) cio->arg);
-			}
-			break;
-		case T_ArrayCoerceExpr:
-			{
-				ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node;
-
-				APP_JUMB(acexpr->resulttype);
-				JumbleExpr(jstate, (Node *) acexpr->arg);
-				JumbleExpr(jstate, (Node *) acexpr->elemexpr);
-			}
-			break;
-		case T_ConvertRowtypeExpr:
-			{
-				ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node;
-
-				APP_JUMB(crexpr->resulttype);
-				JumbleExpr(jstate, (Node *) crexpr->arg);
-			}
-			break;
-		case T_CollateExpr:
-			{
-				CollateExpr *ce = (CollateExpr *) node;
-
-				APP_JUMB(ce->collOid);
-				JumbleExpr(jstate, (Node *) ce->arg);
-			}
-			break;
-		case T_CaseExpr:
-			{
-				CaseExpr   *caseexpr = (CaseExpr *) node;
-
-				JumbleExpr(jstate, (Node *) caseexpr->arg);
-				foreach(temp, caseexpr->args)
-				{
-					CaseWhen   *when = lfirst_node(CaseWhen, temp);
-
-					JumbleExpr(jstate, (Node *) when->expr);
-					JumbleExpr(jstate, (Node *) when->result);
-				}
-				JumbleExpr(jstate, (Node *) caseexpr->defresult);
-			}
-			break;
-		case T_CaseTestExpr:
-			{
-				CaseTestExpr *ct = (CaseTestExpr *) node;
-
-				APP_JUMB(ct->typeId);
-			}
-			break;
-		case T_ArrayExpr:
-			JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements);
-			break;
-		case T_RowExpr:
-			JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args);
-			break;
-		case T_RowCompareExpr:
-			{
-				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-
-				APP_JUMB(rcexpr->rctype);
-				JumbleExpr(jstate, (Node *) rcexpr->largs);
-				JumbleExpr(jstate, (Node *) rcexpr->rargs);
-			}
-			break;
-		case T_CoalesceExpr:
-			JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args);
-			break;
-		case T_MinMaxExpr:
-			{
-				MinMaxExpr *mmexpr = (MinMaxExpr *) node;
-
-				APP_JUMB(mmexpr->op);
-				JumbleExpr(jstate, (Node *) mmexpr->args);
-			}
-			break;
-		case T_XmlExpr:
-			{
-				XmlExpr    *xexpr = (XmlExpr *) node;
-
-				APP_JUMB(xexpr->op);
-				JumbleExpr(jstate, (Node *) xexpr->named_args);
-				JumbleExpr(jstate, (Node *) xexpr->args);
-			}
-			break;
-		case T_NullTest:
-			{
-				NullTest   *nt = (NullTest *) node;
-
-				APP_JUMB(nt->nulltesttype);
-				JumbleExpr(jstate, (Node *) nt->arg);
-			}
-			break;
-		case T_BooleanTest:
-			{
-				BooleanTest *bt = (BooleanTest *) node;
-
-				APP_JUMB(bt->booltesttype);
-				JumbleExpr(jstate, (Node *) bt->arg);
-			}
-			break;
-		case T_CoerceToDomain:
-			{
-				CoerceToDomain *cd = (CoerceToDomain *) node;
-
-				APP_JUMB(cd->resulttype);
-				JumbleExpr(jstate, (Node *) cd->arg);
-			}
-			break;
-		case T_CoerceToDomainValue:
-			{
-				CoerceToDomainValue *cdv = (CoerceToDomainValue *) node;
-
-				APP_JUMB(cdv->typeId);
-			}
-			break;
-		case T_SetToDefault:
-			{
-				SetToDefault *sd = (SetToDefault *) node;
-
-				APP_JUMB(sd->typeId);
-			}
-			break;
-		case T_CurrentOfExpr:
-			{
-				CurrentOfExpr *ce = (CurrentOfExpr *) node;
-
-				APP_JUMB(ce->cvarno);
-				if (ce->cursor_name)
-					APP_JUMB_STRING(ce->cursor_name);
-				APP_JUMB(ce->cursor_param);
-			}
-			break;
-		case T_NextValueExpr:
-			{
-				NextValueExpr *nve = (NextValueExpr *) node;
-
-				APP_JUMB(nve->seqid);
-				APP_JUMB(nve->typeId);
-			}
-			break;
-		case T_InferenceElem:
-			{
-				InferenceElem *ie = (InferenceElem *) node;
-
-				APP_JUMB(ie->infercollid);
-				APP_JUMB(ie->inferopclass);
-				JumbleExpr(jstate, ie->expr);
-			}
-			break;
-		case T_TargetEntry:
-			{
-				TargetEntry *tle = (TargetEntry *) node;
-
-				APP_JUMB(tle->resno);
-				APP_JUMB(tle->ressortgroupref);
-				JumbleExpr(jstate, (Node *) tle->expr);
-			}
-			break;
-		case T_RangeTblRef:
-			{
-				RangeTblRef *rtr = (RangeTblRef *) node;
-
-				APP_JUMB(rtr->rtindex);
-			}
-			break;
-		case T_JoinExpr:
-			{
-				JoinExpr   *join = (JoinExpr *) node;
-
-				APP_JUMB(join->jointype);
-				APP_JUMB(join->isNatural);
-				APP_JUMB(join->rtindex);
-				JumbleExpr(jstate, join->larg);
-				JumbleExpr(jstate, join->rarg);
-				JumbleExpr(jstate, join->quals);
-			}
-			break;
-		case T_FromExpr:
-			{
-				FromExpr   *from = (FromExpr *) node;
-
-				JumbleExpr(jstate, (Node *) from->fromlist);
-				JumbleExpr(jstate, from->quals);
-			}
-			break;
-		case T_OnConflictExpr:
-			{
-				OnConflictExpr *conf = (OnConflictExpr *) node;
-
-				APP_JUMB(conf->action);
-				JumbleExpr(jstate, (Node *) conf->arbiterElems);
-				JumbleExpr(jstate, conf->arbiterWhere);
-				JumbleExpr(jstate, (Node *) conf->onConflictSet);
-				JumbleExpr(jstate, conf->onConflictWhere);
-				APP_JUMB(conf->constraint);
-				APP_JUMB(conf->exclRelIndex);
-				JumbleExpr(jstate, (Node *) conf->exclRelTlist);
-			}
-			break;
-		case T_MergeAction:
-			{
-				MergeAction *mergeaction = (MergeAction *) node;
-
-				APP_JUMB(mergeaction->matched);
-				APP_JUMB(mergeaction->commandType);
-				JumbleExpr(jstate, mergeaction->qual);
-				JumbleExpr(jstate, (Node *) mergeaction->targetList);
-			}
-			break;
-		case T_List:
-			foreach(temp, (List *) node)
-			{
-				JumbleExpr(jstate, (Node *) lfirst(temp));
-			}
-			break;
-		case T_IntList:
-			foreach(temp, (List *) node)
-			{
-				APP_JUMB(lfirst_int(temp));
-			}
-			break;
-		case T_SortGroupClause:
-			{
-				SortGroupClause *sgc = (SortGroupClause *) node;
-
-				APP_JUMB(sgc->tleSortGroupRef);
-				APP_JUMB(sgc->eqop);
-				APP_JUMB(sgc->sortop);
-				APP_JUMB(sgc->nulls_first);
-			}
-			break;
-		case T_GroupingSet:
-			{
-				GroupingSet *gsnode = (GroupingSet *) node;
-
-				JumbleExpr(jstate, (Node *) gsnode->content);
-			}
-			break;
-		case T_WindowClause:
-			{
-				WindowClause *wc = (WindowClause *) node;
-
-				APP_JUMB(wc->winref);
-				APP_JUMB(wc->frameOptions);
-				JumbleExpr(jstate, (Node *) wc->partitionClause);
-				JumbleExpr(jstate, (Node *) wc->orderClause);
-				JumbleExpr(jstate, wc->startOffset);
-				JumbleExpr(jstate, wc->endOffset);
-			}
-			break;
-		case T_CommonTableExpr:
-			{
-				CommonTableExpr *cte = (CommonTableExpr *) node;
-
-				/* we store the string name because RTE_CTE RTEs need it */
-				APP_JUMB_STRING(cte->ctename);
-				APP_JUMB(cte->ctematerialized);
-				JumbleQueryInternal(jstate, castNode(Query, cte->ctequery));
-			}
-			break;
-		case T_SetOperationStmt:
-			{
-				SetOperationStmt *setop = (SetOperationStmt *) node;
-
-				APP_JUMB(setop->op);
-				APP_JUMB(setop->all);
-				JumbleExpr(jstate, setop->larg);
-				JumbleExpr(jstate, setop->rarg);
-			}
-			break;
-		case T_RangeTblFunction:
-			{
-				RangeTblFunction *rtfunc = (RangeTblFunction *) node;
-
-				JumbleExpr(jstate, rtfunc->funcexpr);
-			}
-			break;
-		case T_TableFunc:
-			{
-				TableFunc  *tablefunc = (TableFunc *) node;
-
-				JumbleExpr(jstate, tablefunc->docexpr);
-				JumbleExpr(jstate, tablefunc->rowexpr);
-				JumbleExpr(jstate, (Node *) tablefunc->colexprs);
-			}
-			break;
-		case T_TableSampleClause:
-			{
-				TableSampleClause *tsc = (TableSampleClause *) node;
-
-				APP_JUMB(tsc->tsmhandler);
-				JumbleExpr(jstate, (Node *) tsc->args);
-				JumbleExpr(jstate, (Node *) tsc->repeatable);
-			}
-			break;
-		default:
-			/* Only a warning, since we can stumble along anyway */
-			elog(WARNING, "unrecognized node type: %d",
-				 (int) nodeTag(node));
-			break;
-	}
-}
-
 /*
  * Record location of constant within query string of query tree
  * that is currently being walked.
@@ -859,3 +204,154 @@ RecordConstLocation(JumbleState *jstate, int location)
 		jstate->clocations_count++;
 	}
 }
+
+#define JUMBLE_NODE(item) \
+	_jumbleNode(jstate, (Node *) expr->item)
+#define JUMBLE_LOCATION(location) \
+	RecordConstLocation(jstate, expr->location)
+#define JUMBLE_FIELD(item) \
+	AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item))
+#define JUMBLE_FIELD_SINGLE(item) \
+	AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
+#define JUMBLE_STRING(str) \
+do { \
+	if (expr->str) \
+		AppendJumble(jstate, (const unsigned char *) (expr->str), strlen(expr->str) + 1); \
+} while(0)
+
+#include "queryjumblefuncs.funcs.c"
+
+static void
+_jumbleNode(JumbleState *jstate, Node *node)
+{
+	Node	   *expr = node;
+
+	if (expr == NULL)
+		return;
+
+	/* Guard against stack overflow due to overly complex expressions */
+	check_stack_depth();
+
+	/*
+	 * We always emit the node's NodeTag, then any additional fields that are
+	 * considered significant, and then we recurse to any child nodes.
+	 */
+	JUMBLE_FIELD(type);
+
+	switch (nodeTag(expr))
+	{
+#include "queryjumblefuncs.switch.c"
+
+		case T_List:
+		case T_IntList:
+		case T_OidList:
+		case T_XidList:
+			_jumbleList(jstate, expr);
+			break;
+
+		default:
+			/* Only a warning, since we can stumble along anyway */
+			elog(WARNING, "unrecognized node type: %d",
+				 (int) nodeTag(expr));
+			break;
+	}
+
+	/* Special cases to handle outside the automated code */
+	switch (nodeTag(expr))
+	{
+		case T_Param:
+			{
+				Param	   *p = (Param *) node;
+
+				/*
+				 * Update the highest Param id seen, in order to start
+				 * normalization correctly.
+				 */
+				if (p->paramkind == PARAM_EXTERN &&
+					p->paramid > jstate->highest_extern_param_id)
+					jstate->highest_extern_param_id = p->paramid;
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+_jumbleList(JumbleState *jstate, Node *node)
+{
+	List	   *expr = (List *) node;
+	ListCell   *l;
+
+	switch (expr->type)
+	{
+		case T_List:
+			foreach(l, expr)
+				_jumbleNode(jstate, lfirst(l));
+			break;
+		case T_IntList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_int(l));
+			break;
+		case T_OidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_oid(l));
+			break;
+		case T_XidList:
+			foreach(l, expr)
+				JUMBLE_FIELD_SINGLE(lfirst_xid(l));
+			break;
+		default:
+			elog(ERROR, "unrecognized list node type: %d",
+				 (int) expr->type);
+			return;
+	}
+}
+
+static void
+_jumbleRangeTblEntry(JumbleState *jstate, Node *node)
+{
+	RangeTblEntry *expr = (RangeTblEntry *) node;
+
+	JUMBLE_FIELD(rtekind);
+	switch (expr->rtekind)
+	{
+		case RTE_RELATION:
+			JUMBLE_FIELD(relid);
+			JUMBLE_NODE(tablesample);
+			JUMBLE_FIELD(inh);
+			break;
+		case RTE_SUBQUERY:
+			JUMBLE_NODE(subquery);
+			break;
+		case RTE_JOIN:
+			JUMBLE_FIELD(jointype);
+			break;
+		case RTE_FUNCTION:
+			JUMBLE_NODE(functions);
+			break;
+		case RTE_TABLEFUNC:
+			JUMBLE_NODE(tablefunc);
+			break;
+		case RTE_VALUES:
+			JUMBLE_NODE(values_lists);
+			break;
+		case RTE_CTE:
+
+			/*
+			 * Depending on the CTE name here isn't ideal, but it's the only
+			 * info we have to identify the referenced WITH item.
+			 */
+			JUMBLE_STRING(ctename);
+			JUMBLE_FIELD(ctelevelsup);
+			break;
+		case RTE_NAMEDTUPLESTORE:
+			JUMBLE_STRING(enrname);
+			break;
+		case RTE_RESULT:
+			break;
+		default:
+			elog(ERROR, "unrecognized RTE kind: %d", (int) expr->rtekind);
+			break;
+	}
+}
-- 
2.39.0

v7-0004-Add-GUC-utility_query_id.patchtext/x-diff; charset=us-asciiDownload
From a55d0adb5bbd6bd09e0b27d3b1a2b0ed956eb7b0 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 25 Jan 2023 08:47:40 +0900
Subject: [PATCH v7 4/4] Add GUC utility_query_id

This GUC has two modes to control the computation method of query IDs
for utilities:
- 'string', the default, to hash the string query.
- 'jumble', to use the parsed tree.
---
 src/include/nodes/queryjumble.h               |  7 ++
 src/backend/nodes/queryjumblefuncs.c          | 81 ++++++++++++++-----
 src/backend/utils/misc/guc_tables.c           | 16 ++++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 doc/src/sgml/config.sgml                      | 31 +++++++
 .../expected/pg_stat_statements.out           | 31 +++++++
 .../sql/pg_stat_statements.sql                | 17 ++++
 7 files changed, 164 insertions(+), 20 deletions(-)

diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index 204b8f74fd..261aea6bcf 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -59,8 +59,15 @@ enum ComputeQueryIdType
 	COMPUTE_QUERY_ID_REGRESS
 };
 
+enum UtilityQueryIdType
+{
+	UTILITY_QUERY_ID_STRING,
+	UTILITY_QUERY_ID_JUMBLE
+};
+
 /* GUC parameters */
 extern PGDLLIMPORT int compute_query_id;
+extern PGDLLIMPORT int utility_query_id;
 
 
 extern const char *CleanQuerytext(const char *query, int *location, int *len);
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 16fdf7164a..d55adf5020 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -41,12 +41,15 @@
 
 /* GUC parameters */
 int			compute_query_id = COMPUTE_QUERY_ID_AUTO;
+int			utility_query_id = UTILITY_QUERY_ID_STRING;
 
 /* True when compute_query_id is ON, or AUTO and a module requests them */
 bool		query_id_enabled = false;
 
 static void AppendJumble(JumbleState *jstate,
 						 const unsigned char *item, Size size);
+static uint64 compute_utility_query_id(const char *query_text,
+									   int query_location, int query_len);
 static void RecordConstLocation(JumbleState *jstate, int location);
 static void _jumbleNode(JumbleState *jstate, Node *node);
 static void _jumbleList(JumbleState *jstate, Node *node);
@@ -102,29 +105,39 @@ JumbleQuery(Query *query, const char *querytext)
 
 	Assert(IsQueryIdEnabled());
 
-	jstate = (JumbleState *) palloc(sizeof(JumbleState));
+	if (query->utilityStmt &&
+		utility_query_id == UTILITY_QUERY_ID_STRING)
+	{
+		query->queryId = compute_utility_query_id(querytext,
+												  query->stmt_location,
+												  query->stmt_len);
+	}
+	else
+	{
+		jstate = (JumbleState *) palloc(sizeof(JumbleState));
 
-	/* Set up workspace for query jumbling */
-	jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-	jstate->jumble_len = 0;
-	jstate->clocations_buf_size = 32;
-	jstate->clocations = (LocationLen *)
-		palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-	jstate->clocations_count = 0;
-	jstate->highest_extern_param_id = 0;
+		/* Set up workspace for query jumbling */
+		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+		jstate->jumble_len = 0;
+		jstate->clocations_buf_size = 32;
+		jstate->clocations = (LocationLen *)
+			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+		jstate->clocations_count = 0;
+		jstate->highest_extern_param_id = 0;
 
-	/* Compute query ID and mark the Query node with it */
-	_jumbleNode(jstate, (Node *) query);
-	query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
-													  jstate->jumble_len,
-													  0));
+		/* Compute query ID and mark the Query node with it */
+		_jumbleNode(jstate, (Node *) query);
+		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+														  jstate->jumble_len,
+														  0));
 
-	/*
-	 * If we are unlucky enough to get a hash of zero, use 1 instead, to
-	 * prevent confusion with the utility-statement case.
-	 */
-	if (query->queryId == UINT64CONST(0))
-		query->queryId = UINT64CONST(1);
+		/*
+		 * If we are unlucky enough to get a hash of zero, use 1 instead, to
+		 * prevent confusion with the utility-statement case.
+		 */
+		if (query->queryId == UINT64CONST(0))
+			query->queryId = UINT64CONST(1);
+	}
 
 	return jstate;
 }
@@ -142,6 +155,34 @@ EnableQueryId(void)
 		query_id_enabled = true;
 }
 
+/*
+ * Compute a query identifier for the given utility query string.
+ */
+static uint64
+compute_utility_query_id(const char *query_text, int query_location, int query_len)
+{
+	uint64		queryId;
+	const char *sql;
+
+	/*
+	 * Confine our attention to the relevant part of the string, if the query
+	 * is a portion of a multi-statement source string.
+	 */
+	sql = CleanQuerytext(query_text, &query_location, &query_len);
+
+	queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql,
+											   query_len, 0));
+
+	/*
+	 * If we are unlucky enough to get a hash of zero(invalid), use queryID as
+	 * 2 instead, queryID 1 is already in use for normal statements.
+	 */
+	if (queryId == UINT64CONST(0))
+		queryId = UINT64CONST(2);
+
+	return queryId;
+}
+
 /*
  * AppendJumble: Append a value that is substantive in a given query to
  * the current jumble.
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4ac808ed22..97619c4e1d 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -294,6 +294,12 @@ static const struct config_enum_entry compute_query_id_options[] = {
 	{NULL, 0, false}
 };
 
+static const struct config_enum_entry utility_query_id_options[] = {
+	{"string", UTILITY_QUERY_ID_STRING, false},
+	{"jumble", UTILITY_QUERY_ID_JUMBLE, false},
+	{NULL, 0, false}
+};
+
 /*
  * Although only "on", "off", and "partition" are documented, we
  * accept all the likely variants of "on" and "off".
@@ -4574,6 +4580,16 @@ struct config_enum ConfigureNamesEnum[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"utility_query_id", PGC_SUSET, STATS_MONITORING,
+			gettext_noop("Controls method computing query ID for utilities."),
+			NULL
+		},
+		&utility_query_id,
+		UTILITY_QUERY_ID_STRING, utility_query_id_options,
+		NULL, NULL, NULL
+	},
+
 	{
 		{"constraint_exclusion", PGC_USERSET, QUERY_TUNING_OTHER,
 			gettext_noop("Enables the planner to use constraints to optimize queries."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..bbf95af59d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -623,6 +623,7 @@
 # - Monitoring -
 
 #compute_query_id = auto
+#utility_query_id = string		# string, jumble
 #log_statement_stats = off
 #log_parser_stats = off
 #log_planner_stats = off
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f985afc009..4ccd148471 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8241,6 +8241,37 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-utility-query-id" xreflabel="utility_query_id">
+      <term><varname>utility_query_id</varname> (<type>enum</type>)
+      <indexterm>
+       <primary><varname>utility_query_id</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Controls the method used to compute the query identifier of a utility
+        query. Valid values are <literal>string</literal> to use a hash of the
+        query string and <literal>jumble</literal> to compute the query
+        identifier depending on the parsed tree of the utility query.
+        The default is <literal>string</literal>.
+       </para>
+       <para>
+        <literal>jumble</literal> is more costly than <literal>string</literal>
+        as the computation of the query identifier walks through the
+        post-parse-analysis representation of the queries for utility queries.
+        However, <literal>jumble</literal> is able to apply normalization
+        to the queries computed, meaning that queries written differently
+        but having the same query representation may be able to use the same
+        identifier.
+        For example, <literal>BEGIN;</literal> and <literal>begin;</literal>
+        will have the same query identifier under <literal>jumble</literal> as
+        both queries have the same query representation. The query identifier
+        would be different under <literal>string</literal>, because the query
+        strings are different.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-log-statement-stats">
       <term><varname>log_statement_stats</varname> (<type>boolean</type>)
       <indexterm>
diff --git a/contrib/pg_stat_statements/expected/pg_stat_statements.out b/contrib/pg_stat_statements/expected/pg_stat_statements.out
index 9ac5c87c3a..8bdf8beec3 100644
--- a/contrib/pg_stat_statements/expected/pg_stat_statements.out
+++ b/contrib/pg_stat_statements/expected/pg_stat_statements.out
@@ -554,6 +554,7 @@ DROP TABLE pgss_a, pgss_b CASCADE;
 -- utility commands
 --
 SET pg_stat_statements.track_utility = TRUE;
+SET utility_query_id = 'string';
 SELECT pg_stat_statements_reset();
  pg_stat_statements_reset 
 --------------------------
@@ -592,6 +593,36 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
  SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     0 |    0
 (9 rows)
 
+SELECT pg_stat_statements_reset();
+ pg_stat_statements_reset 
+--------------------------
+ 
+(1 row)
+
+SET utility_query_id = 'jumble';
+-- These queries have a different string, but the same parsing
+-- representation.
+Begin;
+Create Table test_utility_query (a int);
+Drop Table test_utility_query;
+Commit;
+BEGIN;
+CREATE TABLE test_utility_query (a int);
+DROP TABLE test_utility_query;
+COMMIT;
+SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
+                                    query                                     | calls | rows 
+------------------------------------------------------------------------------+-------+------
+ Begin                                                                        |     2 |    0
+ Commit                                                                       |     2 |    0
+ Create Table test_utility_query (a int)                                      |     2 |    0
+ Drop Table test_utility_query                                                |     2 |    0
+ SELECT pg_stat_statements_reset()                                            |     1 |    1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     0 |    0
+ SET utility_query_id = 'jumble'                                              |     1 |    0
+(7 rows)
+
+RESET utility_query_id;
 --
 -- Track the total number of rows retrieved or affected by the utility
 -- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW,
diff --git a/contrib/pg_stat_statements/sql/pg_stat_statements.sql b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
index 8f5c866225..81d663f81c 100644
--- a/contrib/pg_stat_statements/sql/pg_stat_statements.sql
+++ b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
@@ -258,6 +258,7 @@ DROP TABLE pgss_a, pgss_b CASCADE;
 -- utility commands
 --
 SET pg_stat_statements.track_utility = TRUE;
+SET utility_query_id = 'string';
 SELECT pg_stat_statements_reset();
 
 SELECT 1;
@@ -272,6 +273,22 @@ DROP FUNCTION PLUS_TWO(INTEGER);
 
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 
+SELECT pg_stat_statements_reset();
+SET utility_query_id = 'jumble';
+-- These queries have a different string, but the same parsing
+-- representation.
+Begin;
+Create Table test_utility_query (a int);
+Drop Table test_utility_query;
+Commit;
+BEGIN;
+CREATE TABLE test_utility_query (a int);
+DROP TABLE test_utility_query;
+COMMIT;
+
+SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
+RESET utility_query_id;
+
 --
 -- Track the total number of rows retrieved or affected by the utility
 -- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW,
-- 
2.39.0

#19Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Michael Paquier (#17)
Re: Generating code for query jumbling through gen_node_support.pl

On 24.01.23 07:57, Michael Paquier wrote:

For the 0004 patch, it should be documented why one would want one behavior
or the other. That's totally unclear right now.

I am not 100% sure whether we should have this part at the end, but as
an exit path in case somebody complains about the extra load with the
automated jumbling compared to the hash of the query strings, that can
be used as a backup. Anyway, attached is what would be a
clarification.

Ok, the documentation make sense now. I wonder what the performance
impact is. Probably, nobody cares about microoptimizing CREATE TABLE
statements. But BEGIN/COMMIT could matter. However, whatever you do in
between the BEGIN and COMMIT will already be jumbled, so you're already
paying the overhead. Hopefully, jumbling such simple commands will have
no noticeable overhead.

In other words, we should test this and hopefully get rid of the
'string' method.

#20Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Michael Paquier (#18)
Re: Generating code for query jumbling through gen_node_support.pl

On 25.01.23 01:08, Michael Paquier wrote:

On Tue, Jan 24, 2023 at 03:57:56PM +0900, Michael Paquier wrote:

Makes sense. That would be my intention if 0004 is the most
acceptable and splitting things makes things a bit easier to review.

There was a silly mistake in 0004 where the jumbling code relied on
compute_query_id rather than utility_query_id, so fixed and rebased as
of v7 attached.

Overall, this looks good to me.

There are a couple of repetitive comments, like "typmod and collation
information are irrelevant for the query jumbling". This applies to all
nodes, so we don't need to repeat it for a number of nodes (and then not
mention it for other nodes). Maybe there should be a central place
somewhere that describes "these kinds of fields should normally be ignored".

#21Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#19)
1 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Thu, Jan 26, 2023 at 09:37:13AM +0100, Peter Eisentraut wrote:

Ok, the documentation make sense now. I wonder what the performance impact
is. Probably, nobody cares about microoptimizing CREATE TABLE statements.
But BEGIN/COMMIT could matter. However, whatever you do in between the
BEGIN and COMMIT will already be jumbled, so you're already paying the
overhead. Hopefully, jumbling such simple commands will have no noticeable
overhead.

In other words, we should test this and hopefully get rid of the 'string'
method.

Yep. I have mentioned a few numbers upthread, and this deserves
discussion.

FYI, I have done more micro-benchmarking to compare both methods for
utility queries by hijacking JumbleQuery() to run the computation in a
tight loop run N times (could not come up with a better idea to avoid
the repeated palloc/pfree overhead), as the path to stress is
_jumbleNode(). See the attached, that should be able to apply on top
of the latest patch set (named as .txt to not feed it to the CF bot,
and need to recompile to switch the iteration).

Using that, I can compile the following results for various cases (-O2
and compute_query_id=on):
query | mode | iterations | avg_runtime_ns | avg_jumble_ns
-------------------------+--------+------------+----------------+---------------
begin | string | 50000000 | 4.53116 | 4.54
begin | jumble | 50000000 | 30.94578 | 30.94
commit | string | 50000000 | 4.76004 | 4.74
commit | jumble | 50000000 | 31.4791 | 31.48
create table 1 column | string | 50000000 | 7.22836 | 7.08
create table 1 column | jumble | 50000000 | 152.10852 | 151.96
create table 5 columns | string | 50000000 | 12.43412 | 12.28
create table 5 columns | jumble | 50000000 | 352.88976 | 349.1
create table 20 columns | string | 5000000 | 49.591 | 48.2
create table 20 columns | jumble | 5000000 | 2272.4066 | 2271
drop table 1 column | string | 50000000 | 6.70538 | 6.56
drop table 1 column | jumble | 50000000 | 50.38 | 50.24
drop table 5 columns | string | 50000000 | 6.88256 | 6.74
drop table 5 columns | jumble | 50000000 | 50.02898 | 49.9
SET work_mem | string | 50000000 | 7.28752 | 7.28
SET work_mem | jumble | 50000000 | 91.66588 | 91.64
(16 rows)

avg_runtime_ns is (query runtime / iterations) and avg_jumble_ns is
the same with the difference between the start/end logs in the txt
patch attached. The overhead to run the query does not matter much if
you compare both. The time it takes to run a jumble is correlated to
the number of nodes to go through for each query, and there is a
larger gap for more nodes to go through. Well, a simple "begin" or
"commit" query has its computation time increase from 4ns to 30ns in
average which would be unnoticeable. The gap is larger for larger
nodes, like SET, still we jump from 7ns to 90ns in this case. DDLs
take the most hit with this method, where a 20-column CREATE TABLE
jumps from 50ns to 2us (note that the iteration is 10 times lower
here).

At the end, that would be unnoticeable for the average user, I guess,
but here are the numbers I get on my laptop :)
--
Michael

Attachments:

v8-Add-benchmark-tweaks-to-stress-jumbling-code.txttext/plain; charset=us-asciiDownload
From ee47c4e1137adc857184d76ab62232e9c3c95451 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 27 Jan 2023 10:58:34 +0900
Subject: [PATCH 1/1] Add benchmark tweaks to stress jumbling code

---
 src/backend/nodes/queryjumblefuncs.c | 36 +++++++++++++++++++++-------
 1 file changed, 27 insertions(+), 9 deletions(-)

diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index d55adf5020..9c134cc5ae 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -101,32 +101,48 @@ CleanQuerytext(const char *query, int *location, int *len)
 JumbleState *
 JumbleQuery(Query *query, const char *querytext)
 {
+#define MAX_QUERY_COMPUTE 5000000
+
 	JumbleState *jstate = NULL;
 
 	Assert(IsQueryIdEnabled());
 
+	elog(WARNING, "start JumbleQuery");
+
 	if (query->utilityStmt &&
 		utility_query_id == UTILITY_QUERY_ID_STRING)
 	{
-		query->queryId = compute_utility_query_id(querytext,
-												  query->stmt_location,
-												  query->stmt_len);
+		for (int i = 0; i < MAX_QUERY_COMPUTE ; i++)
+		{
+			query->queryId = compute_utility_query_id(querytext,
+													  query->stmt_location,
+													  query->stmt_len);
+		}
 	}
 	else
 	{
 		jstate = (JumbleState *) palloc(sizeof(JumbleState));
 
-		/* Set up workspace for query jumbling */
 		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-		jstate->jumble_len = 0;
 		jstate->clocations_buf_size = 32;
 		jstate->clocations = (LocationLen *)
 			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-		jstate->clocations_count = 0;
-		jstate->highest_extern_param_id = 0;
 
-		/* Compute query ID and mark the Query node with it */
-		_jumbleNode(jstate, (Node *) query);
+		for (int i = 0; i < MAX_QUERY_COMPUTE ; i++)
+		{
+			memset(jstate->jumble, 0, sizeof(JUMBLE_SIZE));
+			/* Set up workspace for query jumbling */
+			jstate->jumble_len = 0;
+			jstate->clocations_buf_size = 32;
+			memset(jstate->clocations, 0,
+				   jstate->clocations_buf_size * sizeof(LocationLen));
+			jstate->clocations_count = 0;
+			jstate->highest_extern_param_id = 0;
+
+			/* Compute query ID and mark the Query node with it */
+			_jumbleNode(jstate, (Node *) query);
+		}
+
 		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
 														  jstate->jumble_len,
 														  0));
@@ -139,6 +155,8 @@ JumbleQuery(Query *query, const char *querytext)
 			query->queryId = UINT64CONST(1);
 	}
 
+	elog(WARNING, "end JumbleQuery");
+
 	return jstate;
 }
 
-- 
2.39.0

#22Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#20)
Re: Generating code for query jumbling through gen_node_support.pl

On Thu, Jan 26, 2023 at 09:39:05AM +0100, Peter Eisentraut wrote:

There are a couple of repetitive comments, like "typmod and collation
information are irrelevant for the query jumbling". This applies to all
nodes, so we don't need to repeat it for a number of nodes (and then not
mention it for other nodes). Maybe there should be a central place
somewhere that describes "these kinds of fields should normally be ignored".

The place that would make the most sense to me to centralize this
knowlegde is nodes.h itself, because that's where the node attributes
are defined?
--
Michael

#23Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#17)
Re: Generating code for query jumbling through gen_node_support.pl

On Tue, Jan 24, 2023 at 03:57:56PM +0900, Michael Paquier wrote:

Still, my plan here is to enforce the loading of
pg_stat_statements with compute_query_id = regress and
utility_query_id = jumble (if needed) in a new buildfarm machine,

Actually, about this specific point, I have been able to set up a
buildfarm machine that uses shared_preload_libraries =
pg_stat_statements and compute_query_id = regress in the base
configuration, which is uncovered yet. This works as long as one sets
up EXTRA_INSTALL => "contrib/pg_stat_statements" in build_env.
--
Michael

#24Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#21)
1 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Fri, Jan 27, 2023 at 11:59:47AM +0900, Michael Paquier wrote:

Using that, I can compile the following results for various cases (-O2
and compute_query_id=on):
query | mode | iterations | avg_runtime_ns | avg_jumble_ns
-------------------------+--------+------------+----------------+---------------
begin | string | 50000000 | 4.53116 | 4.54
begin | jumble | 50000000 | 30.94578 | 30.94
commit | string | 50000000 | 4.76004 | 4.74
commit | jumble | 50000000 | 31.4791 | 31.48
create table 1 column | string | 50000000 | 7.22836 | 7.08
create table 1 column | jumble | 50000000 | 152.10852 | 151.96
create table 5 columns | string | 50000000 | 12.43412 | 12.28
create table 5 columns | jumble | 50000000 | 352.88976 | 349.1
create table 20 columns | string | 5000000 | 49.591 | 48.2
create table 20 columns | jumble | 5000000 | 2272.4066 | 2271
drop table 1 column | string | 50000000 | 6.70538 | 6.56
drop table 1 column | jumble | 50000000 | 50.38 | 50.24
drop table 5 columns | string | 50000000 | 6.88256 | 6.74
drop table 5 columns | jumble | 50000000 | 50.02898 | 49.9
SET work_mem | string | 50000000 | 7.28752 | 7.28
SET work_mem | jumble | 50000000 | 91.66588 | 91.64
(16 rows)

Just to close the loop here, I have done more measurements to compare
the jumble done for some DMLs and some SELECTs between HEAD and the
patch (forgot to post some last Friday). Both methods show comparable
results:
query | mode | iterations | avg_runtime_ns | avg_jumble_ns
----------------------+--------+------------+----------------+---------------
insert table 10 cols | master | 50000000 | 377.17878 | 377.04
insert table 10 cols | jumble | 50000000 | 409.47924 | 409.34
insert table 20 cols | master | 50000000 | 692.94924 | 692.8
insert table 20 cols | jumble | 50000000 | 710.0901 | 709.96
insert table 5 cols | master | 50000000 | 232.44308 | 232.3
insert table 5 cols | jumble | 50000000 | 253.49854 | 253.36
select 10 cols | master | 50000000 | 449.13608 | 383.36
select 10 cols | jumble | 50000000 | 491.61912 | 323.86
select 5 cols | master | 50000000 | 277.477 | 277.46
select 5 cols | jumble | 50000000 | 323.88152 | 323.86
(10 rows)

The averages are in ns, so the difference does not bother me much.
There may be some noise mixed in that ;)

(Attached is the tweak I have applied on HEAD to get some numbers.)
--
Michael

Attachments:

jumble-benchmark.txttext/plain; charset=us-asciiDownload
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 16084842a3..c66090bde3 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -101,10 +101,14 @@ CleanQuerytext(const char *query, int *location, int *len)
 JumbleState *
 JumbleQuery(Query *query, const char *querytext)
 {
+#define MAX_QUERY_COMPUTE 50000000
+
 	JumbleState *jstate = NULL;
 
 	Assert(IsQueryIdEnabled());
 
+	elog(WARNING, "start JumbleQuery");
+
 	if (query->utilityStmt)
 	{
 		query->queryId = compute_utility_query_id(querytext,
@@ -114,18 +118,26 @@ JumbleQuery(Query *query, const char *querytext)
 	else
 	{
 		jstate = (JumbleState *) palloc(sizeof(JumbleState));
-
-		/* Set up workspace for query jumbling */
 		jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
-		jstate->jumble_len = 0;
 		jstate->clocations_buf_size = 32;
 		jstate->clocations = (LocationLen *)
 			palloc(jstate->clocations_buf_size * sizeof(LocationLen));
-		jstate->clocations_count = 0;
-		jstate->highest_extern_param_id = 0;
 
-		/* Compute query ID and mark the Query node with it */
-		JumbleQueryInternal(jstate, query);
+		for (int i = 0; i < MAX_QUERY_COMPUTE ; i++)
+		{
+			/* Set up workspace for query jumbling */
+			memset(jstate->jumble, 0, sizeof(JUMBLE_SIZE));
+			jstate->jumble_len = 0;
+			jstate->clocations_buf_size = 32;
+			memset(jstate->clocations, 0,
+				   jstate->clocations_buf_size * sizeof(LocationLen));
+
+			jstate->clocations_count = 0;
+			jstate->highest_extern_param_id = 0;
+
+			/* Compute query ID and mark the Query node with it */
+			JumbleQueryInternal(jstate, query);
+		}
 		query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
 														  jstate->jumble_len,
 														  0));
@@ -138,6 +150,8 @@ JumbleQuery(Query *query, const char *querytext)
 			query->queryId = UINT64CONST(1);
 	}
 
+	elog(WARNING, "end JumbleQuery");
+
 	return jstate;
 }
 
#25Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Michael Paquier (#22)
Re: Generating code for query jumbling through gen_node_support.pl

On 27.01.23 04:07, Michael Paquier wrote:

On Thu, Jan 26, 2023 at 09:39:05AM +0100, Peter Eisentraut wrote:

There are a couple of repetitive comments, like "typmod and collation
information are irrelevant for the query jumbling". This applies to all
nodes, so we don't need to repeat it for a number of nodes (and then not
mention it for other nodes). Maybe there should be a central place
somewhere that describes "these kinds of fields should normally be ignored".

The place that would make the most sense to me to centralize this
knowlegde is nodes.h itself, because that's where the node attributes
are defined?

Either that or src/backend/nodes/README.

#26Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Michael Paquier (#21)
Re: Generating code for query jumbling through gen_node_support.pl

On 27.01.23 03:59, Michael Paquier wrote:

At the end, that would be unnoticeable for the average user, I guess,
but here are the numbers I get on my laptop :)

Personally, I think we do not want the two jumble methods in parallel.

Maybe there are other opinions.

I'm going to set this thread as "Ready for Committer". Either wait a
bit for more feedback on this topic, or just go ahead with either
solution. We can leave it as a semi-open item for reconsideration later.

#27Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#25)
Re: Generating code for query jumbling through gen_node_support.pl

On Mon, Jan 30, 2023 at 11:46:06AM +0100, Peter Eisentraut wrote:

Either that or src/backend/nodes/README.

The README holds nothing about the node attributes currently, so
nodes.h feels most adapted here at the end..

Thanks,
--
Michael

#28Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#26)
Re: Generating code for query jumbling through gen_node_support.pl

On Mon, Jan 30, 2023 at 11:48:45AM +0100, Peter Eisentraut wrote:

I'm going to set this thread as "Ready for Committer". Either wait a bit
for more feedback on this topic, or just go ahead with either solution. We
can leave it as a semi-open item for reconsideration later.

All the measurements I have done for the last couple of days point me
in the direction that there is no need for an extra node based on the
averaged computation times (did a few more today with some long CREATE
FUNCTION, VIEW or event trigger queries, for example). Agreed to add
the extra option as something to consider at some point during beta,
as long as it is fresh. I am not convinced that it will be necessary
but let's see how it goes.

While reviewing all the nodes, I have noticed two mistakes for a few
things marked as query_jumble_ignore but they should not:
- orderClause in WindowClause
- aliascolnames in CommonTableExpr
The rest was fine.

varnullingrels has been added very recently, and there was in
queryjumblefuncs.c a comment explaining why it should be ignored.
This has been moved to nodes.h, like the others.

With all that in mind, I have spent my day polishing that and doing a
close lookup, and the patch has been applied. Thanks a lot!
--
Michael

#29Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#28)
1 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Tue, Jan 31, 2023 at 03:40:56PM +0900, Michael Paquier wrote:

With all that in mind, I have spent my day polishing that and doing a
close lookup, and the patch has been applied. Thanks a lot!

While working on a different patch, I have noticed a small issue in
the way the jumbling happens for A_Const, where ValUnion was not
getting jumbled correctly. This caused a few statements that rely on
this node to compile the same query IDs when using different values.
The full contents of pg_stat_statements for a regression database
point to:
- SET.
- COPY with queries.
- CREATE TABLE with partition bounds and default expressions.

This was causing some confusion in pg_stat_statements where some
utility queries would be incorrectly reported, and at this point the
intention is to keep this area compatible with the string-based method
when it comes to the values. Like read, write and copy nodes, we need
to compile the query ID based on the type of the value, which cannot
be automated. Attached is a patch to fix this issue with some
regression tests, that I'd like to get fixed before moving on with
more business in pg_stat_statements (aka properly show Const nodes for
utilities with normalized queries).

Comments or objections are welcome, of course.

(FWIW, I'd like to think that there is an argument to normalize the
A_Const nodes for a portion of the DDL queries, by ignoring their
values in the query jumbling and mark a location, which would be
really useful for some workloads, but that's a separate discussion I
am keeping for later.)
--
Michael

Attachments:

jumble-aconst.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3d67787e7a..855da99ec0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -355,7 +355,7 @@ union ValUnion
 
 typedef struct A_Const
 {
-	pg_node_attr(custom_copy_equal, custom_read_write)
+	pg_node_attr(custom_copy_equal, custom_read_write, custom_query_jumble)
 
 	NodeTag		type;
 	union ValUnion val;
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 223d1bc826..72ce15e43d 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -49,6 +49,7 @@ static void AppendJumble(JumbleState *jstate,
 						 const unsigned char *item, Size size);
 static void RecordConstLocation(JumbleState *jstate, int location);
 static void _jumbleNode(JumbleState *jstate, Node *node);
+static void _jumbleA_Const(JumbleState *jstate, Node *node);
 static void _jumbleList(JumbleState *jstate, Node *node);
 static void _jumbleRangeTblEntry(JumbleState *jstate, Node *node);
 
@@ -313,6 +314,40 @@ _jumbleList(JumbleState *jstate, Node *node)
 	}
 }
 
+static void
+_jumbleA_Const(JumbleState *jstate, Node *node)
+{
+	A_Const *expr = (A_Const *) node;
+
+	JUMBLE_FIELD(isnull);
+	if (!expr->isnull)
+	{
+		JUMBLE_FIELD(val.node.type);
+		switch (nodeTag(&expr->val))
+		{
+			case T_Integer:
+				JUMBLE_FIELD(val.ival.ival);
+				break;
+			case T_Float:
+				JUMBLE_STRING(val.fval.fval);
+				break;
+			case T_Boolean:
+				JUMBLE_FIELD(val.boolval.boolval);
+				break;
+			case T_String:
+				JUMBLE_STRING(val.sval.sval);
+				break;
+			case T_BitString:
+				JUMBLE_STRING(val.bsval.bsval);
+				break;
+			default:
+				elog(ERROR, "unrecognized node type: %d",
+					 (int) nodeTag(&expr->val));
+				break;
+		}
+	}
+}
+
 static void
 _jumbleRangeTblEntry(JumbleState *jstate, Node *node)
 {
diff --git a/contrib/pg_stat_statements/expected/pg_stat_statements.out b/contrib/pg_stat_statements/expected/pg_stat_statements.out
index fb9ccd920f..753062a9d7 100644
--- a/contrib/pg_stat_statements/expected/pg_stat_statements.out
+++ b/contrib/pg_stat_statements/expected/pg_stat_statements.out
@@ -579,6 +579,14 @@ NOTICE:  table "test" does not exist, skipping
 NOTICE:  table "test" does not exist, skipping
 NOTICE:  function plus_one(pg_catalog.int4) does not exist, skipping
 DROP FUNCTION PLUS_TWO(INTEGER);
+-- This SET query uses two different strings, still thet count as one entry.
+SET work_mem = '1MB';
+Set work_mem = '1MB';
+SET work_mem = '2MB';
+RESET work_mem;
+SET enable_seqscan = off;
+SET enable_seqscan = on;
+RESET enable_seqscan;
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
                                     query                                     | calls | rows 
 ------------------------------------------------------------------------------+-------+------
@@ -588,10 +596,16 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
  DROP FUNCTION PLUS_TWO(INTEGER)                                              |     1 |    0
  DROP TABLE IF EXISTS test                                                    |     3 |    0
  DROP TABLE test                                                              |     1 |    0
+ RESET enable_seqscan                                                         |     1 |    0
+ RESET work_mem                                                               |     1 |    0
  SELECT $1                                                                    |     1 |    1
  SELECT pg_stat_statements_reset()                                            |     1 |    1
  SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     0 |    0
-(9 rows)
+ SET enable_seqscan = off                                                     |     1 |    0
+ SET enable_seqscan = on                                                      |     1 |    0
+ SET work_mem = '1MB'                                                         |     2 |    0
+ SET work_mem = '2MB'                                                         |     1 |    0
+(15 rows)
 
 --
 -- Track the total number of rows retrieved or affected by the utility
diff --git a/contrib/pg_stat_statements/sql/pg_stat_statements.sql b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
index b82cddf16f..8ab8eb41ed 100644
--- a/contrib/pg_stat_statements/sql/pg_stat_statements.sql
+++ b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
@@ -270,6 +270,14 @@ DROP TABLE IF EXISTS test \;
 Drop Table If Exists test \;
 DROP FUNCTION IF EXISTS PLUS_ONE(INTEGER);
 DROP FUNCTION PLUS_TWO(INTEGER);
+-- This SET query uses two different strings, still thet count as one entry.
+SET work_mem = '1MB';
+Set work_mem = '1MB';
+SET work_mem = '2MB';
+RESET work_mem;
+SET enable_seqscan = off;
+SET enable_seqscan = on;
+RESET enable_seqscan;
 
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 
#30Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#29)
Re: Generating code for query jumbling through gen_node_support.pl

On Sun, Feb 05, 2023 at 07:40:57PM +0900, Michael Paquier wrote:

Comments or objections are welcome, of course.

Done this part.

(FWIW, I'd like to think that there is an argument to normalize the
A_Const nodes for a portion of the DDL queries, by ignoring their
values in the query jumbling and mark a location, which would be
really useful for some workloads, but that's a separate discussion I
am keeping for later.)

And I have a patch pretty much OK for this part of the discussion.
Will post soon..
--
Michael

#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#28)
Re: Generating code for query jumbling through gen_node_support.pl

Michael Paquier <michael@paquier.xyz> writes:

With all that in mind, I have spent my day polishing that and doing a
close lookup, and the patch has been applied. Thanks a lot!

I have just noticed that this patch is generating useless jumbling
code for node types such as Path nodes and other planner infrastructure
nodes. That no doubt contributes to the miserable code coverage rating
for queryjumblefuncs.*.c, which have enough dead lines to drag down the
overall rating for all of backend/nodes/. Shouldn't a little more
attention have been paid to excluding entire node classes if they can
never appear in Query?

regards, tom lane

#32Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#31)
2 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Tue, Feb 07, 2023 at 05:32:07PM -0500, Tom Lane wrote:

I have just noticed that this patch is generating useless jumbling
code for node types such as Path nodes and other planner infrastructure
nodes. That no doubt contributes to the miserable code coverage rating
for queryjumblefuncs.*.c, which have enough dead lines to drag down the
overall rating for all of backend/nodes/. Shouldn't a little more
attention have been paid to excluding entire node classes if they can
never appear in Query?

This one was intentional to let extensions play with jumbling of such
nodes, but perhaps you are right that it makes little sense at this
stage. If there is an ask for it later, though.. Using
shared_preload_libraries = pg_stat_statements and compute_query_id =
regress shows that numbers go up to 60% for funcs.c and 30% for
switch.c. Removing nodes like as of the attached brings these numbers
respectively up to 94.5% and 93.5% for a check. With a check-world, I
measure respectively 96.7% and 96.1% because there is more coverage
for extensions, ALTER SYSTEM and database commands, roughly.

This could also be a file-level policy by enforcing no_query_jumble in
gen_node_support.pl by looking at the header name, still I favor
no_query_jumble to keep all the pg_node_attr() in a single area with
the headers. Note that the attached includes in 0002 the tweak to
enforce the computation with compute_query_id if you want to test it
yourself and check my numbers. This is useful IMO as we could detect
missing nodes for all queries (utilities or not), still doing this
change may deserve a separate discussion. Note that I am not seeing
any "unrecognized node type" in any of the logs for any queries.

As a side note, should we be more aggressive with the tests related to
the jumbling code since it is now in core? For example, XmlExpr or
MinMaxExpr, which are part of Query nodes that can be jumbled even in
older branches, have zero coverage by default as
coverage.postgresql.org reports, because everything goes through
pg_stat_statements and it includes no queries with such nodes. My
buildfarm member batta makes sure to stress that no nodes are missing
by overloading pg_stat_statements and compute_query_id = regress in
the configuration, so no nodes are missing from the computation, still
the coverage could be better across the board. Expanding the tests of
pg_stat_statements is needed in this area for some time, still could
there be a point in switching compute_query_id = regress so as it is a
synonym of "on" without the EXPLAIN output rather than "auto"? If the
setting is enforced by pg_regress, the coverage of queryjumble.c would
be so much better, at least..
--
Michael

Attachments:

0002-Switch-compute_query_id-regress-to-mean-on-and-force.patchtext/x-diff; charset=us-asciiDownload
From f7fa4a46c03187d01d1ac3d943aa132dad0a3a52 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 8 Feb 2023 15:42:50 +0900
Subject: [PATCH 2/2] Switch compute_query_id = regress to mean "on" and force
 it in pg_regress

This is just a tweak to for tests with such code paths,
---
 src/include/nodes/queryjumble.h | 2 ++
 src/backend/commands/explain.c  | 2 +-
 src/test/regress/pg_regress.c   | 3 ++-
 doc/src/sgml/config.sgml        | 2 +-
 4 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index 204b8f74fd..3aa7d93255 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -80,6 +80,8 @@ IsQueryIdEnabled(void)
 		return false;
 	if (compute_query_id == COMPUTE_QUERY_ID_ON)
 		return true;
+	if (compute_query_id == COMPUTE_QUERY_ID_REGRESS)
+		return true;
 	return query_id_enabled;
 }
 
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index fbbf28cf06..5aba713348 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -777,7 +777,7 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
 	ExplainPrintSettings(es);
 
 	/*
-	 * COMPUTE_QUERY_ID_REGRESS means COMPUTE_QUERY_ID_AUTO, but we don't show
+	 * COMPUTE_QUERY_ID_REGRESS means COMPUTE_QUERY_ID_ON, but we don't show
 	 * the queryid in any of the EXPLAIN plans to keep stable the results
 	 * generated by regression test suites.
 	 */
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index 6cd5998b9d..d3aafa156c 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -1876,8 +1876,9 @@ create_database(const char *dbname)
 					 "ALTER DATABASE \"%s\" SET lc_numeric TO 'C';"
 					 "ALTER DATABASE \"%s\" SET lc_time TO 'C';"
 					 "ALTER DATABASE \"%s\" SET bytea_output TO 'hex';"
+					 "ALTER DATABASE \"%s\" SET compute_query_id TO 'regress';"
 					 "ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';",
-					 dbname, dbname, dbname, dbname, dbname, dbname);
+					 dbname, dbname, dbname, dbname, dbname, dbname, dbname);
 	psql_end_command(buf, "postgres");
 
 	/*
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index d190be1925..b1bb3435a5 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8226,7 +8226,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
         <literal>on</literal> (always enabled), <literal>auto</literal>,
         which lets modules such as <xref linkend="pgstatstatements"/>
         automatically enable it, and <literal>regress</literal> which
-        has the same effect as <literal>auto</literal>, except that the
+        has the same effect as <literal>on</literal>, except that the
         query identifier is not shown in the <literal>EXPLAIN</literal> output
         in order to facilitate automated regression testing.
         The default is <literal>auto</literal>.
-- 
2.39.1

0001-Mark-more-nodes-with-node-attribute-no_query_jumble.patchtext/x-diff; charset=us-asciiDownload
From 5d8d730e5fc28acdf75e33b00c56c172b400ffe3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 8 Feb 2023 15:22:28 +0900
Subject: [PATCH 1/2] Mark more nodes with node attribute no_query_jumble

This mostly covers plan and path nodes, which should never be included
in the jumbling because we ignore these in Query nodes.
---
 src/include/nodes/parsenodes.h |   8 +++
 src/include/nodes/pathnodes.h  | 126 ++++++++++++++++++++++++++-------
 src/include/nodes/plannodes.h  | 108 +++++++++++++++++++++++++---
 src/include/nodes/primnodes.h  |   4 ++
 4 files changed, 210 insertions(+), 36 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 855da99ec0..29e2940b7d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1239,6 +1239,8 @@ typedef struct RangeTblEntry
  */
 typedef struct RTEPermissionInfo
 {
+	pg_node_attr(no_query_jumble)
+
 	NodeTag		type;
 
 	Oid			relid;			/* relation OID */
@@ -1596,6 +1598,8 @@ typedef enum CTEMaterialize
 
 typedef struct CTESearchClause
 {
+	pg_node_attr(no_query_jumble)
+
 	NodeTag		type;
 	List	   *search_col_list;
 	bool		search_breadth_first;
@@ -1605,6 +1609,8 @@ typedef struct CTESearchClause
 
 typedef struct CTECycleClause
 {
+	pg_node_attr(no_query_jumble)
+
 	NodeTag		type;
 	List	   *cycle_col_list;
 	char	   *cycle_mark_column;
@@ -1731,6 +1737,8 @@ typedef struct TriggerTransition
  */
 typedef struct RawStmt
 {
+	pg_node_attr(no_query_jumble)
+
 	NodeTag		type;
 	Node	   *stmt;			/* raw parse tree */
 	int			stmt_location;	/* start location, or -1 if unknown */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d4b1ec4e4..453dedaec8 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -94,7 +94,7 @@ typedef enum UpperRelationKind
  */
 typedef struct PlannerGlobal
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -194,7 +194,7 @@ typedef struct PlannerInfo PlannerInfo;
 
 struct PlannerInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -853,7 +853,7 @@ typedef enum RelOptKind
 
 typedef struct RelOptInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1098,7 +1098,7 @@ typedef struct IndexOptInfo IndexOptInfo;
 
 struct IndexOptInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1208,7 +1208,7 @@ struct IndexOptInfo
  */
 typedef struct ForeignKeyOptInfo
 {
-	pg_node_attr(custom_read_write, no_copy_equal, no_read)
+	pg_node_attr(custom_read_write, no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1258,7 +1258,7 @@ typedef struct ForeignKeyOptInfo
  */
 typedef struct StatisticExtInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1309,7 +1309,7 @@ typedef struct StatisticExtInfo
  */
 typedef struct JoinDomain
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1371,7 +1371,7 @@ typedef struct JoinDomain
  */
 typedef struct EquivalenceClass
 {
-	pg_node_attr(custom_read_write, no_copy_equal, no_read)
+	pg_node_attr(custom_read_write, no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1422,7 +1422,7 @@ typedef struct EquivalenceClass
  */
 typedef struct EquivalenceMember
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1455,7 +1455,7 @@ typedef struct EquivalenceMember
  */
 typedef struct PathKey
 {
-	pg_node_attr(no_read)
+	pg_node_attr(no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1503,7 +1503,7 @@ typedef enum VolatileFunctionStatus
  */
 typedef struct PathTarget
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1550,7 +1550,7 @@ typedef struct PathTarget
  */
 typedef struct ParamPathInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1596,7 +1596,7 @@ typedef struct ParamPathInfo
  */
 typedef struct Path
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1684,6 +1684,8 @@ typedef struct Path
  */
 typedef struct IndexPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	IndexOptInfo *indexinfo;
 	List	   *indexclauses;
@@ -1730,7 +1732,7 @@ typedef struct IndexPath
  */
 typedef struct IndexClause
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 	struct RestrictInfo *rinfo; /* original restriction or join clause */
@@ -1759,6 +1761,8 @@ typedef struct IndexClause
  */
 typedef struct BitmapHeapPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *bitmapqual;		/* IndexPath, BitmapAndPath, BitmapOrPath */
 } BitmapHeapPath;
@@ -1771,6 +1775,8 @@ typedef struct BitmapHeapPath
  */
 typedef struct BitmapAndPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	List	   *bitmapquals;	/* IndexPaths and BitmapOrPaths */
 	Selectivity bitmapselectivity;
@@ -1784,6 +1790,8 @@ typedef struct BitmapAndPath
  */
 typedef struct BitmapOrPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	List	   *bitmapquals;	/* IndexPaths and BitmapAndPaths */
 	Selectivity bitmapselectivity;
@@ -1798,6 +1806,8 @@ typedef struct BitmapOrPath
  */
 typedef struct TidPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	List	   *tidquals;		/* qual(s) involving CTID = something */
 } TidPath;
@@ -1810,6 +1820,8 @@ typedef struct TidPath
  */
 typedef struct TidRangePath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	List	   *tidrangequals;
 } TidRangePath;
@@ -1824,6 +1836,8 @@ typedef struct TidRangePath
  */
 typedef struct SubqueryScanPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing subquery execution */
 } SubqueryScanPath;
@@ -1840,6 +1854,8 @@ typedef struct SubqueryScanPath
  */
 typedef struct ForeignPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *fdw_outerpath;
 	List	   *fdw_private;
@@ -1868,6 +1884,8 @@ struct CustomPathMethods;
 
 typedef struct CustomPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	uint32		flags;			/* mask of CUSTOMPATH_* flags, see
 								 * nodes/extensible.h */
@@ -1893,6 +1911,8 @@ typedef struct CustomPath
  */
 typedef struct AppendPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	List	   *subpaths;		/* list of component Paths */
 	/* Index of first partial path in subpaths; list_length(subpaths) if none */
@@ -1917,6 +1937,8 @@ extern bool is_dummy_rel(RelOptInfo *rel);
  */
 typedef struct MergeAppendPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	List	   *subpaths;		/* list of component Paths */
 	Cardinality limit_tuples;	/* hard limit on output tuples, or -1 */
@@ -1931,6 +1953,8 @@ typedef struct MergeAppendPath
  */
 typedef struct GroupResultPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	List	   *quals;
 } GroupResultPath;
@@ -1943,6 +1967,8 @@ typedef struct GroupResultPath
  */
 typedef struct MaterialPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;
 } MaterialPath;
@@ -1954,6 +1980,8 @@ typedef struct MaterialPath
  */
 typedef struct MemoizePath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* outerpath to cache tuples from */
 	List	   *hash_operators; /* OIDs of hash equality ops for cache keys */
@@ -1989,6 +2017,8 @@ typedef enum UniquePathMethod
 
 typedef struct UniquePath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;
 	UniquePathMethod umethod;
@@ -2003,6 +2033,8 @@ typedef struct UniquePath
  */
 typedef struct GatherPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path for each worker */
 	bool		single_copy;	/* don't execute path more than once */
@@ -2015,6 +2047,8 @@ typedef struct GatherPath
  */
 typedef struct GatherMergePath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path for each worker */
 	int			num_workers;	/* number of workers sought to help */
@@ -2027,7 +2061,7 @@ typedef struct GatherMergePath
 
 typedef struct JoinPath
 {
-	pg_node_attr(abstract)
+	pg_node_attr(abstract, no_query_jumble)
 
 	Path		path;
 
@@ -2054,6 +2088,8 @@ typedef struct JoinPath
 
 typedef struct NestPath
 {
+	pg_node_attr(no_query_jumble)
+
 	JoinPath	jpath;
 } NestPath;
 
@@ -2094,6 +2130,8 @@ typedef struct NestPath
 
 typedef struct MergePath
 {
+	pg_node_attr(no_query_jumble)
+
 	JoinPath	jpath;
 	List	   *path_mergeclauses;	/* join clauses to be used for merge */
 	List	   *outersortkeys;	/* keys for explicit sort, if any */
@@ -2113,6 +2151,8 @@ typedef struct MergePath
 
 typedef struct HashPath
 {
+	pg_node_attr(no_query_jumble)
+
 	JoinPath	jpath;
 	List	   *path_hashclauses;	/* join clauses used for hashing */
 	int			num_batches;	/* number of batches expected */
@@ -2135,6 +2175,8 @@ typedef struct HashPath
  */
 typedef struct ProjectionPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 	bool		dummypp;		/* true if no separate Result is needed */
@@ -2147,6 +2189,8 @@ typedef struct ProjectionPath
  */
 typedef struct ProjectSetPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 } ProjectSetPath;
@@ -2161,6 +2205,8 @@ typedef struct ProjectSetPath
  */
 typedef struct SortPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 } SortPath;
@@ -2173,6 +2219,8 @@ typedef struct SortPath
  */
 typedef struct IncrementalSortPath
 {
+	pg_node_attr(no_query_jumble)
+
 	SortPath	spath;
 	int			nPresortedCols; /* number of presorted columns */
 } IncrementalSortPath;
@@ -2187,6 +2235,8 @@ typedef struct IncrementalSortPath
  */
 typedef struct GroupPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 	List	   *groupClause;	/* a list of SortGroupClause's */
@@ -2201,6 +2251,8 @@ typedef struct GroupPath
  */
 typedef struct UpperUniquePath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 	int			numkeys;		/* number of pathkey columns to compare */
@@ -2215,6 +2267,8 @@ typedef struct UpperUniquePath
  */
 typedef struct AggPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 	AggStrategy aggstrategy;	/* basic strategy, see nodes.h */
@@ -2231,7 +2285,7 @@ typedef struct AggPath
 
 typedef struct GroupingSetData
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 	List	   *set;			/* grouping set as list of sortgrouprefs */
@@ -2240,7 +2294,7 @@ typedef struct GroupingSetData
 
 typedef struct RollupData
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 	List	   *groupClause;	/* applicable subset of parse->groupClause */
@@ -2257,6 +2311,8 @@ typedef struct RollupData
 
 typedef struct GroupingSetsPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 	AggStrategy aggstrategy;	/* basic strategy */
@@ -2270,6 +2326,8 @@ typedef struct GroupingSetsPath
  */
 typedef struct MinMaxAggPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	List	   *mmaggregates;	/* list of MinMaxAggInfo */
 	List	   *quals;			/* HAVING quals, if any */
@@ -2280,6 +2338,8 @@ typedef struct MinMaxAggPath
  */
 typedef struct WindowAggPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 	WindowClause *winclause;	/* WindowClause we'll be using */
@@ -2293,6 +2353,8 @@ typedef struct WindowAggPath
  */
 typedef struct SetOpPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 	SetOpCmd	cmd;			/* what to do, see nodes.h */
@@ -2308,6 +2370,8 @@ typedef struct SetOpPath
  */
 typedef struct RecursiveUnionPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *leftpath;		/* paths representing input sources */
 	Path	   *rightpath;
@@ -2321,6 +2385,8 @@ typedef struct RecursiveUnionPath
  */
 typedef struct LockRowsPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 	List	   *rowMarks;		/* a list of PlanRowMark's */
@@ -2336,6 +2402,8 @@ typedef struct LockRowsPath
  */
 typedef struct ModifyTablePath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* Path producing source data */
 	CmdType		operation;		/* INSERT, UPDATE, DELETE, or MERGE */
@@ -2359,6 +2427,8 @@ typedef struct ModifyTablePath
  */
 typedef struct LimitPath
 {
+	pg_node_attr(no_query_jumble)
+
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 	Node	   *limitOffset;	/* OFFSET parameter, or NULL if none */
@@ -2509,7 +2579,7 @@ typedef struct LimitPath
 
 typedef struct RestrictInfo
 {
-	pg_node_attr(no_read)
+	pg_node_attr(no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -2724,6 +2794,8 @@ typedef struct MergeScanSelCache
 
 typedef struct PlaceHolderVar
 {
+	pg_node_attr(no_query_jumble)
+
 	Expr		xpr;
 
 	/* the represented expression */
@@ -2825,7 +2897,7 @@ typedef struct SpecialJoinInfo SpecialJoinInfo;
 
 struct SpecialJoinInfo
 {
-	pg_node_attr(no_read)
+	pg_node_attr(no_read, no_query_jumble)
 
 	NodeTag		type;
 	Relids		min_lefthand;	/* base+OJ relids in minimum LHS for join */
@@ -2853,7 +2925,7 @@ struct SpecialJoinInfo
  */
 typedef struct OuterJoinClauseInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 	RestrictInfo *rinfo;		/* a mergejoinable outer-join clause */
@@ -2892,6 +2964,8 @@ typedef struct OuterJoinClauseInfo
 
 typedef struct AppendRelInfo
 {
+	pg_node_attr(no_query_jumble)
+
 	NodeTag		type;
 
 	/*
@@ -2967,7 +3041,7 @@ typedef struct AppendRelInfo
  */
 typedef struct RowIdentityVarInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -3005,7 +3079,7 @@ typedef struct RowIdentityVarInfo
 
 typedef struct PlaceHolderInfo
 {
-	pg_node_attr(no_read)
+	pg_node_attr(no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -3038,7 +3112,7 @@ typedef struct PlaceHolderInfo
  */
 typedef struct MinMaxAggInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -3116,7 +3190,7 @@ typedef struct MinMaxAggInfo
  */
 typedef struct PlannerParamItem
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -3296,7 +3370,7 @@ typedef struct JoinCostWorkspace
  */
 typedef struct AggInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -3330,7 +3404,7 @@ typedef struct AggInfo
  */
 typedef struct AggTransInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 4781a9c632..9199c16f6f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -46,7 +46,7 @@
  */
 typedef struct PlannedStmt
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	NodeTag		type;
 
@@ -122,7 +122,7 @@ typedef struct PlannedStmt
  */
 typedef struct Plan
 {
-	pg_node_attr(abstract, no_equal)
+	pg_node_attr(abstract, no_equal, no_query_jumble)
 
 	NodeTag		type;
 
@@ -199,6 +199,8 @@ typedef struct Plan
  */
 typedef struct Result
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 	Node	   *resconstantqual;
 } Result;
@@ -211,6 +213,8 @@ typedef struct Result
  */
 typedef struct ProjectSet
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 } ProjectSet;
 
@@ -231,6 +235,8 @@ typedef struct ProjectSet
  */
 typedef struct ModifyTable
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 	CmdType		operation;		/* INSERT, UPDATE, DELETE, or MERGE */
 	bool		canSetTag;		/* do we set the command tag/es_processed? */
@@ -265,6 +271,8 @@ struct PartitionPruneInfo;		/* forward reference to struct below */
  */
 typedef struct Append
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 	Bitmapset  *apprelids;		/* RTIs of appendrel(s) formed by this node */
 	List	   *appendplans;
@@ -287,6 +295,8 @@ typedef struct Append
  */
 typedef struct MergeAppend
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* RTIs of appendrel(s) formed by this node */
@@ -325,6 +335,8 @@ typedef struct MergeAppend
  */
 typedef struct RecursiveUnion
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* ID of Param representing work table */
@@ -356,6 +368,8 @@ typedef struct RecursiveUnion
  */
 typedef struct BitmapAnd
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 	List	   *bitmapplans;
 } BitmapAnd;
@@ -370,6 +384,8 @@ typedef struct BitmapAnd
  */
 typedef struct BitmapOr
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 	bool		isshared;
 	List	   *bitmapplans;
@@ -384,7 +400,7 @@ typedef struct BitmapOr
  */
 typedef struct Scan
 {
-	pg_node_attr(abstract)
+	pg_node_attr(abstract, no_query_jumble)
 
 	Plan		plan;
 	Index		scanrelid;		/* relid is index into the range table */
@@ -396,6 +412,8 @@ typedef struct Scan
  */
 typedef struct SeqScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 } SeqScan;
 
@@ -405,6 +423,8 @@ typedef struct SeqScan
  */
 typedef struct SampleScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	/* use struct pointer to avoid including parsenodes.h here */
 	struct TableSampleClause *tablesample;
@@ -449,6 +469,8 @@ typedef struct SampleScan
  */
 typedef struct IndexScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	Oid			indexid;		/* OID of index to scan */
 	List	   *indexqual;		/* list of index quals (usually OpExprs) */
@@ -492,6 +514,8 @@ typedef struct IndexScan
  */
 typedef struct IndexOnlyScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	Oid			indexid;		/* OID of index to scan */
 	List	   *indexqual;		/* list of index quals (usually OpExprs) */
@@ -520,6 +544,8 @@ typedef struct IndexOnlyScan
  */
 typedef struct BitmapIndexScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	Oid			indexid;		/* OID of index to scan */
 	bool		isshared;		/* Create shared bitmap if set */
@@ -538,6 +564,8 @@ typedef struct BitmapIndexScan
  */
 typedef struct BitmapHeapScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	List	   *bitmapqualorig; /* index quals, in standard expr form */
 } BitmapHeapScan;
@@ -552,6 +580,8 @@ typedef struct BitmapHeapScan
  */
 typedef struct TidScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	List	   *tidquals;		/* qual(s) involving CTID = something */
 } TidScan;
@@ -565,6 +595,8 @@ typedef struct TidScan
  */
 typedef struct TidRangeScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	List	   *tidrangequals;	/* qual(s) involving CTID op something */
 } TidRangeScan;
@@ -598,6 +630,8 @@ typedef enum SubqueryScanStatus
 
 typedef struct SubqueryScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	Plan	   *subplan;
 	SubqueryScanStatus scanstatus;
@@ -609,6 +643,8 @@ typedef struct SubqueryScan
  */
 typedef struct FunctionScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	List	   *functions;		/* list of RangeTblFunction nodes */
 	bool		funcordinality; /* WITH ORDINALITY */
@@ -620,6 +656,8 @@ typedef struct FunctionScan
  */
 typedef struct ValuesScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	List	   *values_lists;	/* list of expression lists */
 } ValuesScan;
@@ -630,6 +668,8 @@ typedef struct ValuesScan
  */
 typedef struct TableFuncScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	TableFunc  *tablefunc;		/* table function node */
 } TableFuncScan;
@@ -640,6 +680,8 @@ typedef struct TableFuncScan
  */
 typedef struct CteScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	int			ctePlanId;		/* ID of init SubPlan for CTE */
 	int			cteParam;		/* ID of Param representing CTE output */
@@ -651,6 +693,8 @@ typedef struct CteScan
  */
 typedef struct NamedTuplestoreScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	char	   *enrname;		/* Name given to Ephemeral Named Relation */
 } NamedTuplestoreScan;
@@ -661,6 +705,8 @@ typedef struct NamedTuplestoreScan
  */
 typedef struct WorkTableScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	int			wtParam;		/* ID of Param representing work table */
 } WorkTableScan;
@@ -707,6 +753,8 @@ typedef struct WorkTableScan
  */
 typedef struct ForeignScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
@@ -739,6 +787,8 @@ struct CustomScanMethods;
 
 typedef struct CustomScan
 {
+	pg_node_attr(no_query_jumble)
+
 	Scan		scan;
 	uint32		flags;			/* mask of CUSTOMPATH_* flags, see
 								 * nodes/extensible.h */
@@ -786,7 +836,7 @@ typedef struct CustomScan
  */
 typedef struct Join
 {
-	pg_node_attr(abstract)
+	pg_node_attr(abstract, no_query_jumble)
 
 	Plan		plan;
 	JoinType	jointype;
@@ -807,13 +857,15 @@ typedef struct Join
  */
 typedef struct NestLoop
 {
+	pg_node_attr(no_query_jumble)
+
 	Join		join;
 	List	   *nestParams;		/* list of NestLoopParam nodes */
 } NestLoop;
 
 typedef struct NestLoopParam
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	NodeTag		type;
 	int			paramno;		/* number of the PARAM_EXEC Param to set */
@@ -833,6 +885,8 @@ typedef struct NestLoopParam
  */
 typedef struct MergeJoin
 {
+	pg_node_attr(no_query_jumble)
+
 	Join		join;
 
 	/* Can we skip mark/restore calls? */
@@ -862,6 +916,8 @@ typedef struct MergeJoin
  */
 typedef struct HashJoin
 {
+	pg_node_attr(no_query_jumble)
+
 	Join		join;
 	List	   *hashclauses;
 	List	   *hashoperators;
@@ -880,6 +936,8 @@ typedef struct HashJoin
  */
 typedef struct Material
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 } Material;
 
@@ -889,6 +947,8 @@ typedef struct Material
  */
 typedef struct Memoize
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* size of the two arrays below */
@@ -931,6 +991,8 @@ typedef struct Memoize
  */
 typedef struct Sort
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* number of sort-key columns */
@@ -955,6 +1017,8 @@ typedef struct Sort
  */
 typedef struct IncrementalSort
 {
+	pg_node_attr(no_query_jumble)
+
 	Sort		sort;
 	int			nPresortedCols; /* number of presorted columns */
 } IncrementalSort;
@@ -967,6 +1031,8 @@ typedef struct IncrementalSort
  */
 typedef struct Group
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* number of grouping columns */
@@ -996,6 +1062,8 @@ typedef struct Group
  */
 typedef struct Agg
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* basic strategy, see nodes.h */
@@ -1038,6 +1106,8 @@ typedef struct Agg
  */
 typedef struct WindowAgg
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* ID referenced by window functions */
@@ -1112,6 +1182,8 @@ typedef struct WindowAgg
  */
 typedef struct Unique
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* number of columns to check for uniqueness */
@@ -1140,6 +1212,8 @@ typedef struct Unique
  */
 typedef struct Gather
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 	int			num_workers;	/* planned number of worker processes */
 	int			rescan_param;	/* ID of Param that signals a rescan, or -1 */
@@ -1155,6 +1229,8 @@ typedef struct Gather
  */
 typedef struct GatherMerge
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* planned number of worker processes */
@@ -1197,6 +1273,8 @@ typedef struct GatherMerge
  */
 typedef struct Hash
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/*
@@ -1217,6 +1295,8 @@ typedef struct Hash
  */
 typedef struct SetOp
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* what to do, see nodes.h */
@@ -1256,6 +1336,8 @@ typedef struct SetOp
  */
 typedef struct LockRows
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 	List	   *rowMarks;		/* a list of PlanRowMark's */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
@@ -1270,6 +1352,8 @@ typedef struct LockRows
  */
 typedef struct Limit
 {
+	pg_node_attr(no_query_jumble)
+
 	Plan		plan;
 
 	/* OFFSET parameter, or NULL if none */
@@ -1377,7 +1461,7 @@ typedef enum RowMarkType
  */
 typedef struct PlanRowMark
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	NodeTag		type;
 	Index		rti;			/* range table index of markable relation */
@@ -1425,7 +1509,7 @@ typedef struct PlanRowMark
  */
 typedef struct PartitionPruneInfo
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	NodeTag		type;
 	Bitmapset  *root_parent_relids;
@@ -1452,7 +1536,7 @@ typedef struct PartitionPruneInfo
  */
 typedef struct PartitionedRelPruneInfo
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1495,7 +1579,7 @@ typedef struct PartitionedRelPruneInfo
  */
 typedef struct PartitionPruneStep
 {
-	pg_node_attr(abstract, no_equal)
+	pg_node_attr(abstract, no_equal, no_query_jumble)
 
 	NodeTag		type;
 	int			step_id;
@@ -1530,6 +1614,8 @@ typedef struct PartitionPruneStep
  */
 typedef struct PartitionPruneStepOp
 {
+	pg_node_attr(no_query_jumble)
+
 	PartitionPruneStep step;
 
 	StrategyNumber opstrategy;
@@ -1552,6 +1638,8 @@ typedef enum PartitionPruneCombineOp
 
 typedef struct PartitionPruneStepCombine
 {
+	pg_node_attr(no_query_jumble)
+
 	PartitionPruneStep step;
 
 	PartitionPruneCombineOp combineOp;
@@ -1570,7 +1658,7 @@ typedef struct PartitionPruneStepCombine
  */
 typedef struct PlanInvalItem
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	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 6d740be5c0..1be1642d92 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -982,6 +982,8 @@ typedef struct SubLink
  */
 typedef struct SubPlan
 {
+	pg_node_attr(no_query_jumble)
+
 	Expr		xpr;
 	/* Fields copied from original SubLink: */
 	SubLinkType subLinkType;	/* see above */
@@ -1029,6 +1031,8 @@ typedef struct SubPlan
  */
 typedef struct AlternativeSubPlan
 {
+	pg_node_attr(no_query_jumble)
+
 	Expr		xpr;
 	List	   *subplans;		/* SubPlan(s) with equivalent results */
 } AlternativeSubPlan;
-- 
2.39.1

#33Andres Freund
andres@anarazel.de
In reply to: Michael Paquier (#32)
Re: Generating code for query jumbling through gen_node_support.pl

Hi,

On 2023-02-08 15:47:51 +0900, Michael Paquier wrote:

This one was intentional to let extensions play with jumbling of such
nodes, but perhaps you are right that it makes little sense at this
stage. If there is an ask for it later, though.. Using
shared_preload_libraries = pg_stat_statements and compute_query_id =
regress shows that numbers go up to 60% for funcs.c and 30% for
switch.c. Removing nodes like as of the attached brings these numbers
respectively up to 94.5% and 93.5% for a check. With a check-world, I
measure respectively 96.7% and 96.1% because there is more coverage
for extensions, ALTER SYSTEM and database commands, roughly.

Given that we already pay the price of multiple regress runs, and that
jumbling is now really a core feature, perhaps we should enable
pg_stat_statements in pg_upgrade or 027_stream_regress.pl? I'd hope it
wouldn't add a meaningful amount of time? A tiny bit of verification at the
end should also be ok.

Both pg_upgrade and 027_stream_regress.pl have some advantages. The former
would test pg_upgrade interactions with shared_preload_libraries, the latter
could do some basic checks of pg_stat_statements on a standby.

Greetings,

Andres Freund

#34Michael Paquier
michael@paquier.xyz
In reply to: Andres Freund (#33)
Re: Generating code for query jumbling through gen_node_support.pl

On Tue, Feb 07, 2023 at 11:01:03PM -0800, Andres Freund wrote:

Given that we already pay the price of multiple regress runs, and that
jumbling is now really a core feature, perhaps we should enable
pg_stat_statements in pg_upgrade or 027_stream_regress.pl? I'd hope it
wouldn't add a meaningful amount of time? A tiny bit of verification at the
end should also be ok.

Yeah, I have briefly mentioned this part upthread:
/messages/by-id/Y8+BdCOjxykre5es@paquier.xyz

It would not, I guess, as long as pg_stat_statements.max is set large
enough in the TAP test. There are currently 21k~22k entries in the
regression database, much larger than the default of 5000 so this may
become an issue on small-ish machines if left untouched even in a TAP
test.

Both pg_upgrade and 027_stream_regress.pl have some advantages. The former
would test pg_upgrade interactions with shared_preload_libraries, the latter
could do some basic checks of pg_stat_statements on a standby.

Yes, there could be more checks, potentially useful for both cases, so
I may choose both at the end of the day. Checking the consistency of
the contents of pg_stat_statements across a pg_upgrade run for the
same version may be one thing? I am not sure if it is that
interesting, TBH, still that's one idea :)
--
Michael

#35Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#32)
Re: Generating code for query jumbling through gen_node_support.pl

On Wed, Feb 08, 2023 at 03:47:51PM +0900, Michael Paquier wrote:

This one was intentional to let extensions play with jumbling of such
nodes, but perhaps you are right that it makes little sense at this
stage. If there is an ask for it later, though.. Using
shared_preload_libraries = pg_stat_statements and compute_query_id =
regress shows that numbers go up to 60% for funcs.c and 30% for
switch.c. Removing nodes like as of the attached brings these numbers
respectively up to 94.5% and 93.5% for a check. With a check-world, I
measure respectively 96.7% and 96.1% because there is more coverage
for extensions, ALTER SYSTEM and database commands, roughly.

Tom, did you get a chance to look at what is proposed here and expand
the use of query_jumble_ignore in the definitions of the nodes rather
than have an enforced per-file policy in gen_node_support.pl? The
attached improves the situation by as much as we can, still the
numbers reported by coverage.postgresql.org won't go that up until we
enforce query jumbling testing on the regression database (something
we should have done since this code moved to core, I guess).
--
Michael

#36Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#35)
Re: Generating code for query jumbling through gen_node_support.pl

Michael Paquier <michael@paquier.xyz> writes:

Tom, did you get a chance to look at what is proposed here and expand
the use of query_jumble_ignore in the definitions of the nodes rather
than have an enforced per-file policy in gen_node_support.pl?

Sorry, didn't look at it before.

I'm okay with the pathnodes.h changes --- although surely you don't need
changes like this:

-	pg_node_attr(abstract)
+	pg_node_attr(abstract, no_query_jumble)

"abstract" should already imply "no_query_jumble".

I wonder too if you could shorten the changes by making no_query_jumble
an inheritable attribute, and then just applying it to Path and Plan.

The changes in parsenodes.h seem wrong, except for RawStmt. Those node
types are used in parsed queries, aren't they?

regards, tom lane

#37Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#36)
2 attachment(s)
Re: Generating code for query jumbling through gen_node_support.pl

On Thu, Feb 09, 2023 at 06:12:50PM -0500, Tom Lane wrote:

I'm okay with the pathnodes.h changes --- although surely you don't need
changes like this:

-	pg_node_attr(abstract)
+	pg_node_attr(abstract, no_query_jumble)

"abstract" should already imply "no_query_jumble".

Okay, understood. Following this string of thoughts, I am a bit
surprised for two cases, though:
- PartitionPruneStep.
- Plan.
Both are abstract and both are marked with no_equal. I guess that
applying no_query_jumble to both of them is fine, and that's what you
mean?

I wonder too if you could shorten the changes by making no_query_jumble
an inheritable attribute, and then just applying it to Path and Plan.

Ah.  I did not catch what you meant here at first, but I think that I
do now.  Are you referring to the part of gen_node_support.pl where we
propagate properties when a node is a supertype?  This part would be
taken into account when a node is parsed but we find that its first
member is already tracked as a node:
	# 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_query_jumble, $in_struct
+		  if elem $supertype, @no_query_jumble;
	}

A benefit of doing that would also discard all the Scan and Sort
nodes. So like the other no_* attributes, it makes sense to force the
inheritance here.

The changes in parsenodes.h seem wrong, except for RawStmt. Those node
types are used in parsed queries, aren't they?

RTEPermissionInfo is a recent addition, as of a61b1f7. This commit
documents it as a plan node, still it is part of a Query while being
ignored in the query jumbling since its introduction, so I am a bit
confused by this one.

Anyway, none of these need to be included in the query jumbling
currently because they are ignored, but I'd be fine to generate their
code by default as they could become relevant if other nodes begin to
rely on them more heavily, as being part of queries. Peter E. has
mentioned upthread that a few nodes should include more jumbling while
some other parts should be ignored. This should be analyzed
separately because ~15 does not seem to be strictly right, either.

Attached is a patch refreshed with all that. Feel free to ignore 0002
as that's just useful to enforce the tests to go through the jumbling
code. The attached reaches 95.0% of line coverage after a check-world
in funcs.c.

Thoughts?
--
Michael

Attachments:

v2-0001-Mark-more-nodes-with-node-attribute-no_query_jumb.patchtext/x-diff; charset=us-asciiDownload
From 316df43f7574f54ff93606549167102e319f5473 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 10 Feb 2023 11:15:24 +0900
Subject: [PATCH v2 1/2] Mark more nodes with node attribute no_query_jumble

This mostly covers Plan and Path nodes, which should never be included
in the jumbling because we ignore these in Query nodes.  no_query_jumble
is now an inherited attribute to be able to achieve that, like its read
and write counterparts.
---
 src/include/nodes/nodes.h             |  8 ++--
 src/include/nodes/parsenodes.h        |  5 +++
 src/include/nodes/pathnodes.h         | 54 ++++++++++++++-------------
 src/include/nodes/plannodes.h         | 16 ++++----
 src/include/nodes/primnodes.h         |  4 ++
 src/backend/nodes/gen_node_support.pl |  2 +
 6 files changed, 52 insertions(+), 37 deletions(-)

diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 75dfe1919d..e6cf520547 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -77,10 +77,10 @@ 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, and
- * no_read node attributes are automatically inherited from the supertype.
- * (Notice that nodetag_only does not inherit, so it's not quite equivalent
- * to a combination of other attributes.)
+ * type, then it is the supertype of that type.  The no_copy, no_equal,
+ * no_query_jumble and no_read node attributes are automatically inherited
+ * from the supertype. (Notice that nodetag_only does not inherit, so it's
+ * not quite equivalent to a combination of other attributes.)
  *
  * Valid node field attributes:
  *
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 855da99ec0..9c97148b69 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1728,9 +1728,14 @@ typedef struct TriggerTransition
  *
  * stmt_location/stmt_len identify the portion of the source text string
  * containing this raw statement (useful for multi-statement strings).
+ *
+ * This is irrelevant for query jumbling, as this is not used in parsed
+ * queries.
  */
 typedef struct RawStmt
 {
+	pg_node_attr(no_query_jumble)
+
 	NodeTag		type;
 	Node	   *stmt;			/* raw parse tree */
 	int			stmt_location;	/* start location, or -1 if unknown */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d4b1ec4e4..be4d791212 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -94,7 +94,7 @@ typedef enum UpperRelationKind
  */
 typedef struct PlannerGlobal
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -194,7 +194,7 @@ typedef struct PlannerInfo PlannerInfo;
 
 struct PlannerInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -853,7 +853,7 @@ typedef enum RelOptKind
 
 typedef struct RelOptInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1098,7 +1098,7 @@ typedef struct IndexOptInfo IndexOptInfo;
 
 struct IndexOptInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1208,7 +1208,7 @@ struct IndexOptInfo
  */
 typedef struct ForeignKeyOptInfo
 {
-	pg_node_attr(custom_read_write, no_copy_equal, no_read)
+	pg_node_attr(custom_read_write, no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1258,7 +1258,7 @@ typedef struct ForeignKeyOptInfo
  */
 typedef struct StatisticExtInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1309,7 +1309,7 @@ typedef struct StatisticExtInfo
  */
 typedef struct JoinDomain
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1371,7 +1371,7 @@ typedef struct JoinDomain
  */
 typedef struct EquivalenceClass
 {
-	pg_node_attr(custom_read_write, no_copy_equal, no_read)
+	pg_node_attr(custom_read_write, no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1422,7 +1422,7 @@ typedef struct EquivalenceClass
  */
 typedef struct EquivalenceMember
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1455,7 +1455,7 @@ typedef struct EquivalenceMember
  */
 typedef struct PathKey
 {
-	pg_node_attr(no_read)
+	pg_node_attr(no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1503,7 +1503,7 @@ typedef enum VolatileFunctionStatus
  */
 typedef struct PathTarget
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1550,7 +1550,7 @@ typedef struct PathTarget
  */
 typedef struct ParamPathInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1596,7 +1596,7 @@ typedef struct ParamPathInfo
  */
 typedef struct Path
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1730,7 +1730,7 @@ typedef struct IndexPath
  */
 typedef struct IndexClause
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 	struct RestrictInfo *rinfo; /* original restriction or join clause */
@@ -2231,7 +2231,7 @@ typedef struct AggPath
 
 typedef struct GroupingSetData
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 	List	   *set;			/* grouping set as list of sortgrouprefs */
@@ -2240,7 +2240,7 @@ typedef struct GroupingSetData
 
 typedef struct RollupData
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 	List	   *groupClause;	/* applicable subset of parse->groupClause */
@@ -2509,7 +2509,7 @@ typedef struct LimitPath
 
 typedef struct RestrictInfo
 {
-	pg_node_attr(no_read)
+	pg_node_attr(no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -2724,6 +2724,8 @@ typedef struct MergeScanSelCache
 
 typedef struct PlaceHolderVar
 {
+	pg_node_attr(no_query_jumble)
+
 	Expr		xpr;
 
 	/* the represented expression */
@@ -2825,7 +2827,7 @@ typedef struct SpecialJoinInfo SpecialJoinInfo;
 
 struct SpecialJoinInfo
 {
-	pg_node_attr(no_read)
+	pg_node_attr(no_read, no_query_jumble)
 
 	NodeTag		type;
 	Relids		min_lefthand;	/* base+OJ relids in minimum LHS for join */
@@ -2853,7 +2855,7 @@ struct SpecialJoinInfo
  */
 typedef struct OuterJoinClauseInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 	RestrictInfo *rinfo;		/* a mergejoinable outer-join clause */
@@ -2892,6 +2894,8 @@ typedef struct OuterJoinClauseInfo
 
 typedef struct AppendRelInfo
 {
+	pg_node_attr(no_query_jumble)
+
 	NodeTag		type;
 
 	/*
@@ -2967,7 +2971,7 @@ typedef struct AppendRelInfo
  */
 typedef struct RowIdentityVarInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -3005,7 +3009,7 @@ typedef struct RowIdentityVarInfo
 
 typedef struct PlaceHolderInfo
 {
-	pg_node_attr(no_read)
+	pg_node_attr(no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -3038,7 +3042,7 @@ typedef struct PlaceHolderInfo
  */
 typedef struct MinMaxAggInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -3116,7 +3120,7 @@ typedef struct MinMaxAggInfo
  */
 typedef struct PlannerParamItem
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -3296,7 +3300,7 @@ typedef struct JoinCostWorkspace
  */
 typedef struct AggInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
@@ -3330,7 +3334,7 @@ typedef struct AggInfo
  */
 typedef struct AggTransInfo
 {
-	pg_node_attr(no_copy_equal, no_read)
+	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
 
 	NodeTag		type;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 4781a9c632..659bd05c0c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -46,7 +46,7 @@
  */
 typedef struct PlannedStmt
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	NodeTag		type;
 
@@ -122,7 +122,7 @@ typedef struct PlannedStmt
  */
 typedef struct Plan
 {
-	pg_node_attr(abstract, no_equal)
+	pg_node_attr(abstract, no_equal, no_query_jumble)
 
 	NodeTag		type;
 
@@ -813,7 +813,7 @@ typedef struct NestLoop
 
 typedef struct NestLoopParam
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	NodeTag		type;
 	int			paramno;		/* number of the PARAM_EXEC Param to set */
@@ -1377,7 +1377,7 @@ typedef enum RowMarkType
  */
 typedef struct PlanRowMark
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	NodeTag		type;
 	Index		rti;			/* range table index of markable relation */
@@ -1425,7 +1425,7 @@ typedef struct PlanRowMark
  */
 typedef struct PartitionPruneInfo
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	NodeTag		type;
 	Bitmapset  *root_parent_relids;
@@ -1452,7 +1452,7 @@ typedef struct PartitionPruneInfo
  */
 typedef struct PartitionedRelPruneInfo
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	NodeTag		type;
 
@@ -1495,7 +1495,7 @@ typedef struct PartitionedRelPruneInfo
  */
 typedef struct PartitionPruneStep
 {
-	pg_node_attr(abstract, no_equal)
+	pg_node_attr(abstract, no_equal, no_query_jumble)
 
 	NodeTag		type;
 	int			step_id;
@@ -1570,7 +1570,7 @@ typedef struct PartitionPruneStepCombine
  */
 typedef struct PlanInvalItem
 {
-	pg_node_attr(no_equal)
+	pg_node_attr(no_equal, no_query_jumble)
 
 	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 6d740be5c0..1be1642d92 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -982,6 +982,8 @@ typedef struct SubLink
  */
 typedef struct SubPlan
 {
+	pg_node_attr(no_query_jumble)
+
 	Expr		xpr;
 	/* Fields copied from original SubLink: */
 	SubLinkType subLinkType;	/* see above */
@@ -1029,6 +1031,8 @@ typedef struct SubPlan
  */
 typedef struct AlternativeSubPlan
 {
+	pg_node_attr(no_query_jumble)
+
 	Expr		xpr;
 	List	   *subplans;		/* SubPlan(s) with equivalent results */
 } AlternativeSubPlan;
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 19ed29657c..2e57db915b 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -422,6 +422,8 @@ foreach my $infile (@ARGV)
 						  if elem $supertype, @no_equal;
 						push @no_read, $in_struct
 						  if elem $supertype, @no_read;
+						push @no_query_jumble, $in_struct
+						  if elem $supertype, @no_query_jumble;
 					}
 				}
 
-- 
2.39.1

v2-0002-Switch-compute_query_id-regress-to-mean-on-and-fo.patchtext/x-diff; charset=us-asciiDownload
From 51bc070c098721fd91ae729408365a222c3d1959 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 10 Feb 2023 11:21:57 +0900
Subject: [PATCH v2 2/2] Switch compute_query_id = regress to mean "on" and
 force it in pg_regress

This is just a tweak to force the tests to go through the query
jumbling.
---
 src/include/nodes/queryjumble.h      | 2 ++
 src/backend/commands/explain.c       | 2 +-
 src/backend/nodes/queryjumblefuncs.c | 2 +-
 src/test/regress/pg_regress.c        | 3 ++-
 doc/src/sgml/config.sgml             | 2 +-
 5 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index 204b8f74fd..3aa7d93255 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -80,6 +80,8 @@ IsQueryIdEnabled(void)
 		return false;
 	if (compute_query_id == COMPUTE_QUERY_ID_ON)
 		return true;
+	if (compute_query_id == COMPUTE_QUERY_ID_REGRESS)
+		return true;
 	return query_id_enabled;
 }
 
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index fbbf28cf06..5aba713348 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -777,7 +777,7 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
 	ExplainPrintSettings(es);
 
 	/*
-	 * COMPUTE_QUERY_ID_REGRESS means COMPUTE_QUERY_ID_AUTO, but we don't show
+	 * COMPUTE_QUERY_ID_REGRESS means COMPUTE_QUERY_ID_ON, but we don't show
 	 * the queryid in any of the EXPLAIN plans to keep stable the results
 	 * generated by regression test suites.
 	 */
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index d7fd72d70f..4d594106ec 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -257,7 +257,7 @@ _jumbleNode(JumbleState *jstate, Node *node)
 
 		default:
 			/* Only a warning, since we can stumble along anyway */
-			elog(WARNING, "unrecognized node type: %d",
+			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(expr));
 			break;
 	}
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index 6cd5998b9d..d3aafa156c 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -1876,8 +1876,9 @@ create_database(const char *dbname)
 					 "ALTER DATABASE \"%s\" SET lc_numeric TO 'C';"
 					 "ALTER DATABASE \"%s\" SET lc_time TO 'C';"
 					 "ALTER DATABASE \"%s\" SET bytea_output TO 'hex';"
+					 "ALTER DATABASE \"%s\" SET compute_query_id TO 'regress';"
 					 "ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';",
-					 dbname, dbname, dbname, dbname, dbname, dbname);
+					 dbname, dbname, dbname, dbname, dbname, dbname, dbname);
 	psql_end_command(buf, "postgres");
 
 	/*
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 8c56b134a8..56f8dac286 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8226,7 +8226,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
         <literal>on</literal> (always enabled), <literal>auto</literal>,
         which lets modules such as <xref linkend="pgstatstatements"/>
         automatically enable it, and <literal>regress</literal> which
-        has the same effect as <literal>auto</literal>, except that the
+        has the same effect as <literal>on</literal>, except that the
         query identifier is not shown in the <literal>EXPLAIN</literal> output
         in order to facilitate automated regression testing.
         The default is <literal>auto</literal>.
-- 
2.39.1

#38Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#37)
Re: Generating code for query jumbling through gen_node_support.pl

Michael Paquier <michael@paquier.xyz> writes:

Okay, understood. Following this string of thoughts, I am a bit
surprised for two cases, though:
- PartitionPruneStep.
- Plan.
Both are abstract and both are marked with no_equal. I guess that
applying no_query_jumble to both of them is fine, and that's what you
mean?

On second thought, the point of that is to allow the no_equal property
to automatically inherit to child node types, so doing likewise
for no_query_jumble is sensible.

The changes in parsenodes.h seem wrong, except for RawStmt. Those node
types are used in parsed queries, aren't they?

RTEPermissionInfo is a recent addition, as of a61b1f7. This commit
documents it as a plan node, still it is part of a Query while being
ignored in the query jumbling since its introduction, so I am a bit
confused by this one.

Hmm ... it is part of Query, so that documentation is wrong, and the
fact that it's not reached by query jumbling kind of seems like a bug.
However, it might be that everything in it is derived from something
else that *is* covered by jumbling, in which case that's okay, if
underdocumented.

... Peter E. has
mentioned upthread that a few nodes should include more jumbling while
some other parts should be ignored. This should be analyzed
separately because ~15 does not seem to be strictly right, either.

Yeah. It'd surprise me not at all if people have overlooked that.

v2 looks good to me as far as it goes. I agree these other questions
deserve a separate look.

regards, tom lane

#39Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#38)
Re: Generating code for query jumbling through gen_node_support.pl

On Fri, Feb 10, 2023 at 04:40:08PM -0500, Tom Lane wrote:

v2 looks good to me as far as it goes.

Thanks. I have applied that after an extra lookup.

I agree these other questions deserve a separate look.

Okay, I may be able to come back to that. Another point is that we
need to do a better job in forcing the execution of the query jumbling
in one of the TAP tests running pg_regress, outside
pg_stat_statements, to maximize coverage. Will see to that on a
separate thread.
--
Michael

#40Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#26)
Re: Generating code for query jumbling through gen_node_support.pl

On Mon, Jan 30, 2023 at 11:48:45AM +0100, Peter Eisentraut wrote:

On 27.01.23 03:59, Michael Paquier wrote:

At the end, that would be unnoticeable for the average user, I guess,
but here are the numbers I get on my laptop :)

Personally, I think we do not want the two jumble methods in parallel.

Maybe there are other opinions.

(Thanks Jonathan for the poke.)

Now that we are in mid-beta for 16, it would be a good time to
conclude on this open item:
"Reconsider a utility_query_id GUC to control if query jumbling of
utilities can go through the past string-only mode and the new mode?"

In Postgres ~15, utility commands used a hash of the query string to
compute their query ID. The current query jumbling code uses a Query
instead, like any other queries. I have registered this open item as
a self-reminder, mostly in case there would be an argument to have a
GUC where users could switch from one mode to another. See here as
well for some computation times for each method (table is in ns, wiht
millions of iterations):
/messages/by-id/Y9eeYinDb1AcpWrG@paquier.xyz

I still don't think that we need both methods based on these numbers,
but there may be more opinions about that? Are people OK if this open
item is discarded?
--
Michael

#41Andrey Lepikhov
a.lepikhov@postgrespro.ru
In reply to: Michael Paquier (#40)
Re: Generating code for query jumbling through gen_node_support.pl

On 11/7/2023 05:35, Michael Paquier wrote:

On Mon, Jan 30, 2023 at 11:48:45AM +0100, Peter Eisentraut wrote:

On 27.01.23 03:59, Michael Paquier wrote:

At the end, that would be unnoticeable for the average user, I guess,
but here are the numbers I get on my laptop :)

Personally, I think we do not want the two jumble methods in parallel.

Maybe there are other opinions.

(Thanks Jonathan for the poke.)

Now that we are in mid-beta for 16, it would be a good time to
conclude on this open item:
"Reconsider a utility_query_id GUC to control if query jumbling of
utilities can go through the past string-only mode and the new mode?"

In Postgres ~15, utility commands used a hash of the query string to
compute their query ID. The current query jumbling code uses a Query
instead, like any other queries. I have registered this open item as
a self-reminder, mostly in case there would be an argument to have a
GUC where users could switch from one mode to another. See here as
well for some computation times for each method (table is in ns, wiht
millions of iterations):
/messages/by-id/Y9eeYinDb1AcpWrG@paquier.xyz

I still don't think that we need both methods based on these numbers,
but there may be more opinions about that? Are people OK if this open
item is discarded?

I vote for only one method based on a query tree structure.
BTW, did you think about different algorithms of queryId generation?
Auto-generated queryId code can open a way for extensions to have
easy-supporting custom queryIds.

--
regards,
Andrey Lepikhov
Postgres Professional

#42Michael Paquier
michael@paquier.xyz
In reply to: Andrey Lepikhov (#41)
Re: Generating code for query jumbling through gen_node_support.pl

On Tue, Jul 11, 2023 at 12:29:29PM +0700, Andrey Lepikhov wrote:

I vote for only one method based on a query tree structure.

Noted

BTW, did you think about different algorithms of queryId generation?

Not really, except if you are referring to the possibility of being
able to handle differently different portions of the nodes depending
on a context given by the callers willing to do a query jumbling
computation. (For example, choose to *not* silence the Const nodes,
etc.)

Auto-generated queryId code can open a way for extensions to have
easy-supporting custom queryIds.

Extensions can control that at some extent, already.
--
Michael

#43Andrey Lepikhov
a.lepikhov@postgrespro.ru
In reply to: Michael Paquier (#42)
Re: Generating code for query jumbling through gen_node_support.pl

On 11/7/2023 12:35, Michael Paquier wrote:

On Tue, Jul 11, 2023 at 12:29:29PM +0700, Andrey Lepikhov wrote:

I vote for only one method based on a query tree structure.

Noted

BTW, did you think about different algorithms of queryId generation?

Not really, except if you are referring to the possibility of being
able to handle differently different portions of the nodes depending
on a context given by the callers willing to do a query jumbling
computation. (For example, choose to *not* silence the Const nodes,
etc.)

Yes, I have two requests on different queryId algorithms:
1. With suppressed Const nodes.
2. With replacement of Oids with full names - to give a chance to see
the same queryId at different instances for the same query.

It is quite trivial to implement, but not easy in support.

Auto-generated queryId code can open a way for extensions to have
easy-supporting custom queryIds.

Extensions can control that at some extent, already.
--
Michael

--
regards,
Andrey Lepikhov
Postgres Professional

#44Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#40)
Re: Generating code for query jumbling through gen_node_support.pl

On Tue, Jul 11, 2023 at 07:35:43AM +0900, Michael Paquier wrote:

I still don't think that we need both methods based on these numbers,
but there may be more opinions about that? Are people OK if this open
item is discarded?

Hearing nothing about this point, removed from the open item list,
then.
--
Michael