From b597cbb73e4326e2707114ea0ffa2d5a0bc68465 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Tue, 2 Jan 2024 23:55:02 +0100
Subject: [PATCH v1 1/7] pg_node_tree: Don't serialize fields with type-default
 values.

Often, the values in nodes are their default values. By not serializing
those fields and inserting the defaults during deserialization, we
reduce the size of pg_node_tree attributes seen in e.g. pg_rewrite by a
significant factor.

In passing, we fix a test that had a strict dependency on the
serialization of pg_node_tree; we now do the checks in a more generic
manner, making it more stable and ensuring its stability in future work.
---
 src/backend/nodes/outfuncs.c              | 101 +++++++++++++----
 src/backend/nodes/read.c                  |  45 ++++++++
 src/backend/nodes/readfuncs.c             | 129 +++++++++++++++++++---
 src/include/nodes/readfuncs.h             |   1 +
 src/test/regress/expected/rowsecurity.out |   5 +-
 src/test/regress/sql/rowsecurity.sql      |   5 +-
 6 files changed, 247 insertions(+), 39 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e66a99247e..29f4e43581 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -41,94 +41,157 @@ static void outDouble(StringInfo str, double d);
 	appendStringInfoString(str, nodelabel)
 
 /* Write an integer field (anything written as ":fldname %d") */
-#define WRITE_INT_FIELD(fldname) \
+#define WRITE_INT_FIELD_DIRECT(fldname) \
 	appendStringInfo(str, " :" CppAsString(fldname) " %d", node->fldname)
+#define WRITE_INT_FIELD_DEFAULT(fldname, default) \
+	((node->fldname == default) ? (0) : WRITE_INT_FIELD_DIRECT(fldname))
+#define WRITE_INT_FIELD(fldname) \
+	WRITE_INT_FIELD_DEFAULT(fldname, 0)
 
 /* Write an unsigned integer field (anything written as ":fldname %u") */
-#define WRITE_UINT_FIELD(fldname) \
+#define WRITE_UINT_FIELD_DIRECT(fldname) \
 	appendStringInfo(str, " :" CppAsString(fldname) " %u", node->fldname)
+#define WRITE_UINT_FIELD_DEFAULT(fldname, default) \
+	((node->fldname == default) ? (0) : WRITE_UINT_FIELD_DIRECT(fldname))
+#define WRITE_UINT_FIELD(fldname) \
+	WRITE_UINT_FIELD_DEFAULT(fldname, 0)
 
 /* Write an unsigned integer field (anything written with UINT64_FORMAT) */
-#define WRITE_UINT64_FIELD(fldname) \
+#define WRITE_UINT64_FIELD_DIRECT(fldname) \
 	appendStringInfo(str, " :" CppAsString(fldname) " " UINT64_FORMAT, \
 					 node->fldname)
+#define WRITE_UINT64_FIELD_DEFAULT(fldname, default) \
+	((node->fldname == default) ? (0) : WRITE_UINT64_FIELD_DIRECT(fldname))
+#define WRITE_UINT64_FIELD(fldname) \
+	WRITE_UINT64_FIELD_DEFAULT(fldname, 0)
 
 /* Write an OID field (don't hard-wire assumption that OID is same as uint) */
-#define WRITE_OID_FIELD(fldname) \
+#define WRITE_OID_FIELD_DIRECT(fldname) \
 	appendStringInfo(str, " :" CppAsString(fldname) " %u", node->fldname)
+#define WRITE_OID_FIELD_DEFAULT(fldname, default) \
+	((node->fldname == default) ? (0) : WRITE_OID_FIELD_DIRECT(fldname))
+#define WRITE_OID_FIELD(fldname) \
+	WRITE_OID_FIELD_DEFAULT(fldname, 0)
 
 /* Write a long-integer field */
-#define WRITE_LONG_FIELD(fldname) \
+#define WRITE_LONG_FIELD_DIRECT(fldname) \
 	appendStringInfo(str, " :" CppAsString(fldname) " %ld", node->fldname)
