Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c2d93c3

Browse files
committed
SQL/JSON: Fix coercion of constructor outputs to types with typmod
Ensure SQL/JSON constructor functions that allow specifying the target type using the RETURNING clause perform implicit cast to that type. This ensures that output values that exceed the specified length produce an error rather than being silently truncated. This behavior conforms to the SQL standard. Reported-by: Alvaro Herrera <alvherre@alvh.no-ip.org> Reviewed-by: Jian He <jian.universality@gmail.com> Discussion: https://postgr.es/m/202405271326.5a5rprki64aw%40alvherre.pgsql
1 parent 065583c commit c2d93c3

File tree

3 files changed

+52
-1
lines changed

3 files changed

+52
-1
lines changed

src/backend/parser/parse_expr.c

+23-1
Original file line numberDiff line numberDiff line change
@@ -3583,6 +3583,7 @@ coerceJsonFuncExpr(ParseState *pstate, Node *expr,
35833583
Node *res;
35843584
int location;
35853585
Oid exprtype = exprType(expr);
3586+
int32 baseTypmod = returning->typmod;
35863587

35873588
/* if output type is not specified or equals to function type, return */
35883589
if (!OidIsValid(returning->typid) || returning->typid == exprtype)
@@ -3611,10 +3612,19 @@ coerceJsonFuncExpr(ParseState *pstate, Node *expr,
36113612
return (Node *) fexpr;
36123613
}
36133614

3615+
/*
3616+
* For domains, consider the base type's typmod to decide whether to setup
3617+
* an implicit or explicit cast.
3618+
*/
3619+
if (get_typtype(returning->typid) == TYPTYPE_DOMAIN)
3620+
(void) getBaseTypeAndTypmod(returning->typid, &baseTypmod);
3621+
36143622
/* try to coerce expression to the output type */
36153623
res = coerce_to_target_type(pstate, expr, exprtype,
3616-
returning->typid, returning->typmod,
3624+
returning->typid, baseTypmod,
3625+
baseTypmod > 0 ? COERCION_IMPLICIT :
36173626
COERCION_EXPLICIT,
3627+
baseTypmod > 0 ? COERCE_IMPLICIT_CAST :
36183628
COERCE_EXPLICIT_CAST,
36193629
location);
36203630

@@ -3640,6 +3650,7 @@ makeJsonConstructorExpr(ParseState *pstate, JsonConstructorType type,
36403650
JsonConstructorExpr *jsctor = makeNode(JsonConstructorExpr);
36413651
Node *placeholder;
36423652
Node *coercion;
3653+
int32 baseTypmod = returning->typmod;
36433654

36443655
jsctor->args = args;
36453656
jsctor->func = fexpr;
@@ -3677,6 +3688,17 @@ makeJsonConstructorExpr(ParseState *pstate, JsonConstructorType type,
36773688
placeholder = (Node *) cte;
36783689
}
36793690

3691+
/*
3692+
* Convert the source expression to text, because coerceJsonFuncExpr()
3693+
* will create an implicit cast to the RETURNING types with typmod and
3694+
* there are no implicit casts from json(b) to such types. For domains,
3695+
* the base type's typmod will be considered, so do so here too.
3696+
*/
3697+
if (get_typtype(returning->typid) == TYPTYPE_DOMAIN)
3698+
(void) getBaseTypeAndTypmod(returning->typid, &baseTypmod);
3699+
if (baseTypmod > 0)
3700+
placeholder = coerce_to_specific_type(pstate, placeholder, TEXTOID,
3701+
"JSON_CONSTRUCTOR()");
36803702
coercion = coerceJsonFuncExpr(pstate, placeholder, returning, true);
36813703

36823704
if (coercion != placeholder)

src/test/regress/expected/sqljson.out

+17
Original file line numberDiff line numberDiff line change
@@ -1278,3 +1278,20 @@ CREATE OR REPLACE VIEW public.is_json_view AS
12781278
'{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object
12791279
FROM generate_series(1, 3) i(i)
12801280
DROP VIEW is_json_view;
1281+
-- Test implicit coercion to a fixed-length type specified in RETURNING
1282+
SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING varchar(2));
1283+
ERROR: value too long for type character varying(2)
1284+
SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING varchar(2)));
1285+
ERROR: value too long for type character varying(2)
1286+
SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING varchar(2)));
1287+
ERROR: value too long for type character varying(2)
1288+
SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING varchar(2)) FROM generate_series(1,1) i;
1289+
ERROR: value too long for type character varying(2)
1290+
SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING varchar(2)) FROM generate_series(1, 1) i;
1291+
ERROR: value too long for type character varying(2)
1292+
-- Now try domain over fixed-length type
1293+
CREATE DOMAIN sqljson_char2 AS char(2) CHECK (VALUE NOT IN ('12'));
1294+
SELECT JSON_SERIALIZE('123' RETURNING sqljson_char2);
1295+
ERROR: value too long for type character(2)
1296+
SELECT JSON_SERIALIZE('12' RETURNING sqljson_char2);
1297+
ERROR: value for domain sqljson_char2 violates check constraint "sqljson_char2_check"

src/test/regress/sql/sqljson.sql

+12
Original file line numberDiff line numberDiff line change
@@ -463,3 +463,15 @@ SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT
463463
\sv is_json_view
464464

465465
DROP VIEW is_json_view;
466+
467+
-- Test implicit coercion to a fixed-length type specified in RETURNING
468+
SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING varchar(2));
469+
SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING varchar(2)));
470+
SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING varchar(2)));
471+
SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING varchar(2)) FROM generate_series(1,1) i;
472+
SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING varchar(2)) FROM generate_series(1, 1) i;
473+
474+
-- Now try domain over fixed-length type
475+
CREATE DOMAIN sqljson_char2 AS char(2) CHECK (VALUE NOT IN ('12'));
476+
SELECT JSON_SERIALIZE('123' RETURNING sqljson_char2);
477+
SELECT JSON_SERIALIZE('12' RETURNING sqljson_char2);

0 commit comments

Comments
 (0)