]> 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:21:18 +0000 (10:21 +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 1b44b7a78d24cd8e1cc1cafb693e32adec8644b9..043e43b630964d8e5f080ecc23c3a04b64547875 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 cacef7d41517c62762034017e78623b8cc580430..1f712666511161408f6fb5ca79cb0eb20012969a 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;