On 13.07.22 00:38, Tom Lane wrote:
Peter Eisentraut <peter.eisentr...@enterprisedb.com> writes:
This is also needed to be able to store utility statements in (unquoted)
SQL function bodies.  I have some in-progress code for that that I need
to dust off.  IIRC, there are still some nontrivial issues to work
through on the reading side.  I don't have a problem with enabling the
outfuncs side in the meantime.

BTW, I experimented with trying to enable WRITE_READ_PARSE_PLAN_TREES
for utility statements, and found that the immediate problem is that
Constraint and a couple of other node types lack read functions
(they're the ones marked "custom_read_write, no_read" in parsenodes.h).
They have out functions, so writing the inverses seems like it's just
something nobody ever got around to.  Perhaps there are deeper problems
lurking behind that one, though.

Here are patches for that.

v1-0001-Fix-reading-of-most-negative-integer-value-nodes.patch
v1-0002-Fix-reading-of-BitString-nodes.patch

These are some of those lurking problems.

v1-0003-Add-read-support-for-some-missing-raw-parse-nodes.patch

This adds the read support for the missing nodes.

The above patches are candidates for committing.

At this point we have one structural problem left: char * node fields output with WRITE_STRING_FIELD() (ultimately outToken()) don't distinguish between empty strings and NULL values. A write/read roundtrip ends up as NULL for an empty string. This shows up in the regression tests for commands such as

CREATE TABLESPACE regress_tblspace LOCATION '';
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' ...

This will need some expansion of the output format to handle this.

v1-0004-XXX-Turn-on-WRITE_READ_PARSE_PLAN_TREES-for-testi.patch
v1-0005-Implement-WRITE_READ_PARSE_PLAN_TREES-for-raw-par.patch
v1-0006-Enable-WRITE_READ_PARSE_PLAN_TREES-of-rewritten-u.patch

This is for testing the above. Note that in 0005 we need some special handling for float values to preserve the full precision across write/read. I suppose this could be unified with the code the preserves the location fields when doing write/read checking.

v1-0007-Enable-utility-statements-in-unquoted-SQL-functio.patch

This demonstrates what the ultimate goal is. A few more tests should be added eventually.
From 89def573ced57fc320ad191613dac62c8992c27a Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Fri, 12 Aug 2022 21:15:40 +0200
Subject: [PATCH v1 1/7] Fix reading of most-negative integer value nodes

The main parser checks whether a literal fits into an int when
deciding whether it should be put into an Integer or Float node.  The
parser processes integer literals without signs.  So a most-negative
integer literal will not fit into Integer and will end up as a Float
node.

The node tokenizer did this differently.  It included the sign when
checking whether the literal fit into int.  So a most-negative integer
would indeed fit that way and end up as an Integer node.

In order to preserve the node structure correctly, we need the node
tokenizer to also analyze integer literals without sign.

There are a number of test cases in the regression tests that have a
most-negative integer argument of some utility statement, so this
issue is easily reproduced under WRITE_READ_PARSE_PLAN_TREES.
---
 src/backend/nodes/read.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/nodes/read.c b/src/backend/nodes/read.c
index 4a54996b63..a9cb81b129 100644
--- a/src/backend/nodes/read.c
+++ b/src/backend/nodes/read.c
@@ -267,7 +267,7 @@ nodeTokenType(const char *token, int length)
                char       *endptr;
 
                errno = 0;
-               (void) strtoint(token, &endptr, 10);
+               (void) strtoint(numptr, &endptr, 10);
                if (endptr != token + length || errno == ERANGE)
                        return T_Float;
                return T_Integer;
-- 
2.37.1

From 8a469d0a7195be4ecc65155b82c5836dfb13b920 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Fri, 12 Aug 2022 21:16:26 +0200
Subject: [PATCH v1 2/7] Fix reading of BitString nodes

The node tokenizer went out of its way to store BitString node values
without the leading 'b'.  But everything else in the system stores the
leading 'b'.  This would break if a BitString node is
read-printed-read.

Also, the node tokenizer didn't know that BitString node tokens could
also start with 'x'.
---
 src/backend/nodes/read.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/backend/nodes/read.c b/src/backend/nodes/read.c
index a9cb81b129..fe84f140ee 100644
--- a/src/backend/nodes/read.c
+++ b/src/backend/nodes/read.c
@@ -288,7 +288,7 @@ nodeTokenType(const char *token, int length)
                retval = T_Boolean;
        else if (*token == '"' && length > 1 && token[length - 1] == '"')
                retval = T_String;
-       else if (*token == 'b')
+       else if (*token == 'b' || *token == 'x')
                retval = T_BitString;
        else
                retval = OTHER_TOKEN;
@@ -471,11 +471,10 @@ nodeRead(const char *token, int tok_len)
                        break;
                case T_BitString:
                        {
-                               char       *val = palloc(tok_len);
+                               char       *val = palloc(tok_len + 1);
 
-                               /* skip leading 'b' */
-                               memcpy(val, token + 1, tok_len - 1);
-                               val[tok_len - 1] = '\0';
+                               memcpy(val, token, tok_len);
+                               val[tok_len] = '\0';
                                result = (Node *) makeBitString(val);
                                break;
                        }
-- 
2.37.1

From aa730a9d98232517fda500d94311e71bec7beaef Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Fri, 12 Aug 2022 20:10:30 +0200
Subject: [PATCH v1 3/7] Add read support for some missing raw parse nodes

The node types A_Const, Constraint, and A_Expr had custom output
functions, but no read functions were implemented so far.

The A_Expr output format had to be tweaked a bit to make it easier to
parse.

Also error out if an unrecognized enum value is found in each case,
instead of just printing a placeholder value.  That was maybe ok for
debugging but won't work if we want to have robust round-tripping.
---
 src/backend/nodes/outfuncs.c   |   9 +-
 src/backend/nodes/readfuncs.c  | 247 +++++++++++++++++++++++++++++++++
 src/include/nodes/parsenodes.h |   6 +-
 3 files changed, 254 insertions(+), 8 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 60610e3a4b..24ea0487e7 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -548,12 +548,12 @@ _outA_Expr(StringInfo str, const A_Expr *node)
                        WRITE_NODE_FIELD(name);
                        break;
                case AEXPR_OP_ANY:
-                       WRITE_NODE_FIELD(name);
                        appendStringInfoString(str, " ANY");
+                       WRITE_NODE_FIELD(name);
                        break;
                case AEXPR_OP_ALL:
-                       WRITE_NODE_FIELD(name);
                        appendStringInfoString(str, " ALL");
+                       WRITE_NODE_FIELD(name);
                        break;
                case AEXPR_DISTINCT:
                        appendStringInfoString(str, " DISTINCT");
@@ -600,7 +600,7 @@ _outA_Expr(StringInfo str, const A_Expr *node)
                        WRITE_NODE_FIELD(name);
                        break;
                default:
-                       appendStringInfoString(str, " ??");
+                       elog(ERROR, "unrecognized A_Expr_Kind: %d", (int) 
node->kind);
                        break;
        }
 
