1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-03 01:21:48 +03:00

SQL/JSON: Some fixes to JsonBehavior expression casting

1. Remove the special case handling when casting the JsonBehavior
   expressions to types with typmod, like 86d33987 did for the casting
   of SQL/JSON constructor functions.

2. Fix casting for fixed-length character and bit string types by
   using assignment-level casts.  This is again similar to what
   86d33987 did, but for ON ERROR / EMPTY expressions.

3. Use runtime coercion for the boolean ON ERROR constants so that
   using fixed-length character string types, for example, for an
   EXISTS column doesn't cause a "value too long for type
   character(n)" when the parser tries to coerce the default ON ERROR
   value "false" to that type, that is, even when clause is not
   specified.

4. Simplify the conditions of when to use runtime coercion vs
   creating the cast expression in the parser itself.  jsonb-valued
   expressions are now always coerced at runtime and boolean
   expressions too if the target type is a string type for the
   reasons mentioned above.

New tests are from a patch that Jian He posted.  Outputs of some
existing tests change because the coercion now happens at runtime
instead of at parse time.

Reported-by: Jian He <jian.universality@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Author: Amit Langote <amitlangote09@gmail.com>
Discussion: https://postgr.es/m/CACJufxEo4sUjKCYtda0_qt9tazqqKPmF1cqhW9KBOUeJFqQd2g@mail.gmail.com
Backpatch-through: 17
This commit is contained in:
Amit Langote 2024-07-30 10:37:56 +09:00
parent f208a16035
commit 847ee701bd
5 changed files with 164 additions and 52 deletions

View File

@ -4685,51 +4685,91 @@ transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior,
if (expr == NULL && btype != JSON_BEHAVIOR_ERROR) if (expr == NULL && btype != JSON_BEHAVIOR_ERROR)
expr = GetJsonBehaviorConst(btype, location); expr = GetJsonBehaviorConst(btype, location);
if (expr) /*
* Try to coerce the expression if needed.
*
* Use runtime coercion using json_populate_type() if the expression is
* NULL, jsonb-valued, or boolean-valued (unless the target type is
* integer or domain over integer, in which case use the
* boolean-to-integer cast function).
*
* For other non-NULL expressions, try to find a cast and error out if one
* is not found.
*/
if (expr && exprType(expr) != returning->typid)
{ {
Node *coerced_expr = expr;
bool isnull = (IsA(expr, Const) && ((Const *) expr)->constisnull); bool isnull = (IsA(expr, Const) && ((Const *) expr)->constisnull);
/*
* Coerce NULLs and "internal" (that is, not specified by the user)
* jsonb-valued expressions at runtime using json_populate_type().
*
* For other (user-specified) non-NULL values, try to find a cast and
* error out if one is not found.
*/
if (isnull || if (isnull ||
(exprType(expr) == JSONBOID && exprType(expr) == JSONBOID ||
btype == default_behavior)) (exprType(expr) == BOOLOID &&
getBaseType(returning->typid) != INT4OID))
{
coerce_at_runtime = true; coerce_at_runtime = true;
/*
* json_populate_type() expects to be passed a jsonb value, so gin
* up a Const containing the appropriate boolean value represented
* as jsonb, discarding the original Const containing a plain
* boolean.
*/
if (exprType(expr) == BOOLOID)
{
char *val = btype == JSON_BEHAVIOR_TRUE ? "true" : "false";
expr = (Node *) makeConst(JSONBOID, -1, InvalidOid, -1,
DirectFunctionCall1(jsonb_in,
CStringGetDatum(val)),
false, false);
}
}
else else
{ {
int32 baseTypmod = returning->typmod; Node *coerced_expr;
char typcategory = TypeCategory(returning->typid);
if (get_typtype(returning->typid) == TYPTYPE_DOMAIN) /*
(void) getBaseTypeAndTypmod(returning->typid, &baseTypmod); * Use an assignment cast if coercing to a string type so that
* build_coercion_expression() assumes implicit coercion when
if (baseTypmod > 0) * coercing the typmod, so that inputs exceeding length cause an
expr = coerce_to_specific_type(pstate, expr, TEXTOID, * error instead of silent truncation.
"JSON_FUNCTION()"); */
coerced_expr = coerced_expr =
coerce_to_target_type(pstate, expr, exprType(expr), coerce_to_target_type(pstate, expr, exprType(expr),
returning->typid, baseTypmod, returning->typid, returning->typmod,
baseTypmod > 0 ? COERCION_IMPLICIT : (typcategory == TYPCATEGORY_STRING ||
typcategory == TYPCATEGORY_BITSTRING) ?
COERCION_ASSIGNMENT :
COERCION_EXPLICIT, COERCION_EXPLICIT,
baseTypmod > 0 ? COERCE_IMPLICIT_CAST :
COERCE_EXPLICIT_CAST, COERCE_EXPLICIT_CAST,
exprLocation((Node *) behavior)); exprLocation((Node *) behavior));
}
if (coerced_expr == NULL) if (coerced_expr == NULL)
ereport(ERROR, {
errcode(ERRCODE_CANNOT_COERCE), /*
errmsg("cannot cast behavior expression of type %s to %s", * Provide a HINT if the expression comes from a DEFAULT
format_type_be(exprType(expr)), * clause.
format_type_be(returning->typid)), */
parser_errposition(pstate, exprLocation(expr))); if (btype == JSON_BEHAVIOR_DEFAULT)
else ereport(ERROR,
errcode(ERRCODE_CANNOT_COERCE),
errmsg("cannot cast behavior expression of type %s to %s",
format_type_be(exprType(expr)),
format_type_be(returning->typid)),
errhint("You will need to explicitly cast the expression to type %s.",
format_type_be(returning->typid)),
parser_errposition(pstate, exprLocation(expr)));
else
ereport(ERROR,
errcode(ERRCODE_CANNOT_COERCE),
errmsg("cannot cast behavior expression of type %s to %s",
format_type_be(exprType(expr)),
format_type_be(returning->typid)),
parser_errposition(pstate, exprLocation(expr)));
}
expr = coerced_expr; expr = coerced_expr;
}
} }
if (behavior) if (behavior)