+#define WRITE_LONG_FIELD_DEFAULT(fldname, default) \
+	((node->fldname == default) ? (0) : WRITE_LONG_FIELD_DIRECT(fldname))
+#define WRITE_LONG_FIELD(fldname) \
+	WRITE_LONG_FIELD_DEFAULT(fldname, 0)
+
 
 /* Write a char field (ie, one ascii character) */
-#define WRITE_CHAR_FIELD(fldname) \
+#define WRITE_CHAR_FIELD_DIRECT(fldname) \
 	(appendStringInfo(str, " :" CppAsString(fldname) " "), \
 	 outChar(str, node->fldname))
+#define WRITE_CHAR_FIELD_DEFAULT(fldname, default) \
+	((node->fldname == default) ? (0) : WRITE_CHAR_FIELD_DIRECT(fldname))
+#define WRITE_CHAR_FIELD(fldname) \
+	WRITE_CHAR_FIELD_DEFAULT(fldname, '\0')
 
 /* Write an enumerated-type field as an integer code */
-#define WRITE_ENUM_FIELD(fldname, enumtype) \
+#define WRITE_ENUM_FIELD_DIRECT(fldname, enumtype) \
 	appendStringInfo(str, " :" CppAsString(fldname) " %d", \
 					 (int) node->fldname)
+#define WRITE_ENUM_FIELD_DEFAULT(fldname, enumtype, default) \
+	((node->fldname == default) ? (0) : WRITE_ENUM_FIELD_DIRECT(fldname, enumtype))
+#define WRITE_ENUM_FIELD(fldname, enumtype) \
+	WRITE_ENUM_FIELD_DEFAULT(fldname, enumtype, 0)
 
 /* Write a float field (actually, they're double) */
-#define WRITE_FLOAT_FIELD(fldname) \
+#define WRITE_FLOAT_FIELD_DIRECT(fldname) \
 	(appendStringInfo(str, " :" CppAsString(fldname) " "), \
 	 outDouble(str, node->fldname))
+#define WRITE_FLOAT_FIELD_DEFAULT(fldname, default) \
+	((node->fldname == default) ? (0) : WRITE_FLOAT_FIELD_DIRECT(fldname))
+#define WRITE_FLOAT_FIELD(fldname) \
+	WRITE_FLOAT_FIELD_DEFAULT(fldname, 0.0)
 
 /* Write a boolean field */
-#define WRITE_BOOL_FIELD(fldname) \
+#define WRITE_BOOL_FIELD_DIRECT(fldname) \
 	appendStringInfo(str, " :" CppAsString(fldname) " %s", \
 					 booltostr(node->fldname))
+#define WRITE_BOOL_FIELD_DEFAULT(fldname, default) \
+	((node->fldname == default) ? (0) : WRITE_BOOL_FIELD_DIRECT(fldname))
+#define WRITE_BOOL_FIELD(fldname) \
+	WRITE_BOOL_FIELD_DEFAULT(fldname, false)
+
+/*
+ * Non-null defaults of by-ref types are exceedingly rare (if not generally
+ * nonexistent), so we don't (yet) have a specialized macro for non-NULL
+ * defaults omission.
+ */
 
 /* Write a character-string (possibly NULL) field */
-#define WRITE_STRING_FIELD(fldname) \
+#define WRITE_STRING_FIELD_DIRECT(fldname) \
 	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
 	 outToken(str, node->fldname))
+#define WRITE_STRING_FIELD(fldname) \
+	((node->fldname == NULL) ? (0) : WRITE_STRING_FIELD_DIRECT(fldname))
 
 /* Write a parse location field (actually same as INT case) */
-#define WRITE_LOCATION_FIELD(fldname) \
+#define WRITE_LOCATION_FIELD_DIRECT(fldname) \
 	appendStringInfo(str, " :" CppAsString(fldname) " %d", node->fldname)
+#define WRITE_LOCATION_FIELD(fldname) \
+	(node->fldname == -1 ? (0) : WRITE_LOCATION_FIELD_DIRECT(fldname))
 
 /* Write a Node field */
-#define WRITE_NODE_FIELD(fldname) \
+#define WRITE_NODE_FIELD_DIRECT(fldname) \
 	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
 	 outNode(str, node->fldname))
