From 9e762146e04fe35872044a45abfa248494bbbf02 Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Mon, 1 Jun 2026 13:21:52 +0800 Subject: [PATCH v2] pg_stat_statements: Fix normalization of + signs for FETCH and MOVE FETCH and MOVE accept a possibly-signed integer count. Query normalization already consumed both tokens of a negative count, but did not do the same for an explicit plus sign. As a result, statements such as "FETCH FORWARD +2 c" could be displayed incorrectly in pg_stat_statements as "FETCH FORWARD $12 c". Treat a leading plus sign like a leading minus sign when determining the length of a constant. Add regression coverage for FETCH and MOVE variants, including whitespace between the sign and the integer. Author: Chao Li --- .../pg_stat_statements/expected/cursors.out | 31 +++++++++++++++++++ contrib/pg_stat_statements/sql/cursors.sql | 10 ++++++ src/backend/nodes/queryjumblefuncs.c | 26 +++++++++------- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/contrib/pg_stat_statements/expected/cursors.out b/contrib/pg_stat_statements/expected/cursors.out index 6afb48ace92..56c2e7ae705 100644 --- a/contrib/pg_stat_statements/expected/cursors.out +++ b/contrib/pg_stat_statements/expected/cursors.out @@ -68,6 +68,37 @@ SELECT pg_stat_statements_reset() IS NOT NULL AS t; t (1 row) +-- Normalization of FETCH and MOVE statements with an explicit plus sign +BEGIN; +DECLARE pgss_plus_cursor SCROLL CURSOR FOR SELECT FROM generate_series(1, 10); +FETCH +2 pgss_plus_cursor; +-- +(2 rows) + +FETCH FORWARD +2 pgss_plus_cursor; +-- +(2 rows) + +MOVE RELATIVE +2 pgss_plus_cursor; +COMMIT; +SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | query +-------+-------------------------------------------------------------------------------- + 1 | BEGIN + 1 | COMMIT + 1 | DECLARE pgss_plus_cursor SCROLL CURSOR FOR SELECT FROM generate_series($1, $2) + 1 | FETCH $1 pgss_plus_cursor + 1 | FETCH FORWARD $1 pgss_plus_cursor + 1 | MOVE RELATIVE $1 pgss_plus_cursor + 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t +(7 rows) + +SELECT pg_stat_statements_reset() IS NOT NULL AS t; + t +--- + t +(1 row) + -- Normalization of FETCH statements BEGIN; DECLARE pgss_cursor CURSOR FOR SELECT FROM generate_series(1, 10); diff --git a/contrib/pg_stat_statements/sql/cursors.sql b/contrib/pg_stat_statements/sql/cursors.sql index 78bb4228433..2d07a743f6b 100644 --- a/contrib/pg_stat_statements/sql/cursors.sql +++ b/contrib/pg_stat_statements/sql/cursors.sql @@ -29,6 +29,16 @@ COMMIT; SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; SELECT pg_stat_statements_reset() IS NOT NULL AS t; +-- Normalization of FETCH and MOVE statements with an explicit plus sign +BEGIN; +DECLARE pgss_plus_cursor SCROLL CURSOR FOR SELECT FROM generate_series(1, 10); +FETCH +2 pgss_plus_cursor; +FETCH FORWARD +2 pgss_plus_cursor; +MOVE RELATIVE +2 pgss_plus_cursor; +COMMIT; +SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset() IS NOT NULL AS t; + -- Normalization of FETCH statements BEGIN; DECLARE pgss_cursor CURSOR FOR SELECT FROM generate_series(1, 10); diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c index 7c63766a51c..43e27df3397 100644 --- a/src/backend/nodes/queryjumblefuncs.c +++ b/src/backend/nodes/queryjumblefuncs.c @@ -812,9 +812,9 @@ CompLocation(const void *a, const void *b) * avoid re-scanning statements before the one of interest, so it's worth * doing.) * - * N.B. There is an assumption that a '-' character at a Const location begins - * a negative numeric constant. This precludes there ever being another - * reason for a constant to start with a '-'. + * N.B. There is an assumption that a '+' or '-' character at a Const location + * begins a signed numeric constant. This precludes there ever being another + * reason for a constant to start with either sign. * * It is the caller's responsibility to free the result, if necessary. */ @@ -891,19 +891,21 @@ ComputeConstantLengths(const JumbleState *jstate, const char *query, */ if (yylloc >= loc) { - if (query[loc] == '-') + if (query[loc] == '-' || query[loc] == '+') { /* - * It's a negative value - this is the one and only case + * It's a signed 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. + * Do not compensate for a location at the leading sign. + * For negative constants this can happen because of + * doNegate() in gram.y. Starting from the sign allows + * queries like "select * from foo where bar = 1" and + * "select * from foo where bar = -2" to be treated + * similarly. Grammar rules using SignedIconst can also + * provide a location at a leading '+', for example in + * "FETCH +2 c". Consuming both tokens normalizes such a + * value as a single constant too. */ tok = core_yylex(&yylval, &yylloc, yyscanner); if (tok == 0) -- 2.50.1 (Apple Git-155)