]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix deparsing of JSON_ARRAY(subquery) with a FORMAT clause
authorRichard Guo <rguo@postgresql.org>
Mon, 27 Jul 2026 01:21:18 +0000 (10:21 +0900)
committerRichard Guo <rguo@postgresql.org>
Mon, 27 Jul 2026 01:22:04 +0000 (10:22 +0900)
Commit 8d829f5a0 introduced the JSCTOR_JSON_ARRAY_QUERY constructor
type so that ruleutils.c could deparse JSON_ARRAY(subquery) using its
original syntax, storing the transformed subquery in a new orig_query
field.  However, the input FORMAT clause of JSON_ARRAY(subquery FORMAT
...) was not preserved for deparsing.  The format was recorded only in
the executable expression kept in the func field, which ruleutils.c
does not inspect, so it is silently dropped.

This is more than cosmetic, because FORMAT JSON changes the result:
without it a text value is treated as a string to be quoted, while
with it the value is treated as already-formatted JSON.

To fix, record the input FORMAT in a new deparse-only field of
JsonConstructorExpr, alongside orig_query, and emit it in ruleutils.c.

Bump catalog version.

Author: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Reviewed-by: Richard Guo <guofenglinux@gmail.com>
Discussion: https://postgr.es/m/4C89B193-7D54-4705-9CF9-F0D484B9E099@gmail.com
Backpatch-through: 19

src/backend/parser/parse_expr.c
src/backend/utils/adt/ruleutils.c
src/include/catalog/catversion.h
src/include/nodes/primnodes.h
src/test/regress/expected/sqljson.out
src/test/regress/sql/sqljson.sql

index e6ea34a7809371e7bb1a2b946f5fca1317124196..30c889f505f6473ee424dea9e942c28dde16fff3 100644 (file)
@@ -3808,6 +3808,8 @@ transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
  *  - orig_query: the transformed Query of the user's original subquery, so
  *    that ruleutils.c can deparse the original JSON_ARRAY(SELECT ...) syntax
  *    for view definitions.
+ *
+ *  - format: the input FORMAT clause, so that ruleutils.c can deparse it.
  */
 static Node *
 transformJsonArrayQueryConstructor(ParseState *pstate,
@@ -3944,6 +3946,7 @@ transformJsonArrayQueryConstructor(ParseState *pstate,
                                                                         false, ctor->absent_on_null,
                                                                         ctor->location);
        ((JsonConstructorExpr *) result)->orig_query = (Node *) query;
+       ((JsonConstructorExpr *) result)->format = ctor->format;
 
        return result;
 }
index c6555948df8553a37a9e7022a1d0f2cbe1a856ca..e0fc194b953af99e21ac89331e74da91bb5b5143 100644 (file)
@@ -12291,6 +12291,7 @@ get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
                                          context->prettyFlags, context->wrapColumn,
                                          context->indentLevel);
 
+               get_json_format(ctor->format, buf);
                get_json_constructor_options(ctor, buf);
                appendStringInfoChar(buf, ')');
 
index d0399cc1cbeec305e4bcb4631e40dd1499db0be5..83d462f4d4aae940a5e7d461312c7817c2ba63a9 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202607201
+#define CATALOG_VERSION_NO     202607271
 
 #endif
index bb05aeebee4121898c3c44adca44cb43a804f7bb..775437e44265647137918b9e5bb7e7ccccd851d1 100644 (file)
@@ -1718,6 +1718,10 @@ typedef enum JsonConstructorType
  * orig_query holds the user's original subquery for JSON_ARRAY(query), used
  * only by ruleutils.c for deparsing; it is not walked because func is
  * authoritative for all other purposes.
+ *
+ * format likewise holds the input FORMAT clause of JSON_ARRAY(query), which
+ * is otherwise only represented inside func; it is used only by ruleutils.c
+ * for deparsing.
  */
 typedef struct JsonConstructorExpr
 {
@@ -1728,6 +1732,7 @@ typedef struct JsonConstructorExpr
        Expr       *coercion;           /* coercion to RETURNING type */
        JsonReturning *returning;       /* RETURNING clause */
        Node       *orig_query;         /* original subquery for deparsing */
+       JsonFormat *format;                     /* input FORMAT for JSON_ARRAY(query) */
        bool            absent_on_null; /* ABSENT ON NULL? */
        bool            unique;                 /* WITH UNIQUE KEYS? (JSON_OBJECT[AGG] only) */
        ParseLoc        location;
index 091a0b98574ae3bd99253386d80f47eabddcb6b4..d72278d67caa0e4ffa8acfbe9e8e71a7fb3f8014 100644 (file)
@@ -1233,6 +1233,13 @@ CREATE OR REPLACE VIEW public.json_array_subquery_view AS
  SELECT JSON_ARRAY( SELECT foo.i
            FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i) RETURNING text) AS "json_array"
 DROP VIEW json_array_subquery_view;
+-- JSON_ARRAY(subquery) with an input FORMAT clause
+CREATE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT '{"a": 1}'::text FORMAT JSON);
+\sv json_array_subquery_view
+CREATE OR REPLACE VIEW public.json_array_subquery_view AS
+ SELECT JSON_ARRAY( SELECT '{"a": 1}'::text AS text FORMAT JSON RETURNING json) AS "json_array"
+DROP VIEW json_array_subquery_view;
 -- Test mutability of JSON_OBJECTAGG, JSON_ARRAYAGG, JSON_ARRAY, JSON_OBJECT
 create type comp1 as (a int, b date);
 create domain d_comp1 as comp1;
index 2550da15c4523e95461cc28e0d4d16e5190855ac..96217a5593552791b9bbb3b794bae6dfac25372c 100644 (file)
@@ -443,6 +443,14 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING
 
 DROP VIEW json_array_subquery_view;
 
+-- JSON_ARRAY(subquery) with an input FORMAT clause
+CREATE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT '{"a": 1}'::text FORMAT JSON);
+
+\sv json_array_subquery_view
+
+DROP VIEW json_array_subquery_view;
+
 -- Test mutability of JSON_OBJECTAGG, JSON_ARRAYAGG, JSON_ARRAY, JSON_OBJECT
 create type comp1 as (a int, b date);
 create domain d_comp1 as comp1;