From f66f65b35f9b43f5c6b9d72eda3c958d6abceab2 Mon Sep 17 00:00:00 2001 From: Ilya Staroverov Date: Wed, 21 Oct 2026 10:44:04 +0300 Subject: [PATCH v4 2/3] Don't give the SQL/JSON behavior constants a location GetJsonBehaviorConst() builds the Const for an ON EMPTY or ON ERROR behavior like NULL, TRUE, FALSE, UNKNOWN, EMPTY, EMPTY ARRAY or EMPTY OBJECT. Such a Const doesn't correspond to a constant in the query text, but it was given the location of the behavior clause. Query jumbling records the locations of Consts, so pg_stat_statements replaced the behavior keyword with a parameter symbol when normalizing the query text: for example, "EMPTY ARRAY ON EMPTY" became "$2 ARRAY ON EMPTY", and "TRUE ON ERROR" and "FALSE ON ERROR" both became "$2 ON ERROR", although the behavior is part of the query ID, so such queries got separate entries with the same text. The same happened with the ON ERROR clause of JSON_TABLE. Leave the location of the Const unset. It was only otherwise used as the error position when coercing the Const fails, so use the location of the behavior clause there directly. Query IDs don't change, since the locations of Consts are only recorded, not hashed. Oversight in 6185c9737cf4. Author: Ilya Staroverov Reviewed-by: Sami Imseih Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/086A90EE-9771-4387-8B46-BF84A610F04F@localhost.localdomain Discussion: https://postgr.es/m/DFE8E2CE-7A51-447C-B600-7D3979E38F1B@localhost.localdomain Discussion: https://postgr.es/m/457969A9-5D24-422F-B09E-F388053DE028@localhost.localdomain --- .../pg_stat_statements/expected/sqljson.out | 26 +++++++++---------- src/backend/parser/parse_expr.c | 15 +++++++---- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/contrib/pg_stat_statements/expected/sqljson.out b/contrib/pg_stat_statements/expected/sqljson.out index b300ffba5..7157793fd 100644 --- a/contrib/pg_stat_statements/expected/sqljson.out +++ b/contrib/pg_stat_statements/expected/sqljson.out @@ -166,16 +166,16 @@ SELECT JSON_QUERY(js, '$.a' ERROR ON ERROR) FROM pgss_sqljson_tab; (0 rows) SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; - calls | query --------+--------------------------------------------------------------------- - 2 | SELECT JSON_QUERY(js, $1 $2 ARRAY ON EMPTY) FROM pgss_sqljson_tab - 1 | SELECT JSON_QUERY(js, $1 $2 ARRAY ON ERROR) 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 + calls | query +-------+----------------------------------------------------------------------- 1 | SELECT JSON_QUERY(js, $1 DEFAULT $2 ON EMPTY) 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 ARRAY ON ERROR) 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 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 (9 rows) @@ -306,13 +306,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 | 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 - 1 | 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 + 1 | 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) @@ -699,7 +699,7 @@ SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; 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 05a6b72d4..6ac9243c7 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; @@ -5064,8 +5064,13 @@ GetJsonBehaviorConst(JsonBehaviorType btype, int location) break; } + /* + * Leave the location unset: the constant doesn't appear in the query + * text, so it mustn't look like it does, for example to query jumbling, + * which would make pg_stat_statements replace the behavior keyword with a + * parameter symbol. + */ con = makeConst(typid, -1, InvalidOid, len, val, isnull, isbyval); - con->location = location; return (Node *) con; } -- 2.43.0