From 1910daf98317bc321562e7d7436a791538825d0e Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 23 Oct 2026 16:21:54 +0900 Subject: [PATCH] Fix handling of some SQL/JSON locations in query jumbling GetJsonBehaviorConst() builds Const values for clauses like ON EMPTY or ON ERROR in SQL/JSON, and was setting up a location without considering the fact that these are not constants as per the query text: they are query keywords. For example "EMPTY ARRAY ON EMPTY" became "$2 ARRAY ON EMPTY", leading to confusing information reported for example in PGSS. The Const nodes used by GetJsonBehaviorConst() is now left unset. The location was only used for an error position. This code path cannot be reached currently but we cannot use the expression location if not set; this spot is switched to the location of behavior keyword instead, which is better than nothing. This is obviously a bug. Unfortunately, no backpatch is done as this impacts the information of saved in PGSS and the shape of the Nodes themselves. Oversight in 6185c9737cf4. Author: Ilya Staroverov Reviewed-by: Sami Imseih Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/457969A9-5D24-422F-B09E-F388053DE028@localhost.localdomain --- .../pg_stat_statements/expected/sqljson.out | 34 +++++++++---------- src/backend/parser/parse_expr.c | 9 +++-- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/contrib/pg_stat_statements/expected/sqljson.out b/contrib/pg_stat_statements/expected/sqljson.out index e62504122de..43f0a560c6f 100644 --- a/contrib/pg_stat_statements/expected/sqljson.out +++ b/contrib/pg_stat_statements/expected/sqljson.out @@ -72,11 +72,11 @@ SELECT JSON_VALUE(js, '$.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR) FROM pgss_sql SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C", calls; calls | query -------+----------------------------------------------------------------------------------------- - 2 | SELECT JSON_VALUE(js, $1 $2 ON EMPTY ERROR ON ERROR) FROM pgss_sqljson_tab 1 | SELECT JSON_VALUE(js, $1 DEFAULT $2 ON EMPTY DEFAULT $3 ON ERROR) FROM pgss_sqljson_tab 2 | SELECT JSON_VALUE(js, $1 DEFAULT $2 ON EMPTY) FROM pgss_sqljson_tab 1 | SELECT JSON_VALUE(js, $1 DEFAULT $2 ON ERROR) FROM pgss_sqljson_tab - 2 | SELECT JSON_VALUE(js, $1 ERROR ON EMPTY $2 ON ERROR) FROM pgss_sqljson_tab + 2 | SELECT JSON_VALUE(js, $1 ERROR ON EMPTY NULL ON ERROR) FROM pgss_sqljson_tab + 2 | SELECT JSON_VALUE(js, $1 NULL ON EMPTY ERROR ON ERROR) FROM pgss_sqljson_tab 3 | SELECT JSON_VALUE(js, $1) FROM pgss_sqljson_tab 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t (7 rows) @@ -219,19 +219,19 @@ SELECT JSON_QUERY(js, '$.a' EMPTY ARRAY ON EMPTY EMPTY OBJECT ON ERROR) (0 rows) SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C", calls; - calls | query --------+--------------------------------------------------------------------- - 1 | SELECT JSON_QUERY(js, $1 $2 ARRAY ON EMPTY $3 OBJECT ON ERROR) + - | FROM pgss_sqljson_tab - 2 | SELECT JSON_QUERY(js, $1 $2 ARRAY ON EMPTY) FROM pgss_sqljson_tab - 1 | SELECT JSON_QUERY(js, $1 $2 OBJECT ON EMPTY) FROM pgss_sqljson_tab - 1 | SELECT JSON_QUERY(js, $1 $2 OBJECT ON ERROR) FROM pgss_sqljson_tab - 1 | SELECT JSON_QUERY(js, $1 $2 ON EMPTY) FROM pgss_sqljson_tab - 2 | SELECT JSON_QUERY(js, $1 $2 ON ERROR) FROM pgss_sqljson_tab + calls | query +-------+----------------------------------------------------------------------- 1 | SELECT JSON_QUERY(js, $1 DEFAULT $2 ON EMPTY) FROM pgss_sqljson_tab 1 | SELECT JSON_QUERY(js, $1 DEFAULT $2 ON ERROR) FROM pgss_sqljson_tab + 1 | SELECT JSON_QUERY(js, $1 EMPTY ARRAY ON EMPTY EMPTY OBJECT ON ERROR) + + | FROM pgss_sqljson_tab + 2 | SELECT JSON_QUERY(js, $1 EMPTY ARRAY ON EMPTY) FROM pgss_sqljson_tab + 1 | SELECT JSON_QUERY(js, $1 EMPTY OBJECT ON EMPTY) FROM pgss_sqljson_tab + 1 | SELECT JSON_QUERY(js, $1 EMPTY OBJECT ON ERROR) FROM pgss_sqljson_tab + 2 | SELECT JSON_QUERY(js, $1 EMPTY ON ERROR) FROM pgss_sqljson_tab 1 | SELECT JSON_QUERY(js, $1 ERROR ON EMPTY) FROM pgss_sqljson_tab 1 | SELECT JSON_QUERY(js, $1 ERROR ON ERROR) FROM pgss_sqljson_tab + 1 | SELECT JSON_QUERY(js, $1 NULL ON EMPTY) FROM pgss_sqljson_tab 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t (11 rows) @@ -429,13 +429,13 @@ SELECT JSON_EXISTS(js, '$.a ? (@ > $x)' PASSING 2 AS x) FROM pgss_sqljson_tab; (0 rows) SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C", calls; - calls | query --------+------------------------------------------------------------------ - 1 | SELECT JSON_EXISTS(js, $1 $2 ON ERROR) FROM pgss_sqljson_tab - 1 | SELECT JSON_EXISTS(js, $1 $2 ON ERROR) FROM pgss_sqljson_tab - 2 | SELECT JSON_EXISTS(js, $1 $2 ON ERROR) FROM pgss_sqljson_tab + calls | query +-------+------------------------------------------------------------------- 1 | SELECT JSON_EXISTS(js, $1 ERROR ON ERROR) FROM pgss_sqljson_tab + 2 | SELECT JSON_EXISTS(js, $1 FALSE ON ERROR) FROM pgss_sqljson_tab 2 | SELECT JSON_EXISTS(js, $1 PASSING $2 AS x) FROM pgss_sqljson_tab + 1 | SELECT JSON_EXISTS(js, $1 TRUE ON ERROR) FROM pgss_sqljson_tab + 1 | SELECT JSON_EXISTS(js, $1 UNKNOWN ON ERROR) FROM pgss_sqljson_tab 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t (6 rows) @@ -995,7 +995,7 @@ SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C", calls; calls | query -------+------------------------------------------------------------------- 3 | SELECT * FROM pgss_sqljson_tab, + - | JSON_TABLE(js, $1 COLUMNS (a int PATH '$.a') $2 ON ERROR) jt + | JSON_TABLE(js, $1 COLUMNS (a int PATH '$.a') EMPTY ON ERROR) jt 1 | SELECT * FROM pgss_sqljson_tab, + | JSON_TABLE(js, $1 COLUMNS (a int PATH '$.a') ERROR ON ERROR) jt 2 | SELECT * FROM pgss_sqljson_tab, + diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 05a6b72d4c9..728392f436f 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -99,7 +99,7 @@ static JsonBehavior *transformJsonBehavior(ParseState *pstate, JsonExpr *jsexpr, JsonBehavior *behavior, JsonBehaviorType default_behavior, JsonReturning *returning); -static Node *GetJsonBehaviorConst(JsonBehaviorType btype, int location); +static Node *GetJsonBehaviorConst(JsonBehaviorType btype); static Node *make_row_comparison_op(ParseState *pstate, List *opname, List *largs, List *rargs, int location); static Node *make_row_distinct_op(ParseState *pstate, List *opname, @@ -4897,7 +4897,7 @@ transformJsonBehavior(ParseState *pstate, JsonExpr *jsexpr, } if (expr == NULL && btype != JSON_BEHAVIOR_ERROR) - expr = GetJsonBehaviorConst(btype, location); + expr = GetJsonBehaviorConst(btype); /* * Try to coerce the expression if needed. @@ -4988,7 +4988,7 @@ transformJsonBehavior(ParseState *pstate, JsonExpr *jsexpr, 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))); + parser_errposition(pstate, location)); } expr = coerced_expr; @@ -5010,7 +5010,7 @@ transformJsonBehavior(ParseState *pstate, JsonExpr *jsexpr, * JsonBehaviorType. */ static Node * -GetJsonBehaviorConst(JsonBehaviorType btype, int location) +GetJsonBehaviorConst(JsonBehaviorType btype) { Datum val = (Datum) 0; Oid typid = JSONBOID; @@ -5065,7 +5065,6 @@ GetJsonBehaviorConst(JsonBehaviorType btype, int location) } con = makeConst(typid, -1, InvalidOid, len, val, isnull, isbyval); - con->location = location; return (Node *) con; }