From 9f2541b4ddc41f0efbdcd1ef79454da778179f0b Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Mon, 26 May 2025 21:41:17 -0500 Subject: [PATCH v6 1/4] Fix broken normalization due to duplicate constant locations 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, these locations are not excluded from consideration and will increment the $n counter for every recorded occurrence of such a location. In practice, this can lead to incorrect normalization in certain cases. select where '1' IN ('2'::int, '3'::int::text) would be normalized to: select where $1 IN ($3, $4) instead of the correct: select where $1 IN ($2, $3) This is because the left-expression, '1' is used as an argument in the OpExpr generated for every element in the IN clause. To correct, 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. --- contrib/pg_stat_statements/pg_stat_statements.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index d8fdf42df79..c58f34e9f30 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2818,9 +2818,7 @@ generate_normalized_query(JumbleState *jstate, const char *query, last_off = 0, /* Offset from start for previous tok */ last_tok_len = 0; /* Length (in bytes) of that tok */ bool in_squashed = false; /* in a run of squashed consts? */ - int skipped_constants = 0; /* Position adjustment of later - * constants after squashed ones */ - + int num_constants_replaced = 0; /* * 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 */ 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_squashed = false; @@ -2902,12 +2900,10 @@ generate_normalized_query(JumbleState *jstate, const char *query, /* ... and then start a run of squashed constants */ 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 */ in_squashed = true; - - skipped_constants++; } else { -- 2.39.5 (Apple Git-154)