+#define WRITE_NODE_FIELD(fldname) \
+	(node->fldname == NULL ? (0) : WRITE_NODE_FIELD_DIRECT(fldname))
 
 /* Write a bitmapset field */
-#define WRITE_BITMAPSET_FIELD(fldname) \
+#define WRITE_BITMAPSET_FIELD_DIRECT(fldname) \
 	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
 	 outBitmapset(str, node->fldname))
+#define WRITE_BITMAPSET_FIELD(fldname) \
+	(node->fldname == NULL ? (0) : WRITE_BITMAPSET_FIELD_DIRECT(fldname))
 
 /* Write a variable-length array (not a List) of Node pointers */
-#define WRITE_NODE_ARRAY(fldname, len) \
+#define WRITE_NODE_ARRAY_DIRECT(fldname, len) \
 	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
 	 writeNodeArray(str, (const Node * const *) node->fldname, len))
+#define WRITE_NODE_ARRAY(fldname, len) \
+	(node->fldname == NULL ? (0) : WRITE_NODE_ARRAY_DIRECT(fldname, len))
 
 /* Write a variable-length array of AttrNumber */
-#define WRITE_ATTRNUMBER_ARRAY(fldname, len) \
+#define WRITE_ATTRNUMBER_ARRAY_DIRECT(fldname, len) \
 	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
 	 writeAttrNumberCols(str, node->fldname, len))
+#define WRITE_ATTRNUMBER_ARRAY(fldname, len) \
+	(node->fldname == NULL ? (0) : WRITE_ATTRNUMBER_ARRAY_DIRECT(fldname, len))
 
 /* Write a variable-length array of Oid */
-#define WRITE_OID_ARRAY(fldname, len) \
+#define WRITE_OID_ARRAY_DIRECT(fldname, len) \
 	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
 	 writeOidCols(str, node->fldname, len))
+#define WRITE_OID_ARRAY(fldname, len) \
+	(node->fldname == NULL ? (0) : WRITE_OID_ARRAY_DIRECT(fldname, len))
 
 /* Write a variable-length array of Index */
-#define WRITE_INDEX_ARRAY(fldname, len) \
+#define WRITE_INDEX_ARRAY_DIRECT(fldname, len) \
 	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
 	 writeIndexCols(str, node->fldname, len))
+#define WRITE_INDEX_ARRAY(fldname, len) \
+	(node->fldname == NULL ? (0) : WRITE_INDEX_ARRAY_DIRECT(fldname, len))
 
 /* Write a variable-length array of int */
-#define WRITE_INT_ARRAY(fldname, len) \
+#define WRITE_INT_ARRAY_DIRECT(fldname, len) \
 	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
 	 writeIntCols(str, node->fldname, len))
+#define WRITE_INT_ARRAY(fldname, len) \
+	(node->fldname == NULL ? (0) : WRITE_INT_ARRAY_DIRECT(fldname, len))
 
 /* Write a variable-length array of bool */
-#define WRITE_BOOL_ARRAY(fldname, len) \
+#define WRITE_BOOL_ARRAY_DIRECT(fldname, len) \
 	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
 	 writeBoolCols(str, node->fldname, len))
+#define WRITE_BOOL_ARRAY(fldname, len) \
+	(node->fldname == NULL ? (0) : WRITE_BOOL_ARRAY_DIRECT(fldname, len))
 
 #define booltostr(x)  ((x) ? "true" : "false")
 
diff --git a/src/backend/nodes/read.c b/src/backend/nodes/read.c
index 813eda3e73..2815b268be 100644
--- a/src/backend/nodes/read.c
+++ b/src/backend/nodes/read.c
@@ -205,6 +205,51 @@ pg_strtok(int *length)
 	return ret_str;
 }
 
