pg_stat_statements: Normalization Bug with + Signs in FETCH/MOVE
Core Problem
The pg_stat_statements extension incorrectly normalizes queries containing a + sign before integer constants in FETCH and MOVE statements. The normalization logic — which replaces literal constants with parameter placeholders ($1, $2, etc.) — fails to recognize that the + sign is part of the integer constant in these contexts, leading to malformed normalized queries.
Concrete Manifestation
When a user executes fetch +1 c, the normalized query becomes fetch $11 c instead of the correct fetch $1 c. What happens is:
- The
+sign is treated as a separate token and replaced with$1 - The integer
1that follows is left in place (or vice versa), producing the nonsensical$11
Similarly, fetch + 1 c produces fetch $1 1 c — the + is replaced but the integer remains as a separate unreplaced token.
The analogous case with - works correctly because the normalization code in pg_stat_statements already has explicit handling for negative values (leading - operators), recognizing that the minus sign and the following integer constitute a single constant to be replaced.
Technical Architecture
How FETCH/MOVE Parsing Differs from Arithmetic Expressions
Michael Paquier initially suggested this might be a general problem affecting expressions like select +1. However, Evan Li demonstrated through debug_print_raw_parse output that the two cases are fundamentally different in how the parser represents them:
SELECT +1: The + is parsed as a unary A_EXPR operator node with:
locationat position 7 (the+)- A child
A_CONSTnode withlocationat position 8 (the1)
These are two distinct nodes in the parse tree, and pg_stat_statements correctly handles them as separate entities (the operator stays, the constant gets normalized).
FETCH +1 c: The grammar's SignedIconst production consumes both the + and the integer, producing a FETCHSTMT node with:
howMany = 1(the resolved integer value)location = 6(pointing to the+sign position)
Here, the + and integer are collapsed into a single parse tree node, but the location field points to the + sign. The normalization code needs to understand that it must replace from the + position through the end of the integer literal — exactly as it already does for the - case.
The Existing - Handling Pattern
The normalization code in pg_stat_statements already contains a special case for negative constants. When it encounters a location pointing to a - character, it treats the minus sign plus the following numeric literal as a single replaceable span. This was implemented to handle doNegate() in gram.y, which adjusts the location of negative constants to include the leading minus.
The SignedIconst production in the grammar performs an analogous adjustment for FETCH/MOVE — it sets the location to the position of the + or - sign. The - case works by accident (it matches the existing negative-constant handling), but the + case has no corresponding handler.
Proposed Fix
The patch extends the existing - handling logic to also recognize a + at the constant's location. When the normalization code finds that query[loc] == '+', it should skip forward past the + (and any intervening whitespace) to find the actual integer literal, then replace the entire span from + through the end of the integer with a single parameter placeholder.
This is architecturally minimal — it adds a parallel case to an already-established pattern — and ensures that FETCH +1 c normalizes to fetch $1 c, matching the behavior of fetch -1 c → fetch $1 c.
Design Considerations
-
Scope of the fix: The fix is correctly scoped to FETCH/MOVE because those are the only statements where
SignedIconstproduces a node with location pointing at a+sign. General expressions likeSELECT +1parse differently and don't need this fix. -
Query grouping implications: Without this fix, queries like
FETCH +1 candFETCH +2 cmight not be grouped together inpg_stat_statements, defeating the purpose of normalization. -
Backward compatibility: This is a pure bugfix with no user-visible behavioral change beyond producing correct normalized output.