Skip to content

Commit ef5e60a

Browse files
committed
Fix internal error from CollateExpr in SQL/JSON DEFAULT expressions
SQL/JSON functions such as JSON_VALUE could fail with "unrecognized node type" errors when a DEFAULT clause contained an explicit COLLATE expression. That happened because assign_collations_walker() could invoke exprSetCollation() on a JsonBehavior expression whose DEFAULT still contained a CollateExpr, which exprSetCollation() does not handle. For example: SELECT JSON_VALUE('{"a":1}', '$.c' RETURNING text DEFAULT 'A' COLLATE "C" ON EMPTY); Fix by validating in transformJsonBehavior() that the DEFAULT expression's collation matches the enclosing JSON expression’s collation. In exprSetCollation(), replace the recursive call on the JsonBehavior expression with an assertion that its collation already matches the target, since the parser now enforces that condition. Reported-by: Jian He <[email protected]> Author: Jian He <[email protected]> Reviewed-by: Amit Langote <[email protected]> Discussion: https://postgr.es/m/CACJufxHVwYYSyiVQ6o+PsRX6zQ7rAFinh_fv1kCfTsT1xG4Zeg@mail.gmail.com Backpatch-through: 17
1 parent a5a68dd commit ef5e60a

File tree

4 files changed

+113
-14
lines changed

4 files changed

+113
-14
lines changed

src/backend/nodes/nodeFuncs.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,12 +1274,8 @@ exprSetCollation(Node *expr, Oid collation)
12741274
}
12751275
break;
12761276
case T_JsonBehavior:
1277-
{
1278-
JsonBehavior *behavior = (JsonBehavior *) expr;
1279-
1280-
if (behavior->expr)
1281-
exprSetCollation(behavior->expr, collation);
1282-
}
1277+
Assert(((JsonBehavior *) expr)->expr == NULL ||
1278+
exprCollation(((JsonBehavior *) expr)->expr) == collation);
12831279
break;
12841280
case T_NullTest:
12851281
/* NullTest's result is boolean ... */

src/backend/parser/parse_expr.c

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ static Node *transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func);
9494
static void transformJsonPassingArgs(ParseState *pstate, const char *constructName,
9595
JsonFormatType format, List *args,
9696
List **passing_values, List **passing_names);
97-
static JsonBehavior *transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior,
97+
static JsonBehavior *transformJsonBehavior(ParseState *pstate, JsonExpr *jsexpr,
98+
JsonBehavior *behavior,
9899
JsonBehaviorType default_behavior,
99100
JsonReturning *returning);
100101
static Node *GetJsonBehaviorConst(JsonBehaviorType btype, int location);
@@ -4529,13 +4530,16 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
45294530
{
45304531
jsexpr->returning->typid = BOOLOID;
45314532
jsexpr->returning->typmod = -1;
4533+
jsexpr->collation = InvalidOid;
45324534
}
45334535

45344536
/* JSON_TABLE() COLUMNS can specify a non-boolean type. */
45354537
if (jsexpr->returning->typid != BOOLOID)
45364538
jsexpr->use_json_coercion = true;
45374539

4538-
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
4540+
jsexpr->on_error = transformJsonBehavior(pstate,
4541+
jsexpr,
4542+
func->on_error,
45394543
JSON_BEHAVIOR_FALSE,
45404544
jsexpr->returning);
45414545
break;
@@ -4550,6 +4554,8 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
45504554
ret->typmod = -1;
45514555
}
45524556

4557+
jsexpr->collation = get_typcollation(jsexpr->returning->typid);
4558+
45534559
/*
45544560
* Keep quotes on scalar strings by default, omitting them only if
45554561
* OMIT QUOTES is specified.
@@ -4566,11 +4572,15 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
45664572
jsexpr->use_json_coercion = true;
45674573

45684574
/* Assume NULL ON EMPTY when ON EMPTY is not specified. */
4569-
jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty,
4575+
jsexpr->on_empty = transformJsonBehavior(pstate,
4576+
jsexpr,
4577+
func->on_empty,
45704578
JSON_BEHAVIOR_NULL,
45714579
jsexpr->returning);
45724580
/* Assume NULL ON ERROR when ON ERROR is not specified. */
4573-
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
4581+
jsexpr->on_error = transformJsonBehavior(pstate,
4582+
jsexpr,
4583+
func->on_error,
45744584
JSON_BEHAVIOR_NULL,
45754585
jsexpr->returning);
45764586
break;
@@ -4582,6 +4592,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
45824592
jsexpr->returning->typid = TEXTOID;
45834593
jsexpr->returning->typmod = -1;
45844594
}
4595+
jsexpr->collation = get_typcollation(jsexpr->returning->typid);
45854596