+/*
+ * Check if the next token is 'expect_token'.
+ *
+ * It handles similar to pg_strtok, except that this does not consume the
+ * next token, and has special casing for some less common tokens.
+ */
+bool
+pg_strtoken_next(const char *expect_token)
+{
+	const char *local_str;		/* working pointer to string */
+	Size expect_len = strlen(expect_token);
+	char next_char;
+
+	local_str = pg_strtok_ptr;
+
+	while (*local_str == ' ' || *local_str == '\n' || *local_str == '\t')
+		local_str++;
+
+	if (*local_str == '\0')
+		return false;			/* no more tokens */
+
+	Assert(expect_len > 0);
+
+	next_char = local_str[expect_len];
+
+	/* check if the next few bytes match the token */
+	if (strncmp(local_str, expect_token, expect_len) != 0)
+		return false;
+
+	/*
+	 * Check that the token was actually terminated at the end of the
+	 * expected token with a character that is a separate token.
+	 * Otherwise, we'd get positive matches for mathing the token of "is"
+	 * against a local_str of "isn't", which is clearly wrong.
+	 */
+	return (next_char == '\0' ||
+			next_char == ' ' ||
+			next_char == '\n' ||
+			next_char == '\t' ||
+			next_char == '(' ||
+			next_char == ')' ||
+			next_char == '{' ||
+			next_char == '}');
+}
+
 /*
  * debackslash -
  *	  create a palloc'd string holding the given token.
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index cc2021c1f7..74a2204389 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -56,112 +56,205 @@
 	READ_LOCALS_NO_FIELDS(nodeTypeName);	\
 	READ_TEMP_LOCALS()
 
+/* a scaffold function to read an optionally-omitted field */
+#define READ_OPT_SCAFFOLD(fldname, read_field_code, default_value) \
+	if (pg_strtoken_next(":" CppAsString(fldname))) \
+	{ \
+		read_field_code; \
+	} \
+	else \
+		local_node->fldname = default_value
+
 /* Read an integer field (anything written as ":fldname %d") */
-#define READ_INT_FIELD(fldname) \
+#define READ_INT_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	local_node->fldname = atoi(token)
+#define READ_INT_FIELD_DEFAULT(fldname, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_INT_FIELD_DIRECT(fldname), default_value)
+#define READ_INT_FIELD(fldname) \
+	READ_INT_FIELD_DEFAULT(fldname, 0)
 
 /* Read an unsigned integer field (anything written as ":fldname %u") */
-#define READ_UINT_FIELD(fldname) \
+#define READ_UINT_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	local_node->fldname = atoui(token)
+#define READ_UINT_FIELD_DEFAULT(fldname, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_UINT_FIELD_DIRECT(fldname), default_value)
+#define READ_UINT_FIELD(fldname) \
+	READ_UINT_FIELD_DEFAULT(fldname, 0)
 
 /* Read an unsigned integer field (anything written using UINT64_FORMAT) */
-#define READ_UINT64_FIELD(fldname) \
+#define READ_UINT64_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	local_node->fldname = strtou64(token, NULL, 10)
+#define READ_UINT64_FIELD_DEFAULT(fldname, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_UINT64_FIELD_DIRECT(fldname), default_value)
+#define READ_UINT64_FIELD(fldname) \
+	READ_UINT64_FIELD_DEFAULT(fldname, 0)
 
 /* Read a long integer field (anything written as ":fldname %ld") */
-#define READ_LONG_FIELD(fldname) \
+#define READ_LONG_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	local_node->fldname = atol(token)
+#define READ_LONG_FIELD_DEFAULT(fldname, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_LONG_FIELD_DIRECT(fldname), default_value)
+#define READ_LONG_FIELD(fldname) \
+	READ_LONG_FIELD_DEFAULT(fldname, 0)
 
 /* Read an OID field (don't hard-wire assumption that OID is same as uint) */
-#define READ_OID_FIELD(fldname) \
+#define READ_OID_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	local_node->fldname = atooid(token)
+#define READ_OID_FIELD_DEFAULT(fldname, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_OID_FIELD_DIRECT(fldname), default_value)
+#define READ_OID_FIELD(fldname) \
+	READ_OID_FIELD_DEFAULT(fldname, 0)
 
 /* Read a char field (ie, one ascii character) */
-#define READ_CHAR_FIELD(fldname) \
+#define READ_CHAR_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	/* avoid overhead of calling debackslash() for one char */ \
 	local_node->fldname = (length == 0) ? '\0' : (token[0] == '\\' ? token[1] : token[0])