View File

@ -556,21 +556,38 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
(1 row) (1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$.a')); SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$.a'));
ERROR: cannot cast behavior expression of type boolean to smallint ERROR: could not coerce ON ERROR expression (FALSE) to the RETURNING type
DETAIL: invalid input syntax for type smallint: "false"
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$.a')); SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$.a'));
ERROR: cannot cast behavior expression of type boolean to bigint ERROR: could not coerce ON ERROR expression (FALSE) to the RETURNING type
DETAIL: invalid input syntax for type bigint: "false"
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH '$.a')); SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH '$.a'));
ERROR: cannot cast behavior expression of type boolean to real ERROR: could not coerce ON ERROR expression (FALSE) to the RETURNING type
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(5) EXISTS PATH '$.a')); DETAIL: invalid input syntax for type real: "false"
-- Default FALSE (ON ERROR) doesn't fit char(3)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'));
ERROR: could not coerce ON ERROR expression (FALSE) to the RETURNING type
DETAIL: value too long for type character(3)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a' ERROR ON ERROR));
ERROR: value too long for type character(3)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(5) EXISTS PATH '$.a' ERROR ON ERROR));
a a
------- -------
false false
(1 row) (1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$.a')); SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$.a'));
ERROR: cannot cast behavior expression of type boolean to json a
-------
false
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a')); SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a'));
ERROR: cannot cast behavior expression of type boolean to jsonb a
-------
false
(1 row)
-- JSON_TABLE: WRAPPER/QUOTES clauses on scalar columns -- JSON_TABLE: WRAPPER/QUOTES clauses on scalar columns
SELECT * FROM JSON_TABLE(jsonb '"world"', '$' COLUMNS (item text PATH '$' KEEP QUOTES ON SCALAR STRING)); SELECT * FROM JSON_TABLE(jsonb '"world"', '$' COLUMNS (item text PATH '$' KEEP QUOTES ON SCALAR STRING));
item item

View File

