mirror of
https://github.com/postgres/postgres.git
synced 2025-06-07 11:02:12 +03:00
pg_stat_statements: Fix parameter number gaps in normalized queries
pg_stat_statements anticipates that certain constant locations may be recorded multiple times and attempts to avoid calculating a length for these locations in fill_in_constant_lengths(). However, during generate_normalized_query() where normalized query strings are generated, these locations are not excluded from consideration. This could increment the parameter number counter for every recorded occurrence at such a location, leading to an incorrect normalization in certain cases with gaps in the numbers reported. For example, take this query: SELECT WHERE '1' IN ('2'::int, '3'::int::text) Before this commit, it would be normalized like that, with gaps in the parameter numbers: SELECT WHERE $1 IN ($3::int, $4::int::text) However the correct, less confusing one should be like that: SELECT WHERE $1 IN ($2::int, $3::int::text) This commit fixes the computation of the parameter numbers to track the number of constants replaced with an $n by a separate counter instead of the iterator used to loop through the list of locations. The underlying query IDs are not changed, neither are the normalized strings for existing PGSS hash entries. New entries with fresh normalized queries would automatically get reshaped based on the new parameter numbering. Issue discovered while discussing a separate problem for HEAD, but this affects all the stable branches. Author: Sami Imseih <samimseih@gmail.com> Discussion: https://postgr.es/m/CAA5RZ0tzxvWXsacGyxrixdhy3tTTDfJQqxyFBRFh31nNHBQ5qA@mail.gmail.com Backpatch-through: 13
This commit is contained in:
parent
089f27cf8a
commit
35a428f30b
@ -68,3 +68,61 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
|
|||||||
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
|
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
|
||||||
(4 rows)
|
(4 rows)
|
||||||
|
|
||||||
|
-- Various parameter numbering patterns
|
||||||
|
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
|
||||||
|
t
|
||||||
|
---
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- Unique query IDs with parameter numbers switched.
|
||||||
|
SELECT WHERE ($1::int, 7) IN ((8, $2::int), ($3::int, 9)) \bind '1' '2' '3' \g
|
||||||
|
--
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT WHERE ($2::int, 10) IN ((11, $3::int), ($1::int, 12)) \bind '1' '2' '3' \g
|
||||||
|
--
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT WHERE $1::int IN ($2::int, $3::int) \bind '1' '2' '3' \g
|
||||||
|
--
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT WHERE $2::int IN ($3::int, $1::int) \bind '1' '2' '3' \g
|
||||||
|
--
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT WHERE $3::int IN ($1::int, $2::int) \bind '1' '2' '3' \g
|
||||||
|
--
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
-- Two groups of two queries with the same query ID.
|
||||||
|
SELECT WHERE '1'::int IN ($1::int, '2'::int) \bind '1' \g
|
||||||
|
--
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT WHERE '4'::int IN ($1::int, '5'::int) \bind '2' \g
|
||||||
|
--
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT WHERE $2::int IN ($1::int, '1'::int) \bind '1' '2' \g
|
||||||
|
--
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT WHERE $2::int IN ($1::int, '2'::int) \bind '3' '4' \g
|
||||||
|
--
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
|
||||||
|
query | calls
|
||||||
|
--------------------------------------------------------------+-------
|
||||||
|
SELECT WHERE $1::int IN ($2::int, $3::int) | 1
|
||||||
|
SELECT WHERE $2::int IN ($1::int, $3::int) | 2
|
||||||
|
SELECT WHERE $2::int IN ($1::int, $3::int) | 2
|
||||||
|
SELECT WHERE $2::int IN ($3::int, $1::int) | 1
|
||||||
|
SELECT WHERE $3::int IN ($1::int, $2::int) | 1
|
||||||
|
SELECT WHERE ($1::int, $4) IN (($5, $2::int), ($3::int, $6)) | 1
|
||||||
|
SELECT WHERE ($2::int, $4) IN (($5, $3::int), ($1::int, $6)) | 1
|
||||||
|
SELECT pg_stat_statements_reset() IS NOT NULL AS t | 1
|
||||||
|
(8 rows)
|
||||||
|
|
||||||
|
@ -238,6 +238,35 @@ SELECT pg_stat_statements_reset() IS NOT NULL AS t;
|
|||||||
t
|
t
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
-- normalization of constants and parameters, with constant locations
|
||||||
|
-- recorded one or more times.
|
||||||
|
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
|
||||||
|
t
|
||||||
|
---
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT WHERE '1' IN ('1'::int, '3'::int::text);
|
||||||
|
--
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT WHERE (1, 2) IN ((1, 2), (2, 3));
|
||||||
|
--
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT WHERE (3, 4) IN ((5, 6), (8, 7));
|
||||||
|
--
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
|
||||||
|
query | calls
|
||||||
|
------------------------------------------------------------------------+-------
|
||||||
|
SELECT WHERE $1 IN ($2::int, $3::int::text) | 1
|
||||||
|
SELECT WHERE ($1, $2) IN (($3, $4), ($5, $6)) | 2
|
||||||
|
SELECT pg_stat_statements_reset() IS NOT NULL AS t | 1
|
||||||
|
SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C" | 0
|
||||||
|
(4 rows)
|
||||||
|
|
||||||
--
|
--
|
||||||
-- queries with locking clauses
|
-- queries with locking clauses
|
||||||
--
|
--
|
||||||
|
@ -2818,9 +2818,7 @@ generate_normalized_query(JumbleState *jstate, const char *query,
|
|||||||
last_off = 0, /* Offset from start for previous tok */
|
last_off = 0, /* Offset from start for previous tok */
|
||||||
last_tok_len = 0; /* Length (in bytes) of that tok */
|
last_tok_len = 0; /* Length (in bytes) of that tok */
|
||||||
bool in_squashed = false; /* in a run of squashed consts? */
|
bool in_squashed = false; /* in a run of squashed consts? */
|
||||||
int skipped_constants = 0; /* Position adjustment of later
|
int num_constants_replaced = 0;
|
||||||
* constants after squashed ones */
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get constants' lengths (core system only gives us locations). Note
|
* Get constants' lengths (core system only gives us locations). Note
|
||||||
@ -2878,7 +2876,7 @@ generate_normalized_query(JumbleState *jstate, const char *query,
|
|||||||
|
|
||||||
/* ... and then a param symbol replacing the constant itself */
|
/* ... and then a param symbol replacing the constant itself */
|
||||||
n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d",
|
n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d",
|
||||||
i + 1 + jstate->highest_extern_param_id - skipped_constants);
|
num_constants_replaced++ + 1 + jstate->highest_extern_param_id);
|
||||||
|
|
||||||
/* In case previous constants were merged away, stop doing that */
|
/* In case previous constants were merged away, stop doing that */
|
||||||
in_squashed = false;
|
in_squashed = false;
|
||||||
@ -2902,12 +2900,10 @@ generate_normalized_query(JumbleState *jstate, const char *query,
|
|||||||
|
|
||||||
/* ... and then start a run of squashed constants */
|
/* ... and then start a run of squashed constants */
|
||||||
n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d /*, ... */",
|
n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d /*, ... */",
|
||||||
i + 1 + jstate->highest_extern_param_id - skipped_constants);
|
num_constants_replaced++ + 1 + jstate->highest_extern_param_id);
|
||||||
|
|
||||||
/* The next location will match the block below, to end the run */
|
/* The next location will match the block below, to end the run */
|
||||||
in_squashed = true;
|
in_squashed = true;
|
||||||
|
|
||||||
skipped_constants++;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -19,3 +19,19 @@ SELECT $1 \bind 'unnamed_val1' \g
|
|||||||
\bind_named stmt1 'stmt1_val1' \g
|
\bind_named stmt1 'stmt1_val1' \g
|
||||||
|
|
||||||
SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
|
SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
|
||||||
|
|
||||||
|
-- Various parameter numbering patterns
|
||||||
|
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
|
||||||
|
-- Unique query IDs with parameter numbers switched.
|
||||||
|
SELECT WHERE ($1::int, 7) IN ((8, $2::int), ($3::int, 9)) \bind '1' '2' '3' \g
|
||||||
|
SELECT WHERE ($2::int, 10) IN ((11, $3::int), ($1::int, 12)) \bind '1' '2' '3' \g
|
||||||
|
SELECT WHERE $1::int IN ($2::int, $3::int) \bind '1' '2' '3' \g
|
||||||
|
SELECT WHERE $2::int IN ($3::int, $1::int) \bind '1' '2' '3' \g
|
||||||
|
SELECT WHERE $3::int IN ($1::int, $2::int) \bind '1' '2' '3' \g
|
||||||
|
-- Two groups of two queries with the same query ID.
|
||||||
|
SELECT WHERE '1'::int IN ($1::int, '2'::int) \bind '1' \g
|
||||||
|
SELECT WHERE '4'::int IN ($1::int, '5'::int) \bind '2' \g
|
||||||
|
SELECT WHERE $2::int IN ($1::int, '1'::int) \bind '1' '2' \g
|
||||||
|
SELECT WHERE $2::int IN ($1::int, '2'::int) \bind '3' '4' \g
|
||||||
|
|
||||||
|
SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
|
||||||
|
@ -79,6 +79,14 @@ DEALLOCATE pgss_test;
|
|||||||
SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
|
SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
|
||||||
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
|
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
|
||||||
|
|
||||||
|
-- normalization of constants and parameters, with constant locations
|
||||||
|
-- recorded one or more times.
|
||||||
|
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
|
||||||
|
SELECT WHERE '1' IN ('1'::int, '3'::int::text);
|
||||||
|
SELECT WHERE (1, 2) IN ((1, 2), (2, 3));
|
||||||
|
SELECT WHERE (3, 4) IN ((5, 6), (8, 7));
|
||||||
|
SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
|
||||||
|
|
||||||
--
|
--
|
||||||
-- queries with locking clauses
|
-- queries with locking clauses
|
||||||
--
|
--
|
||||||
|
Loading…
x
Reference in New Issue
Block a user