+#define READ_CHAR_FIELD_DEFAULT(fldname, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_CHAR_FIELD_DIRECT(fldname), default_value)
+#define READ_CHAR_FIELD(fldname) \
+	READ_CHAR_FIELD_DEFAULT(fldname, '\0')
 
 /* Read an enumerated-type field that was written as an integer code */
-#define READ_ENUM_FIELD(fldname, enumtype) \
+#define READ_ENUM_FIELD_DIRECT(fldname, enumtype) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	local_node->fldname = (enumtype) atoi(token)
+#define READ_ENUM_FIELD_DEFAULT(fldname, enumtype, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_ENUM_FIELD_DIRECT(fldname, enumtype), default_value)
+#define READ_ENUM_FIELD(fldname, enumtype) \
+	READ_ENUM_FIELD_DEFAULT(fldname, enumtype, 0)
 
 /* Read a float field */
-#define READ_FLOAT_FIELD(fldname) \
+#define READ_FLOAT_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	local_node->fldname = atof(token)
+#define READ_FLOAT_FIELD_DEFAULT(fldname, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_FLOAT_FIELD_DIRECT(fldname), default_value)
+#define READ_FLOAT_FIELD(fldname) \
+	READ_FLOAT_FIELD_DEFAULT(fldname, 0.0)
 
 /* Read a boolean field */
-#define READ_BOOL_FIELD(fldname) \
+#define READ_BOOL_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	local_node->fldname = strtobool(token)
+#define READ_BOOL_FIELD_DEFAULT(fldname, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_BOOL_FIELD_DIRECT(fldname), default_value)
+#define READ_BOOL_FIELD(fldname) \
+	READ_BOOL_FIELD_DEFAULT(fldname, false)
 
 /* Read a character-string field */
-#define READ_STRING_FIELD(fldname) \
+#define READ_STRING_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	local_node->fldname = nullable_string(token, length)
+/* see WRITE_STRING_FIELD in outfuncs.c */
+#define READ_STRING_FIELD(fldname) \
+	READ_OPT_SCAFFOLD(fldname, READ_STRING_FIELD_DIRECT(fldname), NULL)
 
 /* Read a parse location field (and possibly throw away the value) */
 #ifdef WRITE_READ_PARSE_PLAN_TREES
-#define READ_LOCATION_FIELD(fldname) \
+#define READ_LOCATION_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	local_node->fldname = restore_location_fields ? atoi(token) : -1
 #else
-#define READ_LOCATION_FIELD(fldname) \
+#define READ_LOCATION_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	token = pg_strtok(&length);		/* get field value */ \
 	(void) token;				/* in case not used elsewhere */ \
 	local_node->fldname = -1	/* set field to "unknown" */
 #endif
+/* The default Location field value is -1 ('unknown') */
+#define READ_LOCATION_FIELD(fldname) \
+	READ_OPT_SCAFFOLD(fldname, READ_LOCATION_FIELD_DIRECT(fldname), -1)
 
 /* Read a Node field */
-#define READ_NODE_FIELD(fldname) \
+#define READ_NODE_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	(void) token;				/* in case not used elsewhere */ \
 	local_node->fldname = nodeRead(NULL, 0)
+#define READ_NODE_FIELD_DEFAULT(fldname, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_NODE_FIELD_DIRECT(fldname), default_value)
+#define READ_NODE_FIELD(fldname) \
+	READ_NODE_FIELD_DEFAULT(fldname, NULL)
 
 /* Read a bitmapset field */
-#define READ_BITMAPSET_FIELD(fldname) \
+#define READ_BITMAPSET_FIELD_DIRECT(fldname) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	(void) token;				/* in case not used elsewhere */ \
 	local_node->fldname = _readBitmapset()
+#define READ_BITMAPSET_FIELD_DEFAULT(fldname, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_BITMAPSET_FIELD_DIRECT(fldname), default_value)
+#define READ_BITMAPSET_FIELD(fldname) \
+	READ_BITMAPSET_FIELD_DEFAULT(fldname, NULL)
 
 /* Read an attribute number array */