@ -662,7 +662,11 @@ SELECT JSON_QUERY(jsonb '"aaa"', '$.a' RETURNING char(2) OMIT QUOTES DEFAULT 'bb
(1 row) (1 row)
SELECT JSON_QUERY(jsonb '"aaa"', '$.a' RETURNING char(2) OMIT QUOTES DEFAULT '"bb"'::jsonb ON EMPTY); SELECT JSON_QUERY(jsonb '"aaa"', '$.a' RETURNING char(2) OMIT QUOTES DEFAULT '"bb"'::jsonb ON EMPTY);
ERROR: value too long for type character(2) json_query
------------
bb
(1 row)
-- OMIT QUOTES behavior should not be specified when WITH WRAPPER used: -- OMIT QUOTES behavior should not be specified when WITH WRAPPER used:
-- Should fail -- Should fail
SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES); SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES);
@ -865,13 +869,17 @@ SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea FORMAT JSON);
(1 row) (1 row)
SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR); SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR);
ERROR: cannot cast behavior expression of type jsonb to bytea json_query
LINE 1: ... JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea EMPTY OBJE... ------------
^ \x7b7d
(1 row)
SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR); SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR);
ERROR: cannot cast behavior expression of type jsonb to bytea json_query
LINE 1: ...jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJE... ------------
^ \x7b7d
(1 row)
SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR); SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR);
json_query json_query
------------ ------------
@ -885,13 +893,11 @@ SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR);
(1 row) (1 row)
SELECT JSON_QUERY(jsonb '[3,4]', '$[*]' RETURNING bigint[] EMPTY OBJECT ON ERROR); SELECT JSON_QUERY(jsonb '[3,4]', '$[*]' RETURNING bigint[] EMPTY OBJECT ON ERROR);
ERROR: cannot cast behavior expression of type jsonb to bigint[] ERROR: could not coerce ON ERROR expression (EMPTY OBJECT) to the RETURNING type
LINE 1: ...ON_QUERY(jsonb '[3,4]', '$[*]' RETURNING bigint[] EMPTY OBJE... DETAIL: expected JSON array
^
SELECT JSON_QUERY(jsonb '"[3,4]"', '$[*]' RETURNING bigint[] EMPTY OBJECT ON ERROR); SELECT JSON_QUERY(jsonb '"[3,4]"', '$[*]' RETURNING bigint[] EMPTY OBJECT ON ERROR);
ERROR: cannot cast behavior expression of type jsonb to bigint[] ERROR: could not coerce ON ERROR expression (EMPTY OBJECT) to the RETURNING type
LINE 1: ..._QUERY(jsonb '"[3,4]"', '$[*]' RETURNING bigint[] EMPTY OBJE... DETAIL: expected JSON array
^
-- Coercion fails with quotes on -- Coercion fails with quotes on
SELECT JSON_QUERY(jsonb '"123.1"', '$' RETURNING int2 error on error); SELECT JSON_QUERY(jsonb '"123.1"', '$' RETURNING int2 error on error);
ERROR: invalid input syntax for type smallint: ""123.1"" ERROR: invalid input syntax for type smallint: ""123.1""
@ -1379,7 +1385,8 @@ ERROR: invalid ON ERROR behavior
LINE 1: SELECT JSON_QUERY(jsonb '1', '$' TRUE ON ERROR); LINE 1: SELECT JSON_QUERY(jsonb '1', '$' TRUE ON ERROR);
^ ^
DETAIL: Only ERROR, NULL, EMPTY [ ARRAY ], EMPTY OBJECT, or DEFAULT expression is allowed in ON ERROR for JSON_QUERY(). DETAIL: Only ERROR, NULL, EMPTY [ ARRAY ], EMPTY OBJECT, or DEFAULT expression is allowed in ON ERROR for JSON_QUERY().
-- Test implicit coercion domain over fixed-legth type specified in RETURNING -- Test implicit coercion to a domain over fixed-length type specified in
-- RETURNING
CREATE DOMAIN queryfuncs_char2 AS char(2); CREATE DOMAIN queryfuncs_char2 AS char(2);
CREATE DOMAIN queryfuncs_char2_chk AS char(2) CHECK (VALUE NOT IN ('12')); CREATE DOMAIN queryfuncs_char2_chk AS char(2) CHECK (VALUE NOT IN ('12'));
SELECT JSON_QUERY(jsonb '123', '$' RETURNING queryfuncs_char2 ERROR ON ERROR); SELECT JSON_QUERY(jsonb '123', '$' RETURNING queryfuncs_char2 ERROR ON ERROR);
@ -1415,3 +1422,34 @@ SELECT JSON_VALUE(jsonb '123', '$' RETURNING queryfuncs_char2_chk DEFAULT 1 ON E
(1 row) (1 row)
DROP DOMAIN queryfuncs_char2, queryfuncs_char2_chk; DROP DOMAIN queryfuncs_char2, queryfuncs_char2_chk;
-- Test coercion to domain over another fixed-length type of the ON ERROR /
-- EMPTY expressions. Ask user to cast the DEFAULT expression explicitly if
-- automatic casting cannot be done, for example, from int to bit(2).
CREATE DOMAIN queryfuncs_d_varbit3 AS varbit(3) CHECK (VALUE <> '01');
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING queryfuncs_d_varbit3 DEFAULT '111111' ON ERROR);
ERROR: bit string too long for type bit varying(3)
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING queryfuncs_d_varbit3 DEFAULT '010' ON ERROR);
json_value
------------
010
(1 row)
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING queryfuncs_d_varbit3 DEFAULT '01' ON ERROR);
ERROR: could not coerce ON ERROR expression (DEFAULT) to the RETURNING type
DETAIL: value for domain queryfuncs_d_varbit3 violates check constraint "queryfuncs_d_varbit3_check"
SELECT JSON_VALUE(jsonb '"111"', '$' RETURNING bit(2) ERROR ON ERROR);
ERROR: bit string length 3 does not match type bit(2)
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING bit(3) DEFAULT 1 ON ERROR);
ERROR: cannot cast behavior expression of type integer to bit
LINE 1: ...VALUE(jsonb '1234', '$' RETURNING bit(3) DEFAULT 1 ON ERROR...
^
HINT: You will need to explicitly cast the expression to type bit.
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING bit(3) DEFAULT 1::bit(3) ON ERROR);
json_value
------------
001
(1 row)
SELECT JSON_VALUE(jsonb '"111"', '$.a' RETURNING bit(3) DEFAULT '1111' ON EMPTY);
ERROR: bit string length 4 does not match type bit(3)
DROP DOMAIN queryfuncs_d_varbit3;

