From: Richard Guo Date: Fri, 8 May 2026 08:21:48 +0000 (+0900) Subject: Enforce RETURNING typmod for empty-set JSON_ARRAY(query) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9d124a14b3d4803afee5efc4f794dfb72016cb88;p=thirdparty%2Fpostgresql.git 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 --- 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));