@@ -782,8 +782,7 @@ _outConstraint(StringInfo str, const Constraint *node)
                        break;
 
                default:
-                       appendStringInfo(str, "<unrecognized_constraint %d>",
-                                                        (int) node->contype);
+                       elog(ERROR, "unrecognized ConstrType: %d", (int) 
node->contype);
                        break;
        }
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index bee62fc15c..93049cebb8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -285,6 +285,162 @@ _readBoolExpr(void)
        READ_DONE();
 }
 
+static A_Const *
+_readA_Const(void)
+{
+       READ_LOCALS(A_Const);
+
+       token = pg_strtok(&length);
+       if (strncmp(token, "NULL", 4) == 0)
+               local_node->isnull = true;
+       else
+       {
+               union ValUnion *tmp = nodeRead(NULL, 0);
+
+               memcpy(&local_node->val, tmp, sizeof(*tmp));
+       }
+
+       READ_LOCATION_FIELD(location);
+
+       READ_DONE();
+}
+
+/*
+ * _readConstraint
+ */
+static Constraint *
+_readConstraint(void)
+{
+       READ_LOCALS(Constraint);
+
+       READ_STRING_FIELD(conname);
+       READ_BOOL_FIELD(deferrable);
+       READ_BOOL_FIELD(initdeferred);
+       READ_LOCATION_FIELD(location);
+
+       token = pg_strtok(&length);             /* skip :contype */
+       token = pg_strtok(&length);             /* get field value */
+       if (strncmp(token, "NULL", 4) == 0)
+               local_node->contype = CONSTR_NULL;
+       else if (strncmp(token, "NOT_NULL", 8) == 0)
+               local_node->contype = CONSTR_NOTNULL;
+       else if (strncmp(token, "DEFAULT", 7) == 0)
+               local_node->contype = CONSTR_DEFAULT;
+       else if (strncmp(token, "IDENTITY", 8) == 0)
+               local_node->contype = CONSTR_IDENTITY;
+       else if (strncmp(token, "GENERATED", 9) == 0)
+               local_node->contype = CONSTR_GENERATED;
+       else if (strncmp(token, "CHECK", 5) == 0)
+               local_node->contype = CONSTR_CHECK;
+       else if (strncmp(token, "PRIMARY_KEY", 11) == 0)
+               local_node->contype = CONSTR_PRIMARY;
+       else if (strncmp(token, "UNIQUE", 6) == 0)
+               local_node->contype = CONSTR_UNIQUE;
+       else if (strncmp(token, "EXCLUSION", 9) == 0)
+               local_node->contype = CONSTR_EXCLUSION;
+       else if (strncmp(token, "FOREIGN_KEY", 11) == 0)
+               local_node->contype = CONSTR_FOREIGN;
+       else if (strncmp(token, "ATTR_DEFERRABLE", 15) == 0)
+               local_node->contype = CONSTR_ATTR_DEFERRABLE;
+       else if (strncmp(token, "ATTR_NOT_DEFERRABLE", 19) == 0)
+               local_node->contype = CONSTR_ATTR_NOT_DEFERRABLE;
+       else if (strncmp(token, "ATTR_DEFERRED", 13) == 0)
+               local_node->contype = CONSTR_ATTR_DEFERRED;
+       else if (strncmp(token, "ATTR_IMMEDIATE", 14) == 0)
+               local_node->contype = CONSTR_ATTR_IMMEDIATE;
+
+       switch (local_node->contype)
+       {
+               case CONSTR_NULL:
+               case CONSTR_NOTNULL:
+                       /* no extra fields */
+                       break;
+
+               case CONSTR_DEFAULT:
+                       READ_NODE_FIELD(raw_expr);
+                       READ_STRING_FIELD(cooked_expr);
+                       break;
+
+               case CONSTR_IDENTITY:
+                       READ_NODE_FIELD(options);
+                       READ_CHAR_FIELD(generated_when);
+                       break;
+
+               case CONSTR_GENERATED:
+                       READ_NODE_FIELD(raw_expr);
+                       READ_STRING_FIELD(cooked_expr);
+                       READ_CHAR_FIELD(generated_when);
+                       break;
+
+               case CONSTR_CHECK:
+                       READ_BOOL_FIELD(is_no_inherit);
+                       READ_NODE_FIELD(raw_expr);
+                       READ_STRING_FIELD(cooked_expr);
+                       READ_BOOL_FIELD(skip_validation);
+                       READ_BOOL_FIELD(initially_valid);
+                       break;
+
+               case CONSTR_PRIMARY:
+                       READ_NODE_FIELD(keys);
+                       READ_NODE_FIELD(including);
+                       READ_NODE_FIELD(options);
+                       READ_STRING_FIELD(indexname);
+                       READ_STRING_FIELD(indexspace);
+                       READ_BOOL_FIELD(reset_default_tblspc);
+                       /* access_method and where_clause not currently used */
+                       break;
+
+               case CONSTR_UNIQUE:
+                       READ_BOOL_FIELD(nulls_not_distinct);
+                       READ_NODE_FIELD(keys);
+                       READ_NODE_FIELD(including);
+                       READ_NODE_FIELD(options);
+                       READ_STRING_FIELD(indexname);
+                       READ_STRING_FIELD(indexspace);
+                       READ_BOOL_FIELD(reset_default_tblspc);
+                       /* access_method and where_clause not currently used */
+                       break;
+
+               case CONSTR_EXCLUSION:
+                       READ_NODE_FIELD(exclusions);
+                       READ_NODE_FIELD(including);
+                       READ_NODE_FIELD(options);
+                       READ_STRING_FIELD(indexname);
+                       READ_STRING_FIELD(indexspace);
+                       READ_BOOL_FIELD(reset_default_tblspc);
+                       READ_STRING_FIELD(access_method);
+                       READ_NODE_FIELD(where_clause);
+                       break;
+
+               case CONSTR_FOREIGN:
+                       READ_NODE_FIELD(pktable);
+                       READ_NODE_FIELD(fk_attrs);
+                       READ_NODE_FIELD(pk_attrs);
+                       READ_CHAR_FIELD(fk_matchtype);
+                       READ_CHAR_FIELD(fk_upd_action);
+                       READ_CHAR_FIELD(fk_del_action);
+                       READ_NODE_FIELD(fk_del_set_cols);
+                       READ_NODE_FIELD(old_conpfeqop);
+                       READ_OID_FIELD(old_pktable_oid);
+                       READ_BOOL_FIELD(skip_validation);
+                       READ_BOOL_FIELD(initially_valid);
+                       break;
+
+               case CONSTR_ATTR_DEFERRABLE:
+               case CONSTR_ATTR_NOT_DEFERRABLE:
+               case CONSTR_ATTR_DEFERRED:
+               case CONSTR_ATTR_IMMEDIATE:
+                       /* no extra fields */
+                       break;
+
+               default:
+                       elog(ERROR, "unrecognized ConstrType: %d", (int) 
local_node->contype);
+                       break;
+       }
+
+       READ_DONE();
+}
+
 static RangeTblEntry *
 _readRangeTblEntry(void)
 {
@@ -376,6 +532,97 @@ _readRangeTblEntry(void)
        READ_DONE();
 }
 