View File

@ -266,7 +266,10 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$.a')); SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$.a')); SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH '$.a')); SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(5) EXISTS PATH '$.a')); -- Default FALSE (ON ERROR) doesn't fit char(3)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a' ERROR ON ERROR));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(5) EXISTS PATH '$.a' ERROR ON ERROR));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$.a')); SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a')); SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a'));

View File

@ -472,7 +472,8 @@ SELECT JSON_EXISTS(jsonb '1', '$' DEFAULT 1 ON ERROR);
SELECT JSON_VALUE(jsonb '1', '$' EMPTY ON ERROR); SELECT JSON_VALUE(jsonb '1', '$' EMPTY ON ERROR);
SELECT JSON_QUERY(jsonb '1', '$' TRUE ON ERROR); SELECT JSON_QUERY(jsonb '1', '$' TRUE ON ERROR);
-- Test implicit coercion domain over fixed-legth type specified in RETURNING -- Test implicit coercion to a domain over fixed-length type specified in
-- RETURNING
CREATE DOMAIN queryfuncs_char2 AS char(2); CREATE DOMAIN queryfuncs_char2 AS char(2);
CREATE DOMAIN queryfuncs_char2_chk AS char(2) CHECK (VALUE NOT IN ('12')); CREATE DOMAIN queryfuncs_char2_chk AS char(2) CHECK (VALUE NOT IN ('12'));
SELECT JSON_QUERY(jsonb '123', '$' RETURNING queryfuncs_char2 ERROR ON ERROR); SELECT JSON_QUERY(jsonb '123', '$' RETURNING queryfuncs_char2 ERROR ON ERROR);
@ -484,3 +485,16 @@ SELECT JSON_VALUE(jsonb '123', '$' RETURNING queryfuncs_char2 DEFAULT 1 ON ERROR
SELECT JSON_VALUE(jsonb '123', '$' RETURNING queryfuncs_char2_chk ERROR ON ERROR); SELECT JSON_VALUE(jsonb '123', '$' RETURNING queryfuncs_char2_chk ERROR ON ERROR);
SELECT JSON_VALUE(jsonb '123', '$' RETURNING queryfuncs_char2_chk DEFAULT 1 ON ERROR); SELECT JSON_VALUE(jsonb '123', '$' RETURNING queryfuncs_char2_chk DEFAULT 1 ON ERROR);
DROP DOMAIN queryfuncs_char2, queryfuncs_char2_chk; DROP DOMAIN queryfuncs_char2, queryfuncs_char2_chk;
-- Test coercion to domain over another fixed-length type of the ON ERROR /
-- EMPTY expressions. Ask user to cast the DEFAULT expression explicitly if
-- automatic casting cannot be done, for example, from int to bit(2).
CREATE DOMAIN queryfuncs_d_varbit3 AS varbit(3) CHECK (VALUE <> '01');
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING queryfuncs_d_varbit3 DEFAULT '111111' ON ERROR);
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING queryfuncs_d_varbit3 DEFAULT '010' ON ERROR);
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING queryfuncs_d_varbit3 DEFAULT '01' ON ERROR);
SELECT JSON_VALUE(jsonb '"111"', '$' RETURNING bit(2) ERROR ON ERROR);
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING bit(3) DEFAULT 1 ON ERROR);
SELECT JSON_VALUE(jsonb '1234', '$' RETURNING bit(3) DEFAULT 1::bit(3) ON ERROR);
SELECT JSON_VALUE(jsonb '"111"', '$.a' RETURNING bit(3) DEFAULT '1111' ON EMPTY);
DROP DOMAIN queryfuncs_d_varbit3;