-#define READ_ATTRNUMBER_ARRAY(fldname, len) \
+#define READ_ATTRNUMBER_ARRAY_DIRECT(fldname, len) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	local_node->fldname = readAttrNumberCols(len)
+#define READ_ATTRNUMBER_ARRAY_DEFAULT(fldname, len, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_ATTRNUMBER_ARRAY_DIRECT(fldname, len), default_value)
+#define READ_ATTRNUMBER_ARRAY(fldname, len) \
+	READ_ATTRNUMBER_ARRAY_DEFAULT(fldname, len, NULL)
 
 /* Read an oid array */
-#define READ_OID_ARRAY(fldname, len) \
+#define READ_OID_ARRAY_DIRECT(fldname, len) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	local_node->fldname = readOidCols(len)
+#define READ_OID_ARRAY_DEFAULT(fldname, len, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_OID_ARRAY_DIRECT(fldname, len), default_value)
+#define READ_OID_ARRAY(fldname, len) \
+	READ_OID_ARRAY_DEFAULT(fldname, len, NULL)
 
 /* Read an int array */
-#define READ_INT_ARRAY(fldname, len) \
+#define READ_INT_ARRAY_DIRECT(fldname, len) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	local_node->fldname = readIntCols(len)
+#define READ_INT_ARRAY_DEFAULT(fldname, len, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_INT_ARRAY_DIRECT(fldname, len), default_value)
+#define READ_INT_ARRAY(fldname, len) \
+	READ_INT_ARRAY_DEFAULT(fldname, len, NULL)
 
 /* Read a bool array */
-#define READ_BOOL_ARRAY(fldname, len) \
+#define READ_BOOL_ARRAY_DIRECT(fldname, len) \
 	token = pg_strtok(&length);		/* skip :fldname */ \
+	Assert(strncmp(token, ":" CppAsString(fldname), length) == 0); \
 	local_node->fldname = readBoolCols(len)
+#define READ_BOOL_ARRAY_DEFAULT(fldname, len, default_value) \
+	READ_OPT_SCAFFOLD(fldname, READ_BOOL_ARRAY_DIRECT(fldname, len), default_value)
+#define READ_BOOL_ARRAY(fldname, len) \
+	READ_BOOL_ARRAY_DEFAULT(fldname, len, NULL)
 
 /* Routine exit */
 #define READ_DONE() \
diff --git a/src/include/nodes/readfuncs.h b/src/include/nodes/readfuncs.h
index cba6f0be75..e668b24ad0 100644
--- a/src/include/nodes/readfuncs.h
+++ b/src/include/nodes/readfuncs.h
@@ -27,6 +27,7 @@ extern PGDLLIMPORT bool restore_location_fields;
  * prototypes for functions in read.c (the lisp token parser)
  */
 extern const char *pg_strtok(int *length);
+extern bool pg_strtoken_next(const char *expect_token);
 extern char *debackslash(const char *token, int length);
 extern void *nodeRead(const char *token, int tok_len);
 
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 6988128aa4..a69aa40f82 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -3937,7 +3937,10 @@ CREATE TABLE coll_t (c) AS VALUES ('bar'::text);
 CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C"));
 ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;
 GRANT SELECT ON coll_t TO regress_rls_alice;
-SELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;
+SELECT split AS inputcollid
+FROM pg_policy,
+     lateral unnest(string_to_array(polqual, ':')) as split
+WHERE polrelid = 'coll_t'::regclass and split LIKE '%inputcollid%';
    inputcollid    
 ------------------
  inputcollid 950 
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index dec7340538..cb7a4c776b 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -1732,7 +1732,10 @@ CREATE TABLE coll_t (c) AS VALUES ('bar'::text);
 CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C"));
 ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;
 GRANT SELECT ON coll_t TO regress_rls_alice;
-SELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;
+SELECT split AS inputcollid
+FROM pg_policy,
+     lateral unnest(string_to_array(polqual, ':')) as split
+WHERE polrelid = 'coll_t'::regclass and split LIKE '%inputcollid%';
 SET SESSION AUTHORIZATION regress_rls_alice;
 SELECT * FROM coll_t;
 ROLLBACK;
-- 
2.40.1