+static A_Expr *
+_readA_Expr(void)
+{
+       READ_LOCALS(A_Expr);
+
+       token = pg_strtok(&length);
+
+       if (strncmp(token, "ANY", 3) == 0)
+       {
+               local_node->kind = AEXPR_OP_ANY;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "ALL", 3) == 0)
+       {
+               local_node->kind = AEXPR_OP_ALL;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "DISTINCT", 8) == 0)
+       {
+               local_node->kind = AEXPR_DISTINCT;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "NOT_DISTINCT", 12) == 0)
+       {
+               local_node->kind = AEXPR_NOT_DISTINCT;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "NULLIF", 6) == 0)
+       {
+               local_node->kind = AEXPR_NULLIF;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "IN", 2) == 0)
+       {
+               local_node->kind = AEXPR_IN;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "LIKE", 4) == 0)
+       {
+               local_node->kind = AEXPR_LIKE;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "ILIKE", 5) == 0)
+       {
+               local_node->kind = AEXPR_ILIKE;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "SIMILAR", 7) == 0)
+       {
+               local_node->kind = AEXPR_SIMILAR;
+               READ_NODE_FIELD(name);
+       }
+       /*
+        * Note: Order matters, strings that are prefixes of other strings must
+        * come later.
+        */
+       else if (strncmp(token, "BETWEEN_SYM", 11) == 0)
+       {
+               local_node->kind = AEXPR_BETWEEN_SYM;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "NOT_BETWEEN_SYM", 15) == 0)
+       {
+               local_node->kind = AEXPR_NOT_BETWEEN_SYM;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "BETWEEN", 7) == 0)
+       {
+               local_node->kind = AEXPR_BETWEEN;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, "NOT_BETWEEN", 11) == 0)
+       {
+               local_node->kind = AEXPR_NOT_BETWEEN;
+               READ_NODE_FIELD(name);
+       }
+       else if (strncmp(token, ":name", 5) == 0)
+       {
+               local_node->kind = AEXPR_OP;
+               local_node->name = nodeRead(NULL, 0);
+       }
+       else
+               elog(ERROR, "unrecognized A_Expr kind: %s", token);
+
+       READ_NODE_FIELD(lexpr);
+       READ_NODE_FIELD(rexpr);
+       READ_LOCATION_FIELD(location);
+
+       READ_DONE();
+}
+
 static ExtensibleNode *
 _readExtensibleNode(void)
 {
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b376031856..fd308af4e5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -291,7 +291,7 @@ typedef enum A_Expr_Kind
 
 typedef struct A_Expr
 {
-       pg_node_attr(custom_read_write, no_read)
+       pg_node_attr(custom_read_write)
 
        NodeTag         type;
        A_Expr_Kind kind;                       /* see above */
@@ -319,7 +319,7 @@ union ValUnion
 
 typedef struct A_Const
 {
-       pg_node_attr(custom_copy_equal, custom_read_write, no_read)
+       pg_node_attr(custom_copy_equal, custom_read_write)
 
        NodeTag         type;
        union ValUnion val;
@@ -2619,7 +2619,7 @@ typedef enum ConstrType                   /* types of 
constraints */
 
 typedef struct Constraint
 {
-       pg_node_attr(custom_read_write, no_read)
+       pg_node_attr(custom_read_write)
 
        NodeTag         type;
        ConstrType      contype;                /* see above */
-- 
2.37.1

From 6479e315f700bc65aa8cbe66de07c8fdffd0d8eb Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Wed, 10 Aug 2022 23:37:31 +0200
Subject: [PATCH v1 4/7] XXX: Turn on WRITE_READ_PARSE_PLAN_TREES for testing

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

diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index f2a106f983..f85a7a7312 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -334,7 +334,7 @@
  * outfuncs.c/readfuncs.c, to facilitate catching errors and omissions in
  * those modules.
  */
-/* #define WRITE_READ_PARSE_PLAN_TREES */
+#define WRITE_READ_PARSE_PLAN_TREES
 
 /*
  * Define this to force all raw parse trees for DML statements to be scanned
-- 
2.37.1

From 26edadde9996f6fe266b7eaf77ad202162aea26f Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Wed, 10 Aug 2022 23:40:52 +0200
Subject: [PATCH v1 5/7] Implement WRITE_READ_PARSE_PLAN_TREES for raw parse
 trees

This requires a special node to print and restore the full precision
of float values.
---
 src/backend/nodes/outfuncs.c | 14 ++++++++++++++
 src/backend/tcop/postgres.c  | 26 +++++++++++++++++++++-----
 src/include/nodes/nodes.h    |  4 ++++
 3 files changed, 39 insertions(+), 5 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 24ea0487e7..acc4738581 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include <ctype.h>
+#include <float.h>
 
 #include "access/attnum.h"
 #include "lib/stringinfo.h"
@@ -26,6 +27,9 @@
 
 static void outChar(StringInfo str, char c);
 
+#ifdef WRITE_READ_PARSE_PLAN_TREES
+bool output_all_float_digits = false;
+#endif
 
 /*
  * Macros to simplify output of different kinds of fields.  Use these
@@ -70,8 +74,18 @@ static void outChar(StringInfo str, char c);
                                         (int) node->fldname)
 
 /* Write a float field --- caller must give format to define precision */
+#ifdef WRITE_READ_PARSE_PLAN_TREES
+#define WRITE_FLOAT_FIELD(fldname,format) \
+       do { \
+               if (output_all_float_digits) \
+                       appendStringInfo(str, " :" CppAsString(fldname) " 
%.*f", DBL_DIG + 3, node->fldname); \
+               else \
+                       appendStringInfo(str, " :" CppAsString(fldname) " " 
format, node->fldname); \
+       } while(0)
+#else
 #define WRITE_FLOAT_FIELD(fldname,format) \
        appendStringInfo(str, " :" CppAsString(fldname) " " format, 
node->fldname)
+#endif
 
 /* Write a boolean field */
 #define WRITE_BOOL_FIELD(fldname) \
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7bec4e4ff5..154e6dbeca 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -601,11 +601,27 @@ pg_parse_query(const char *query_string)
        }
 #endif
 
-       /*
-        * Currently, outfuncs/readfuncs support is missing for many raw parse
-        * tree nodes, so we don't try to implement WRITE_READ_PARSE_PLAN_TREES
-        * here.
-        */
+#ifdef WRITE_READ_PARSE_PLAN_TREES
+       /* Optional debugging check: pass raw parsetrees through 
outfuncs/readfuncs */
+       {
+               char       *str;
+               List       *new_list;
+
+               output_all_float_digits = true;
+
+               str = nodeToString(raw_parsetree_list);
+               new_list = stringToNodeWithLocations(str);
+               pfree(str);
+
+               output_all_float_digits = false;
+
+               /* This checks both outfuncs/readfuncs and the equal() 
routines... */
+               if (!equal(new_list, raw_parsetree_list))
+                       elog(WARNING, "outfuncs/readfuncs failed to produce an 
equal raw parse tree");
+               else
+                       raw_parsetree_list = new_list;
+       }
+#endif
 
        TRACE_POSTGRESQL_QUERY_PARSE_DONE(query_string);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cdd6debfa0..6d6478bcb4 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -192,6 +192,10 @@ castNodeImpl(NodeTag type, void *ptr)
 struct Bitmapset;                              /* not to include bitmapset.h 
here */
 struct StringInfoData;                 /* not to include stringinfo.h here */
 
+#ifdef WRITE_READ_PARSE_PLAN_TREES
+extern bool output_all_float_digits;
+#endif
+
 extern void outNode(struct StringInfoData *str, const void *obj);
 extern void outToken(struct StringInfoData *str, const char *s);
 extern void outBitmapset(struct StringInfoData *str,
-- 
2.37.1

From 254ece76b734553de544cbaa93c5a4ede0e587f5 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Wed, 10 Aug 2022 23:41:35 +0200
Subject: [PATCH v1 6/7] Enable WRITE_READ_PARSE_PLAN_TREES of rewritten
 utility statements

This was previously disabled because we lacked outfuncs/readfuncs
support for most utility statement types.
---
 src/backend/tcop/postgres.c | 32 +++++++++++---------------------
 1 file changed, 11 insertions(+), 21 deletions(-)

diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 154e6dbeca..58d2139dc9 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -804,7 +804,7 @@ pg_rewrite_query(Query *query)
                new_list = copyObject(querytree_list);
                /* This checks both copyObject() and the equal() routines... */
                if (!equal(new_list, querytree_list))
-                       elog(WARNING, "copyObject() failed to produce equal 
parse tree");
+                       elog(WARNING, "copyObject() failed to produce an equal 
rewritten parse tree");
                else
                        querytree_list = new_list;
        }
@@ -816,35 +816,25 @@ pg_rewrite_query(Query *query)
                List       *new_list = NIL;
                ListCell   *lc;
 
-               /*
-                * We currently lack outfuncs/readfuncs support for most utility
-                * statement types, so only attempt to write/read non-utility 
queries.
-                */
                foreach(lc, querytree_list)
                {
                        Query      *query = lfirst_node(Query, lc);
+                       char       *str = nodeToString(query);
+                       Query      *new_query = stringToNodeWithLocations(str);
 
-                       if (query->commandType != CMD_UTILITY)
-                       {
-                               char       *str = nodeToString(query);
-                               Query      *new_query = 
stringToNodeWithLocations(str);
-
-                               /*
-                                * queryId is not saved in stored rules, but we 
must preserve
-                                * it here to avoid breaking pg_stat_statements.
-                                */
-                               new_query->queryId = query->queryId;
+                       /*
+                        * queryId is not saved in stored rules, but we must 
preserve
+                        * it here to avoid breaking pg_stat_statements.
+                        */
+                       new_query->queryId = query->queryId;
 
-                               new_list = lappend(new_list, new_query);
-                               pfree(str);
-                       }
-                       else
-                               new_list = lappend(new_list, query);
+                       new_list = lappend(new_list, new_query);
+                       pfree(str);
                }
 
                /* This checks both outfuncs/readfuncs and the equal() 
routines... */
                if (!equal(new_list, querytree_list))
-                       elog(WARNING, "outfuncs/readfuncs failed to produce 
equal parse tree");
+                       elog(WARNING, "outfuncs/readfuncs failed to produce an 
equal rewritten parse tree");
                else
                        querytree_list = new_list;
        }
-- 
2.37.1

From 891c34fd8a1d54e06578ec22b186eafa52ee0f97 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Fri, 12 Aug 2022 20:18:16 +0200
Subject: [PATCH v1 7/7] Enable utility statements in unquoted SQL function
 body

This should now work, since we have full serialization support for
utility statements.

XXX actually not yet
---
 src/backend/commands/functioncmds.c            | 10 ----------
 src/test/regress/expected/create_procedure.out | 11 ++++++++---
 src/test/regress/sql/create_procedure.sql      |  8 ++++++--
 3 files changed, 14 insertions(+), 15 deletions(-)

diff --git a/src/backend/commands/functioncmds.c 
b/src/backend/commands/functioncmds.c
index e7e37146f6..8198936741 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -939,11 +939,6 @@ interpret_AS_clause(Oid languageOid, const char 
*languageName,
                                pstate->p_sourcetext = queryString;
                                sql_fn_parser_setup(pstate, pinfo);
                                q = transformStmt(pstate, stmt);
-                               if (q->commandType == CMD_UTILITY)
-                                       ereport(ERROR,
-                                                       
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                       errmsg("%s is not yet 
supported in unquoted SQL function body",
-                                                                  
GetCommandTagName(CreateCommandTag(q->utilityStmt))));
                                transformed_stmts = lappend(transformed_stmts, 
q);
                                free_parsestate(pstate);
                        }
@@ -958,11 +953,6 @@ interpret_AS_clause(Oid languageOid, const char 
*languageName,
                        pstate->p_sourcetext = queryString;
                        sql_fn_parser_setup(pstate, pinfo);
                        q = transformStmt(pstate, sql_body_in);
-                       if (q->commandType == CMD_UTILITY)
-                               ereport(ERROR,
-                                               
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                               errmsg("%s is not yet supported 
in unquoted SQL function body",
-                                                          
GetCommandTagName(CreateCommandTag(q->utilityStmt))));
                        free_parsestate(pstate);
 
                        *sql_body_out = (Node *) q;
diff --git a/src/test/regress/expected/create_procedure.out 
b/src/test/regress/expected/create_procedure.out
index 46c827f979..95bf3e5256 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -100,13 +100,18 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
  1 | xyzzy
 (4 rows)
 
--- utitlity functions currently not supported here
-CREATE PROCEDURE ptestx()
+CREATE PROCEDURE ptest_util()
 LANGUAGE SQL
 BEGIN ATOMIC
   CREATE TABLE x (a int);
 END;
-ERROR:  CREATE TABLE is not yet supported in unquoted SQL function body
+CALL ptest_util();
+SELECT * FROM x;
+ a 
+---
+(0 rows)
+
+DROP TABLE x;
 CREATE PROCEDURE ptest2()
 LANGUAGE SQL
 AS $$
diff --git a/src/test/regress/sql/create_procedure.sql 
b/src/test/regress/sql/create_procedure.sql
index 75cc0fcf2a..9d33e53a1d 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -42,13 +42,17 @@ CREATE PROCEDURE ptest1s(x text)
 
 SELECT * FROM cp_test ORDER BY b COLLATE "C";
 
--- utitlity functions currently not supported here
-CREATE PROCEDURE ptestx()
+
+CREATE PROCEDURE ptest_util()
 LANGUAGE SQL
 BEGIN ATOMIC
   CREATE TABLE x (a int);
 END;
 
+CALL ptest_util();
+SELECT * FROM x;
+DROP TABLE x;
+
 
 CREATE PROCEDURE ptest2()
 LANGUAGE SQL
-- 
2.37.1

Reply via email to