From 9d124a14b3d4803afee5efc4f794dfb72016cb88 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Fri, 8 May 2026 17:21:48 +0900 Subject: [PATCH] Enforce RETURNING typmod for empty-set JSON_ARRAY(query) Commit 8d829f5a0 introduced a COALESCE wrapper around the JSON_ARRAYAGG subquery so that JSON_ARRAY(query) returns '[]' rather than NULL when the subquery yields no rows, per the SQL/JSON standard. The empty-array Const used as the COALESCE fallback was, however, built with typmod -1 and the type input function was likewise invoked with typmod -1. As a result, any length restriction from the RETURNING clause was silently bypassed on the empty-set path, while the non-empty path enforced it via the JSON_ARRAYAGG coercion. Build the empty-array Const using the typmod of the COALESCE's non-empty argument, and pass that typmod to OidInputFunctionCall as well so the value is length-checked at parse time. This makes the empty-set and non-empty-set paths behave consistently. Reported-by: Ayush Tiwari Author: Richard Guo Discussion: https://postgr.es/m/CAJTYsWXPYqa58YXrU+SQMVonsAhjLS46HNUMU=wO5zm9MgY3_g@mail.gmail.com --- src/backend/parser/parse_expr.c | 12 ++++++++---- src/test/regress/expected/sqljson.out | 19 +++++++++++++++++++ src/test/regress/sql/sqljson.sql | 8 ++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index c3c7aa29720..f1003e57fb2 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -3826,6 +3826,7 @@ transformJsonArrayQueryConstructor(ParseState *pstate, CoalesceExpr *coalesce; Const *empty_const; Oid result_type; + int32 result_typmod; Oid typinput; Oid typioparam; int16 typlen; @@ -3904,19 +3905,22 @@ transformJsonArrayQueryConstructor(ParseState *pstate, /* * Wrap in COALESCE so that an empty result set produces '[]' rather than - * NULL. The empty-array constant is created in the output type so that - * the COALESCE arguments have consistent types. + * NULL. The empty-array constant is created in the output type and + * typmod, so that the COALESCE arguments have consistent types and any + * length restriction from the RETURNING clause is enforced uniformly + * across the empty and non-empty paths. */ result_type = exprType(exec_expr); + result_typmod = exprTypmod(exec_expr); getTypeInputInfo(result_type, &typinput, &typioparam); get_typlenbyval(result_type, &typlen, &typbyval); empty_const = makeConst(result_type, - -1, + result_typmod, exprCollation(exec_expr), (int) typlen, OidInputFunctionCall(typinput, "[]", - typioparam, -1), + typioparam, result_typmod), false, typbyval); diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index a14936a4e6e..143d961c077 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -783,6 +783,25 @@ WHERE JSON_ARRAY( 4 (2 rows) +-- JSON_ARRAY(subquery) RETURNING with a length-restricted output type +-- Should fail +SELECT JSON_ARRAY(SELECT 1 RETURNING varchar(1)); +ERROR: value too long for type character varying(1) +SELECT JSON_ARRAY(SELECT 1 WHERE FALSE RETURNING varchar(1)); +ERROR: value too long for type character varying(1) +-- Should work +SELECT JSON_ARRAY(SELECT 1 RETURNING varchar(3)); + json_array +------------ + [1] +(1 row) + +SELECT JSON_ARRAY(SELECT 1 WHERE FALSE RETURNING varchar(2)); + json_array +------------ + [] +(1 row) + -- Should fail SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); ERROR: subquery must return only one column diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index 00ecf6161bf..ed044d81fdd 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -213,6 +213,14 @@ WHERE JSON_ARRAY( RETURNING jsonb ) = '[]'::jsonb; +-- JSON_ARRAY(subquery) RETURNING with a length-restricted output type +-- Should fail +SELECT JSON_ARRAY(SELECT 1 RETURNING varchar(1)); +SELECT JSON_ARRAY(SELECT 1 WHERE FALSE RETURNING varchar(1)); +-- Should work +SELECT JSON_ARRAY(SELECT 1 RETURNING varchar(3)); +SELECT JSON_ARRAY(SELECT 1 WHERE FALSE RETURNING varchar(2)); + -- Should fail SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i));