45864597
/*
45874598
* Override whatever transformJsonOutput() set these to, which
@@ -4607,11 +4618,15 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
46074618
}
46084619

46094620
/* Assume NULL ON EMPTY when ON EMPTY is not specified. */
4610-
jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty,
4621+
jsexpr->on_empty = transformJsonBehavior(pstate,
4622+
jsexpr,
4623+
func->on_empty,
46114624
JSON_BEHAVIOR_NULL,
46124625
jsexpr->returning);
46134626
/* Assume NULL ON ERROR when ON ERROR is not specified. */
4614-
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
4627+
jsexpr->on_error = transformJsonBehavior(pstate,
4628+
jsexpr,
4629+
func->on_error,
46154630
JSON_BEHAVIOR_NULL,
46164631
jsexpr->returning);
46174632
break;
@@ -4622,14 +4637,17 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
46224637
jsexpr->returning->typid = exprType(jsexpr->formatted_expr);
46234638
jsexpr->returning->typmod = -1;
46244639
}
4640+
jsexpr->collation = get_typcollation(jsexpr->returning->typid);
46254641

46264642
/*
46274643
* Assume EMPTY ARRAY ON ERROR when ON ERROR is not specified.
46284644
*
46294645
* ON EMPTY cannot be specified at the top level but it can be for
46304646
* the individual columns.
46314647
*/
4632-
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
4648+
jsexpr->on_error = transformJsonBehavior(pstate,
4649+
jsexpr,
4650+
func->on_error,
46334651
JSON_BEHAVIOR_EMPTY_ARRAY,
46344652
jsexpr->returning);
46354653
break;
@@ -4705,7 +4723,8 @@ ValidJsonBehaviorDefaultExpr(Node *expr, void *context)
47054723
* Transform a JSON BEHAVIOR clause.
47064724
*/
47074725
static JsonBehavior *
4708-
transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior,
4726+
transformJsonBehavior(ParseState *pstate, JsonExpr *jsexpr,
4727+
JsonBehavior *behavior,
47094728
JsonBehaviorType default_behavior,
47104729
JsonReturning *returning)
47114730
{
@@ -4720,7 +4739,11 @@ transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior,
47204739
location = behavior->location;
47214740
if (btype == JSON_BEHAVIOR_DEFAULT)
47224741
{
4742+
Oid targetcoll = jsexpr->collation;
4743+
Oid exprcoll;
4744+
47234745
expr = transformExprRecurse(pstate, behavior->expr);
4746+
47244747
if (!ValidJsonBehaviorDefaultExpr(expr, NULL))
47254748
ereport(ERROR,
47264749
(errcode(ERRCODE_DATATYPE_MISMATCH),
@@ -4736,6 +4759,24 @@ transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior,
47364759
(errcode(ERRCODE_DATATYPE_MISMATCH),
47374760
errmsg("DEFAULT expression must not return a set"),
47384761
parser_errposition(pstate, exprLocation(expr))));
4762+
4763+
/*
4764+
* Reject a DEFAULT expression whose collation differs from the
4765+
* enclosing JSON expression's result collation
4766+
* (jsexpr->collation), as chosen by the RETURNING clause.
4767+
*/
4768+
exprcoll = exprCollation(expr);
4769+
if (!OidIsValid(exprcoll))
4770+
exprcoll = get_typcollation(exprType(expr));
4771+
if (OidIsValid(targetcoll) && OidIsValid(exprcoll) &&
4772+
targetcoll != exprcoll)
4773+
ereport(ERROR,
4774+
errcode(ERRCODE_COLLATION_MISMATCH),
4775+
errmsg("the collation of DEFAULT expression conflicts with RETURNING clause"),
4776+
errdetail("\"%s\" versus \"%s\"",
4777+
get_collation_name(exprcoll),
4778+
get_collation_name(targetcoll)),
4779+
parser_errposition(pstate, exprLocation(expr)));
47394780
}
47404781
}
47414782

src/test/regress/expected/collate.icu.utf8.out

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2694,6 +2694,55 @@ SELECT * FROM t5 ORDER BY c ASC, a ASC;
26942694
3 | d1 | d1
26952695
(3 rows)
26962696

2697+
-- Check that DEFAULT expressions in SQL/JSON functions use the same collation
2698+
-- as the RETURNING type. Mismatched collations should raise an error.
2699+
CREATE DOMAIN d1 AS text COLLATE case_insensitive;
2700+
CREATE DOMAIN d2 AS text COLLATE "C";
2701+
SELECT JSON_VALUE('{"a": "A"}', '$.a' RETURNING d1 DEFAULT ('C' COLLATE "C") COLLATE case_insensitive ON EMPTY) = 'a'; -- true
2702+
?column?
2703+
----------
2704+
t
2705+
(1 row)
2706+
2707+
SELECT JSON_VALUE('{"a": "A"}', '$.a' RETURNING d1 DEFAULT 'C' ON EMPTY) = 'a'; -- true
2708+
?column?
2709+
----------
2710+
t
2711+
(1 row)
2712+
2713+
SELECT JSON_VALUE('{"a": "A"}', '$.a' RETURNING d1 DEFAULT 'C'::d2 ON EMPTY) = 'a'; -- error
2714+
ERROR: the collation of DEFAULT expression conflicts with RETURNING clause
2715+
LINE 1: ...ON_VALUE('{"a": "A"}', '$.a' RETURNING d1 DEFAULT 'C'::d2 ON...
2716+
^
2717+
DETAIL: "C" versus "case_insensitive"
2718+
SELECT JSON_VALUE('{"a": "A"}', '$.a' RETURNING d1 DEFAULT 'C' COLLATE "C" ON EMPTY) = 'a'; -- error
2719+
ERROR: the collation of DEFAULT expression conflicts with RETURNING clause
2720+
LINE 1: ...ON_VALUE('{"a": "A"}', '$.a' RETURNING d1 DEFAULT 'C' COLLAT...
2721+
^
2722+
DETAIL: "C" versus "case_insensitive"
2723+
SELECT JSON_VALUE('{"a": "A"}', '$.c' RETURNING d1 DEFAULT 'A' ON EMPTY) = 'a'; -- true
2724+
?column?
2725+
----------
2726+
t
2727+
(1 row)
2728+
2729+
SELECT JSON_VALUE('{"a": "A"}', '$.c' RETURNING d1 DEFAULT 'A' COLLATE case_insensitive ON EMPTY) = 'a'; -- true
2730+
?column?
2731+
----------
2732+
t
2733+
(1 row)
2734+
2735+
SELECT JSON_VALUE('{"a": "A"}', '$.c' RETURNING d1 DEFAULT 'A'::d2 ON EMPTY) = 'a'; -- error
2736+
ERROR: the collation of DEFAULT expression conflicts with RETURNING clause
2737+
LINE 1: ...ON_VALUE('{"a": "A"}', '$.c' RETURNING d1 DEFAULT 'A'::d2 ON...
2738+
^
2739+
DETAIL: "C" versus "case_insensitive"
2740+
SELECT JSON_VALUE('{"a": "A"}', '$.c' RETURNING d1 DEFAULT 'A' COLLATE "C" ON EMPTY) = 'a'; -- error
2741+
ERROR: the collation of DEFAULT expression conflicts with RETURNING clause
2742+
LINE 1: ...ON_VALUE('{"a": "A"}', '$.c' RETURNING d1 DEFAULT 'A' COLLAT...
2743+
^
2744+
DETAIL: "C" versus "case_insensitive"
2745+
DROP DOMAIN d1, d2;
26972746
-- cleanup
26982747
RESET search_path;
26992748
SET client_min_messages TO warning;

src/test/regress/sql/collate.icu.utf8.sql

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,19 @@ INSERT INTO t5 (a, b) values (1, 'D1'), (2, 'D2'), (3, 'd1');
997997
-- rewriting.)
998998
SELECT * FROM t5 ORDER BY c ASC, a ASC;
999999

1000+
-- Check that DEFAULT expressions in SQL/JSON functions use the same collation
1001+
-- as the RETURNING type. Mismatched collations should raise an error.
1002+
CREATE DOMAIN d1 AS text COLLATE case_insensitive;
1003+
CREATE DOMAIN d2 AS text COLLATE "C";
1004+
SELECT JSON_VALUE('{"a": "A"}', '$.a' RETURNING d1 DEFAULT ('C' COLLATE "C") COLLATE case_insensitive ON EMPTY) = 'a'; -- true
1005+
SELECT JSON_VALUE('{"a": "A"}', '$.a' RETURNING d1 DEFAULT 'C' ON EMPTY) = 'a'; -- true
1006+
SELECT JSON_VALUE('{"a": "A"}', '$.a' RETURNING d1 DEFAULT 'C'::d2 ON EMPTY) = 'a'; -- error
1007+
SELECT JSON_VALUE('{"a": "A"}', '$.a' RETURNING d1 DEFAULT 'C' COLLATE "C" ON EMPTY) = 'a'; -- error
1008+
SELECT JSON_VALUE('{"a": "A"}', '$.c' RETURNING d1 DEFAULT 'A' ON EMPTY) = 'a'; -- true
1009+
SELECT JSON_VALUE('{"a": "A"}', '$.c' RETURNING d1 DEFAULT 'A' COLLATE case_insensitive ON EMPTY) = 'a'; -- true
1010+
SELECT JSON_VALUE('{"a": "A"}', '$.c' RETURNING d1 DEFAULT 'A'::d2 ON EMPTY) = 'a'; -- error
1011+
SELECT JSON_VALUE('{"a": "A"}', '$.c' RETURNING d1 DEFAULT 'A' COLLATE "C" ON EMPTY) = 'a'; -- error
1012+
DROP DOMAIN d1, d2;
10001013

10011014
-- cleanup
10021015
RESET search_path;

0 commit comments

Comments
 (0)