pg_stat_statements: Fix normalization of + signs for FETCH and MOVE

Started by Chao Li10 days ago3 messageshackers
Jump to latest
#1Chao Li
li.evan.chao@gmail.com

Hi,

While revisiting feature “Show sizes of FETCH queries as constants in pg_stat_statements”, I found another issue where “+” is not properly handled.

See this repro:
```
evantest=*# fetch + 1 c;
g
---
(0 rows)

evantest=*# select calls, query from pg_stat_statements where query like 'fetch%';
calls | query
-------+--------------
1 | fetch $1 1 c
(1 row)

evantest=*# SELECT pg_stat_statements_reset();
pg_stat_statements_reset
-------------------------------
2026-06-01 08:59:19.538839+08
(1 row)

evantest=*# fetch +1 c;
g
---
(0 rows)

evantest=*# select calls, query from pg_stat_statements where query like 'fetch%';
calls | query
-------+-------------
1 | fetch $11 c
(1 row)
```

As shown above, the "+" sign is replaced separately from the integer. However, a "-" sign is handled correctly:
```
evantest=*# SELECT pg_stat_statements_reset();
pg_stat_statements_reset
-------------------------------
2026-06-01 09:09:48.776385+08
(1 row)

evantest=*# fetch - 1 c;
g
----
10
(1 row)

evantest=*# select calls, query from pg_stat_statements where query like 'fetch%';
calls | query
-------+------------
1 | fetch $1 c
(1 row)
```

The same issue exists for “MOVE” as well:
```
evantest=*# move relative + 2 c;
MOVE 0
evantest=*# select calls, query from pg_stat_statements where query like 'move%';
calls | query
-------+----------------------
1 | move relative $1 2 c
(1 row)
```

The fix seems simple, as the "-" sign is already handled:
```
/*
* We should find the token position exactly, but if we somehow
* run past it, work with that.
*/
if (yylloc >= loc)
{
if (query[loc] == '-')
{
/*
* It's a negative value - this is the one and only case
* where we replace more than a single token.
*
* Do not compensate for the special-case adjustment of
* location to that of the leading '-' operator in the
* event of a negative constant (see doNegate() in
* gram.y). It is also useful for our purposes to start
* from the minus symbol. In this way, queries like
* "select * from foo where bar = 1" and "select * from
* foo where bar = -2" can be treated similarly.
*/
```

We just need to handle "+" in the same way. See the attached patch for details.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

v1-0001-pg_stat_statements-Fix-normalization-of-signs-for.patchapplication/octet-stream; name=v1-0001-pg_stat_statements-Fix-normalization-of-signs-for.patch; x-unix-mode=0644Download+54-13
#2Michael Paquier
michael@paquier.xyz
In reply to: Chao Li (#1)
Re: pg_stat_statements: Fix normalization of + signs for FETCH and MOVE

On Mon, Jun 01, 2026 at 01:33:40PM +0800, Chao Li wrote:

As shown above, the "+" sign is replaced separately from the
integer. However, a "-" sign is handled correctly:
```
We just need to handle "+" in the same way. See the attached patch for details.

This is not directly related to FETCH and MOVE. For example:
=# select +1;
?column?
----------
1
(1 row)
=# select query from pg_stat_statements where query ~ 'select';
query
------------
select +$1
(1 row)

I don't recall somebody complaining about that..
--
Michael

#3Chao Li
li.evan.chao@gmail.com
In reply to: Michael Paquier (#2)
Re: pg_stat_statements: Fix normalization of + signs for FETCH and MOVE

On Jun 1, 2026, at 14:06, Michael Paquier <michael@paquier.xyz> wrote:

On Mon, Jun 01, 2026 at 01:33:40PM +0800, Chao Li wrote:

As shown above, the "+" sign is replaced separately from the
integer. However, a "-" sign is handled correctly:
```
We just need to handle "+" in the same way. See the attached patch for details.

This is not directly related to FETCH and MOVE. For example:
=# select +1;
?column?
----------
1
(1 row)
=# select query from pg_stat_statements where query ~ 'select';
query
------------
select +$1
(1 row)

I don't recall somebody complaining about that..
--
Michael

Thanks for pointing out the example. I don’t think “select +1” parse “+” in the same way as a FETCH statement.

If we turn on debug_print_raw_parse:
```
evantest=*# set debug_print_raw_parse = 1 and check the raw parse trees:
SET
evantest=*# select +1;
?column?
----------
1
(1 row)
```

This generates a raw parse tree containing:
```
2026-06-01 15:05:08.528 CST [22501] DETAIL: (
{RAWSTMT
:stmt
{SELECTSTMT
:distinctClause <>
:intoClause <>
:targetList (
{RESTARGET
:name <>
:indirection <>
:val
{A_EXPR
:name ("+")
:lexpr <>
:rexpr
{A_CONST
:val 1
:location 8
}
:rexpr_list_start 0
:rexpr_list_end 0
:location 7
}
:location 7
}
)
```

Here, “+” is parsed as a unary operator, A_EXPR’s location is at “+”, and A_CONST’s location is at “1”.

For a FETCH statement:
```
evantest=*# fetch +1 c;
g
---
4
(1 row)
```

The raw parse tree looks like:
```
2026-06-01 15:05:37.993 CST [22501] DETAIL: (
{RAWSTMT
:stmt
{FETCHSTMT
:direction 0
:howMany 1
:portalname c
:ismove false
:direction_keyword 0
:location 6
}
:stmt_location 0
:stmt_len 10
}
)
```

Here, the count is parsed through SignedIconst, and the FETCHSTMT location is at “+".

So, I still think this issue is specific to FETCH and MOVE. However, I realized that the new code comment in my patch was misleading, so I have updated it.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

v2-0001-pg_stat_statements-Fix-normalization-of-signs-for.patchapplication/octet-stream; name=v2-0001-pg_stat_statements-Fix-normalization-of-signs-for.patch; x-unix-mode=0644Download+55-13