| From: | Staroverov Ilja <i(dot)staroverov(at)ftdata(dot)ru> |
|---|---|
| To: | "pgsql-bugs(at)lists(dot)postgresql(dot)org" <pgsql-bugs(at)lists(dot)postgresql(dot)org> |
| Subject: | pg_stat_statements replaces SQL/JSON ON EMPTY / ON ERROR keywords with parameters |
| Date: | 2026-10-16 12:48:55 |
| Message-ID: | 086A90EE-9771-4387-8B46-BF84A610F04F@localhost.localdomain |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
Hi,
When pg_stat_statements normalizes a query that uses an ON EMPTY or ON
ERROR clause of the SQL/JSON query functions, the behavior keyword is
replaced with a parameter symbol. On 18.6:
CREATE TABLE jt (js jsonb);
SELECT JSON_QUERY(js, '$.b' EMPTY ARRAY ON EMPTY) FROM jt;
SELECT JSON_EXISTS(js, 'strict $.b' TRUE ON ERROR) FROM jt;
SELECT JSON_EXISTS(js, 'strict $.b' FALSE ON ERROR) FROM jt;
SELECT query FROM pg_stat_statements WHERE query LIKE 'SELECT JSON%';
query
-----------------------------------------------------
SELECT JSON_EXISTS(js, $1 $2 ON ERROR) FROM jt
SELECT JSON_EXISTS(js, $1 $2 ON ERROR) FROM jt
SELECT JSON_QUERY(js, $1 $2 ARRAY ON EMPTY) FROM jt
The same happens with EMPTY OBJECT and NULL. DEFAULT <expr> is
normalized correctly. The normalized text is not valid SQL, so it can't
be used with EXPLAIN (GENERIC_PLAN):
EXPLAIN (GENERIC_PLAN) SELECT JSON_QUERY(js, $1 $2 ARRAY ON EMPTY) FROM jt;
ERROR: syntax error at or near "$2"
and the entries for TRUE ON ERROR and FALSE ON ERROR, which do opposite
things, show the same text.
The documentation says that a constant is replaced by a parameter
symbol "when a constant's value has been ignored for purposes of
matching the query to other queries". That's not the case here: these
keywords are not constants in the query text, and the behavior is part
of the query ID. TRUE ON ERROR and FALSE ON ERROR, EMPTY ARRAY and
EMPTY OBJECT, NULL ON EMPTY and ERROR ON EMPTY all give different query
IDs, unlike, say, SELECT true and SELECT false, which are merged and
shown as SELECT $1.
The cause is in GetJsonBehaviorConst(), which builds the Const for such
a behavior and sets its location to that of the behavior clause,
although the Const doesn't correspond to a constant in the query text.
Query jumbling records the locations of Consts, so pg_stat_statements
takes the keyword at that location for a constant. This comes from
6185c9737cf4, so 17 and later are affected. Master, as of 57926d757,
behaves the same.
The attached patch leaves the location of the Const unset. The only
other use of it was the error position when coercing the Const fails,
in transformJsonBehavior(), which now uses the location of the behavior
clause directly, so error messages don't change. With the patch, the
queries above are normalized as
SELECT JSON_QUERY(js, $1 EMPTY ARRAY ON EMPTY) FROM jt
SELECT JSON_EXISTS(js, $1 TRUE ON ERROR) FROM jt
and EXPLAIN (GENERIC_PLAN) works on them. Query IDs don't change,
since the locations of Consts are only recorded, not hashed, so
existing pg_stat_statements entries keep their IDs after a minor
upgrade. The patch adds a pg_stat_statements test, which fails without
the fix. It applies to REL_18_STABLE and REL_19_STABLE. For
REL_17_STABLE, the code applies, but the test has to go elsewhere in
select.sql, since the file ends differently there.
--
Ilya Staroverov
| Attachment | Content-Type | Size |
|---|---|---|
| v1-0001-Don-t-give-the-SQL-JSON-behavior-constants-a-loca.patch | application/octet-stream | 6.3 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Jan Nidzwetzki | 2026-10-16 13:03:59 | Re: START_REPLICATION silently truncates an overlong LSN component |
| Previous Message | Staroverov Ilja | 2026-10-16 11:54:31 | Re: [***DKIM violation***] Re: BUG #19418: SQL/JSON JSON_VALUE() does not conform to ISO/IEC 9075-2:2023(E) 6.34 <JSON value constructor> |