public inbox for [email protected]
help / color / mirror / Atom feedFrom: Sami Imseih <[email protected]>
To: Lukas Fittl <[email protected]>
Cc: Michael Paquier <[email protected]>
Cc: zengman <[email protected]>
Cc: pgsql-hackers <[email protected]>
Cc: Julien Rouhaud <[email protected]>
Subject: Re: Refactor query normalization into core query jumbling
Date: Thu, 26 Mar 2026 12:18:44 -0500
Message-ID: <CAA5RZ0unJX_EwepME9NAuB=u8LjtRCU67FW6yMP07tFkJE5yRA@mail.gmail.com> (raw)
In-Reply-To: <CAP53PkyY7AhQ8jZDwtHokyEfpuHLxxDvtY7EmcasR_n80yLiRQ@mail.gmail.com>
References: <CAA5RZ0tZp5qU0ikZEEqJnxvdSNGh1DWv80sb-k4QAUmiMoOp_Q@mail.gmail.com>
<[email protected]>
<CAA5RZ0vQfE14HyfpoPXDRThVcdCkLgY_HGz+J2qLB9soNUE9QQ@mail.gmail.com>
<[email protected]>
<CAA5RZ0uLS9RrpO2roX7p3EHE4-VJkBsGAB970jrbo1-GRDAi0g@mail.gmail.com>
<[email protected]>
<CAA5RZ0sbWmqdUBFo8JXMJe72pnwjxVY58htJ6pKbwnyQuRctQw@mail.gmail.com>
<CAP53PkxqGbPw5VzpacyJb2wTofYJadCoUmxV8s2o5tHzKznwbg@mail.gmail.com>
<CAA5RZ0t3-srmtaVNfzv0HEzXCa78eQDKYVak+3zgEccnKPZHHA@mail.gmail.com>
<CAA5RZ0t_CJs9ye1PPBy-e0ajZ-6FgKvNEoacW9keCtF_7Y2ycQ@mail.gmail.com>
<CAA5RZ0vEnNvp7_Ni8bWwh4GE53rg_YwNjqynQG=JpNu3YzzAWA@mail.gmail.com>
<CAP53PkzCzeDpg6dCzVrU17LCiCmRSWfEt-5dr4y+VHnTxKr_9w@mail.gmail.com>
<CAA5RZ0tzLhGxR3cCQtPs1=HeGWh0WDDBC1KDTgOh9x9u2gvy1Q@mail.gmail.com>
<CAP53PkyY7AhQ8jZDwtHokyEfpuHLxxDvtY7EmcasR_n80yLiRQ@mail.gmail.com>
> 1) I think "SetConstantLengths" should be renamed to
> "GetConstantLengths", or "CalculateConstantLengths" in 0002, since
> we're no longer modifying JumbleState
Makes sense. I renamed it to ComputeConstantLengths; as this will reflect
that it's more than a getter function, and it's doing actual work. Compute
also seems more consistent with other function names we have out there.
> 2) I wonder if we should export this function in 0002 - that would
> specifically help pg_tracing, which also wants to extract inline
> parameter values in addition to replacing them with a $n parameter
> reference marker - I could also see that being useful for any other
> extension that's interested in pulling out parameter values
> I've also done some testing with two previously mentioned extensions,
> pg_stat_monitor [0] and pg_tracing [1]. Both look like they can be
> adapted to the new method with their regression tests succeeding.
That did cross my mind. I agree with this.
Both of those changes belong in 0002. See the attached v7.
--
Sami Imseih
Amazon Web Services (AWS)
Attachments:
[application/octet-stream] v7-0001-pg_stat_statements-Move-query-normalization-to-co.patch (21.1K, 2-v7-0001-pg_stat_statements-Move-query-normalization-to-co.patch)
download | inline diff:
From e5d441c4e4932b7453293c689c2867c1d9d1d53f Mon Sep 17 00:00:00 2001
From: Ubuntu <[email protected]>
Date: Thu, 18 Dec 2025 18:59:31 +0000
Subject: [PATCH v7 1/2] pg_stat_statements: Move query normalization to core
pg_stat_statements was modifying core generated JumbleState instances
by calling fill_in_constant_lengths() via generate_normalized_query()
to populate constant lengths. This creates problems since extensions
should not modify shared core data structures that may be used by
other extensions, and this could be considered a layering violation.
Move query normalization functions to core as a global function to
fix this. Extensions now call GenerateNormalizedQuery() instead of
directly modifying JumbleState.
This change also addresses code duplication, as several extensions
have been copying generate_normalized_query() to implement their own
normalization. The new core API provides a single, consistent
implementation for all extensions to use.
Functions are renamed to match core naming conventions and comments
are updated for clarity.
Discussion: https://postgr.es/m/CAA5RZ0tZp5qU0ikZEEqJnxvdSNGh1DWv80sb-k4QAUmiMoOp_Q@mail.gmail.com
---
.../pg_stat_statements/pg_stat_statements.c | 273 +-----------------
src/backend/nodes/queryjumblefuncs.c | 253 ++++++++++++++++
src/include/nodes/queryjumble.h | 2 +
3 files changed, 265 insertions(+), 263 deletions(-)
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 6cb14824ec3..c9338442d96 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -50,7 +50,6 @@
#include "access/htup_details.h"
#include "access/parallel.h"
#include "catalog/pg_authid.h"
-#include "common/int.h"
#include "executor/instrument.h"
#include "funcapi.h"
#include "jit/jit.h"
@@ -59,7 +58,6 @@
#include "nodes/queryjumble.h"
#include "optimizer/planner.h"
#include "parser/analyze.h"
-#include "parser/scanner.h"
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -378,11 +376,6 @@ static char *qtext_fetch(Size query_offset, int query_len,
static bool need_gc_qtexts(void);
static void gc_qtexts(void);
static TimestampTz entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only);
-static char *generate_normalized_query(JumbleState *jstate, const char *query,
- int query_loc, int *query_len_p);
-static void fill_in_constant_lengths(JumbleState *jstate, const char *query,
- int query_loc);
-static int comp_location(const void *a, const void *b);
/*
@@ -1360,9 +1353,16 @@ pgss_store(const char *query, int64 queryId,
if (jstate)
{
LWLockRelease(pgss->lock);
- norm_query = generate_normalized_query(jstate, query,
- query_location,
- &query_len);
+
+ /*
+ * generate the normalized query. Note that the normalized
+ * representation may well vary depending on just which
+ * "equivalent" query is used to create the hashtable entry. We
+ * assume this is OK.
+ */
+ norm_query = GenerateNormalizedQuery(jstate, query,
+ query_location,
+ &query_len);
LWLockAcquire(pgss->lock, LW_SHARED);
}
@@ -2824,256 +2824,3 @@ release_lock:
return stats_reset;
}
-
-/*
- * Generate a normalized version of the query string that will be used to
- * represent all similar queries.
- *
- * Note that the normalized representation may well vary depending on
- * just which "equivalent" query is used to create the hashtable entry.
- * We assume this is OK.
- *
- * If query_loc > 0, then "query" has been advanced by that much compared to
- * the original string start, so we need to translate the provided locations
- * to compensate. (This lets us avoid re-scanning statements before the one
- * of interest, so it's worth doing.)
- *
- * *query_len_p contains the input string length, and is updated with
- * the result string length on exit. The resulting string might be longer
- * or shorter depending on what happens with replacement of constants.
- *
- * Returns a palloc'd string.
- */
-static char *
-generate_normalized_query(JumbleState *jstate, const char *query,
- int query_loc, int *query_len_p)
-{
- char *norm_query;
- int query_len = *query_len_p;
- int norm_query_buflen, /* Space allowed for norm_query */
- len_to_wrt, /* Length (in bytes) to write */
- quer_loc = 0, /* Source query byte location */
- n_quer_loc = 0, /* Normalized query byte location */
- last_off = 0, /* Offset from start for previous tok */
- last_tok_len = 0; /* Length (in bytes) of that tok */
- int num_constants_replaced = 0;
-
- /*
- * Get constants' lengths (core system only gives us locations). Note
- * this also ensures the items are sorted by location.
- */
- fill_in_constant_lengths(jstate, query, query_loc);
-
- /*
- * Allow for $n symbols to be longer than the constants they replace.
- * Constants must take at least one byte in text form, while a $n symbol
- * certainly isn't more than 11 bytes, even if n reaches INT_MAX. We
- * could refine that limit based on the max value of n for the current
- * query, but it hardly seems worth any extra effort to do so.
- */
- norm_query_buflen = query_len + jstate->clocations_count * 10;
-
- /* Allocate result buffer */
- norm_query = palloc(norm_query_buflen + 1);
-
- for (int i = 0; i < jstate->clocations_count; i++)
- {
- int off, /* Offset from start for cur tok */
- tok_len; /* Length (in bytes) of that tok */
-
- /*
- * If we have an external param at this location, but no lists are
- * being squashed across the query, then we skip here; this will make
- * us print the characters found in the original query that represent
- * the parameter in the next iteration (or after the loop is done),
- * which is a bit odd but seems to work okay in most cases.
- */
- if (jstate->clocations[i].extern_param && !jstate->has_squashed_lists)
- continue;
-
- off = jstate->clocations[i].location;
-
- /* Adjust recorded location if we're dealing with partial string */
- off -= query_loc;
-
- tok_len = jstate->clocations[i].length;
-
- if (tok_len < 0)
- continue; /* ignore any duplicates */
-
- /* Copy next chunk (what precedes the next constant) */
- len_to_wrt = off - last_off;
- len_to_wrt -= last_tok_len;
- Assert(len_to_wrt >= 0);
- memcpy(norm_query + n_quer_loc, query + quer_loc, len_to_wrt);
- n_quer_loc += len_to_wrt;
-
- /*
- * And insert a param symbol in place of the constant token; and, if
- * we have a squashable list, insert a placeholder comment starting
- * from the list's second value.
- */
- n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d%s",
- num_constants_replaced + 1 + jstate->highest_extern_param_id,
- jstate->clocations[i].squashed ? " /*, ... */" : "");
- num_constants_replaced++;
-
- /* move forward */
- quer_loc = off + tok_len;
- last_off = off;
- last_tok_len = tok_len;
- }
-
- /*
- * We've copied up until the last ignorable constant. Copy over the
- * remaining bytes of the original query string.
- */
- len_to_wrt = query_len - quer_loc;
-
- Assert(len_to_wrt >= 0);
- memcpy(norm_query + n_quer_loc, query + quer_loc, len_to_wrt);
- n_quer_loc += len_to_wrt;
-
- Assert(n_quer_loc <= norm_query_buflen);
- norm_query[n_quer_loc] = '\0';
-
- *query_len_p = n_quer_loc;
- return norm_query;
-}
-
-/*
- * Given a valid SQL string and an array of constant-location records,
- * fill in the textual lengths of those constants.
- *
- * The constants may use any allowed constant syntax, such as float literals,
- * bit-strings, single-quoted strings and dollar-quoted strings. This is
- * accomplished by using the public API for the core scanner.
- *
- * It is the caller's job to ensure that the string is a valid SQL statement
- * with constants at the indicated locations. Since in practice the string
- * has already been parsed, and the locations that the caller provides will
- * have originated from within the authoritative parser, this should not be
- * a problem.
- *
- * Multiple constants can have the same location. We reset lengths of those
- * past the first to -1 so that they can later be ignored.
- *
- * If query_loc > 0, then "query" has been advanced by that much compared to
- * the original string start, so we need to translate the provided locations
- * to compensate. (This lets us 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 '-'.
- */
-static void
-fill_in_constant_lengths(JumbleState *jstate, const char *query,
- int query_loc)
-{
- LocationLen *locs;
- core_yyscan_t yyscanner;
- core_yy_extra_type yyextra;
- core_YYSTYPE yylval;
- YYLTYPE yylloc;
-
- /*
- * Sort the records by location so that we can process them in order while
- * scanning the query text.
- */
- if (jstate->clocations_count > 1)
- qsort(jstate->clocations, jstate->clocations_count,
- sizeof(LocationLen), comp_location);
- locs = jstate->clocations;
-
- /* initialize the flex scanner --- should match raw_parser() */
- yyscanner = scanner_init(query,
- &yyextra,
- &ScanKeywords,
- ScanKeywordTokens);
-
- /* Search for each constant, in sequence */
- for (int i = 0; i < jstate->clocations_count; i++)
- {
- int loc;
- int tok;
-
- /* Ignore constants after the first one in the same location */
- if (i > 0 && locs[i].location == locs[i - 1].location)
- {
- locs[i].length = -1;
- continue;
- }
-
- if (locs[i].squashed)
- continue; /* squashable list, ignore */
-
- /* Adjust recorded location if we're dealing with partial string */
- loc = locs[i].location - query_loc;
- Assert(loc >= 0);
-
- /*
- * We have a valid location for a constant that's not a dupe. Lex
- * tokens until we find the desired constant.
- */
- for (;;)
- {
- tok = core_yylex(&yylval, &yylloc, yyscanner);
-
- /* We should not hit end-of-string, but if we do, behave sanely */
- if (tok == 0)
- break; /* out of inner for-loop */
-
- /*
- * 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 core system's special-case
- * adjustment of location to that of the leading '-'
- * operator in the event of a negative constant. 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"
- * will have identical normalized query strings.
- */
- tok = core_yylex(&yylval, &yylloc, yyscanner);
- if (tok == 0)
- break; /* out of inner for-loop */
- }
-
- /*
- * We now rely on the assumption that flex has placed a zero
- * byte after the text of the current token in scanbuf.
- */
- locs[i].length = strlen(yyextra.scanbuf + loc);
- break; /* out of inner for-loop */
- }
- }
-
- /* If we hit end-of-string, give up, leaving remaining lengths -1 */
- if (tok == 0)
- break;
- }
-
- scanner_finish(yyscanner);
-}
-
-/*
- * comp_location: comparator for qsorting LocationLen structs by location
- */
-static int
-comp_location(const void *a, const void *b)
-{
- int l = ((const LocationLen *) a)->location;
- int r = ((const LocationLen *) b)->location;
-
- return pg_cmp_s32(l, r);
-}
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 87db8dc1a32..c997a1ef609 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -40,10 +40,12 @@
#include "access/transam.h"
#include "catalog/pg_proc.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "nodes/queryjumble.h"
#include "utils/lsyscache.h"
+#include "parser/scanner.h"
#include "parser/scansup.h"
#define JUMBLE_SIZE 1024 /* query serialization buffer size */
@@ -773,3 +775,254 @@ _jumbleRangeTblEntry_eref(JumbleState *jstate,
*/
JUMBLE_STRING(aliasname);
}
+
+/*
+ * CompLocation: comparator for qsorting LocationLen structs by location
+ */
+static int
+CompLocation(const void *a, const void *b)
+{
+ int l = ((const LocationLen *) a)->location;
+ int r = ((const LocationLen *) b)->location;
+
+ return pg_cmp_s32(l, r);
+}
+
+/*
+ * Given a valid SQL string and an array of constant-location records,
+ * fill in the textual lengths of those constants in JumbleState.
+ *
+ * The constants may use any allowed constant syntax, such as float literals,
+ * bit-strings, single-quoted strings and dollar-quoted strings. This is
+ * accomplished by using the public API for the core scanner.
+ *
+ * Multiple constants can have the same location. We reset lengths of those
+ * past the first to -1 so that they can later be ignored.
+ *
+ * If query_loc > 0, then "query" has been advanced by that much compared to
+ * the original string start, as is the case with multi-statement strings, so
+ * we need to translate the provided locations to compensate. (This lets us
+ * 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 '-'.
+ */
+static void
+SetConstantLengths(JumbleState *jstate, const char *query,
+ int query_loc)
+{
+ LocationLen *locs;
+ core_yyscan_t yyscanner;
+ core_yy_extra_type yyextra;
+ core_YYSTYPE yylval;
+ YYLTYPE yylloc;
+
+ /*
+ * Sort the records by location so that we can process them in order while
+ * scanning the query text.
+ */
+ if (jstate->clocations_count > 1)
+ qsort(jstate->clocations, jstate->clocations_count,
+ sizeof(LocationLen), CompLocation);
+ locs = jstate->clocations;
+
+ /* initialize the flex scanner --- should match raw_parser() */
+ yyscanner = scanner_init(query,
+ &yyextra,
+ &ScanKeywords,
+ ScanKeywordTokens);
+
+ /* Search for each constant, in sequence */
+ for (int i = 0; i < jstate->clocations_count; i++)
+ {
+ int loc;
+ int tok;
+
+ /* Ignore constants after the first one in the same location */
+ if (i > 0 && locs[i].location == locs[i - 1].location)
+ {
+ locs[i].length = -1;
+ continue;
+ }
+
+ if (locs[i].squashed)
+ continue; /* squashable list, ignore */
+
+ /*
+ * Adjust the constant's location using the provided starting location
+ * of the current statement. This allows us to avoid scanning a
+ * multi-statement string from the beginning.
+ */
+ loc = locs[i].location - query_loc;
+ Assert(loc >= 0);
+
+ /*
+ * We have a valid location for a constant that's not a dupe. Lex
+ * tokens until we find the desired constant.
+ */
+ for (;;)
+ {
+ tok = core_yylex(&yylval, &yylloc, yyscanner);
+
+ /* We should not hit end-of-string, but if we do, behave sanely */
+ if (tok == 0)
+ break; /* out of inner for-loop */
+
+ /*
+ * 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" will have identical normalized
+ * query strings.
+ */
+ tok = core_yylex(&yylval, &yylloc, yyscanner);
+ if (tok == 0)
+ break; /* out of inner for-loop */
+ }
+
+ /*
+ * We now rely on the assumption that flex has placed a zero
+ * byte after the text of the current token in scanbuf.
+ */
+ locs[i].length = strlen(yyextra.scanbuf + loc);
+ break; /* out of inner for-loop */
+ }
+ }
+
+ /* If we hit end-of-string, give up, leaving remaining lengths -1 */
+ if (tok == 0)
+ break;
+ }
+
+ scanner_finish(yyscanner);
+}
+
+/*
+ * Generate a normalized version of the query string that will be used to
+ * represent all similar queries.
+ *
+ * *query_len_p contains the input string length, and is updated with
+ * the result string length on exit. The resulting string might be longer
+ * or shorter depending on what happens with replacement of constants.
+ *
+ * Similar to SetConstantLengths, we must also translate the provided location
+ * to compensate for multi-statement strings.
+ *
+ * It is the caller's job to ensure that the string is a valid SQL statement
+ * with the correct constant locations in jstate->clocations. Since in
+ * practice the string has already been parsed using the authoritative parser
+ * and the locations are set by core query jumbling, this should not be a problem.
+ *
+ * Returns a palloc'd string.
+ */
+char *
+GenerateNormalizedQuery(JumbleState *jstate, const char *query,
+ int query_loc, int *query_len_p)
+{
+ char *norm_query;
+ int query_len = *query_len_p;
+ int norm_query_buflen, /* Space allowed for norm_query */
+ len_to_wrt, /* Length (in bytes) to write */
+ quer_loc = 0, /* Source query byte location */
+ n_quer_loc = 0, /* Normalized query byte location */
+ last_off = 0, /* Offset from start for previous tok */
+ last_tok_len = 0; /* Length (in bytes) of that tok */
+ int num_constants_replaced = 0;
+
+ /*
+ * Set constants' lengths in JumbleState, as only locations are set during
+ * DoJumble(). Note this also ensures the items are sorted by location.
+ */
+ SetConstantLengths(jstate, query, query_loc);
+
+ /*
+ * Allow for $n symbols to be longer than the constants they replace.
+ * Constants must take at least one byte in text form, while a $n symbol
+ * certainly isn't more than 11 bytes, even if n reaches INT_MAX. We
+ * could refine that limit based on the max value of n for the current
+ * query, but it hardly seems worth any extra effort to do so.
+ */
+ norm_query_buflen = query_len + jstate->clocations_count * 10;
+
+ /* Allocate result buffer */
+ norm_query = palloc(norm_query_buflen + 1);
+
+ for (int i = 0; i < jstate->clocations_count; i++)
+ {
+ int off, /* Offset from start for cur tok */
+ tok_len; /* Length (in bytes) of that tok */
+
+ /*
+ * If we have an external param at this location, but no lists are
+ * being squashed across the query, then we skip here; this will make
+ * us print the characters found in the original query that represent
+ * the parameter in the next iteration (or after the loop is done),
+ * which is a bit odd but seems to work okay in most cases.
+ */
+ if (jstate->clocations[i].extern_param && !jstate->has_squashed_lists)
+ continue;
+
+ off = jstate->clocations[i].location;
+
+ /* Adjust the constant's location (see SetConstantLengths) */
+ off -= query_loc;
+
+ tok_len = jstate->clocations[i].length;
+
+ if (tok_len < 0)
+ continue; /* ignore any duplicates */
+
+ /* Copy next chunk (what precedes the next constant) */
+ len_to_wrt = off - last_off;
+ len_to_wrt -= last_tok_len;
+ Assert(len_to_wrt >= 0);
+ memcpy(norm_query + n_quer_loc, query + quer_loc, len_to_wrt);
+ n_quer_loc += len_to_wrt;
+
+ /*
+ * And insert a param symbol in place of the constant token; and, if
+ * we have a squashable list, insert a placeholder comment starting
+ * from the list's second value.
+ */
+ n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d%s",
+ num_constants_replaced + 1 + jstate->highest_extern_param_id,
+ jstate->clocations[i].squashed ? " /*, ... */" : "");
+ num_constants_replaced++;
+
+ /* move forward */
+ quer_loc = off + tok_len;
+ last_off = off;
+ last_tok_len = tok_len;
+ }
+
+ /*
+ * We've copied up until the last ignorable constant. Copy over the
+ * remaining bytes of the original query string.
+ */
+ len_to_wrt = query_len - quer_loc;
+
+ Assert(len_to_wrt >= 0);
+ memcpy(norm_query + n_quer_loc, query + quer_loc, len_to_wrt);
+ n_quer_loc += len_to_wrt;
+
+ Assert(n_quer_loc <= norm_query_buflen);
+ norm_query[n_quer_loc] = '\0';
+
+ *query_len_p = n_quer_loc;
+ return norm_query;
+}
diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index 9f81893003c..e354f857c37 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -91,6 +91,8 @@ extern PGDLLIMPORT int compute_query_id;
extern const char *CleanQuerytext(const char *query, int *location, int *len);
+extern char *GenerateNormalizedQuery(JumbleState *jstate, const char *query,
+ int query_loc, int *query_len_p);
extern JumbleState *JumbleQuery(Query *query);
extern void EnableQueryId(void);
--
2.47.3
[application/octet-stream] v7-0002-Make-JumbleState-const-in-post_parse_analyze-hook.patch (9.7K, 3-v7-0002-Make-JumbleState-const-in-post_parse_analyze-hook.patch)
download | inline diff:
From 42360a306bfed6b899bd03e60c69b31db59e2ec0 Mon Sep 17 00:00:00 2001
From: Ubuntu <[email protected]>
Date: Tue, 6 Jan 2026 19:29:01 +0000
Subject: [PATCH v7 2/2] Make JumbleState const in post_parse_analyze hook
Change the post_parse_analyze_hook_type signature to take a const
JumbleState parameter, preventing hooks from modifying the jumble
state during query analysis. This improves API safety by making it
clear that hooks should only read from the jumble state, not modify
it.
Update pg_stat_statements and related functions to match the new const
signature. Refactor SetConstantLengths() to return a newly allocated
LocationLen array instead of modifying the JumbleState, and update
GenerateNormalizedQuery() to work with the const JumbleState and
manage the returned array properly.
Also, rename SetConstantLengths() to ComputeConstantLengths() as it is
no longer setting constant lengths, and it's also computing lengths
based on traversing through the LocationLen data.
Furthermore, ComputeConstantLengths is also exported to allow plug-ins
that wish to perform custom processing of literals from a query.
Discussion: https://postgr.es/m/CAA5RZ0tZp5qU0ikZEEqJnxvdSNGh1DWv80sb-k4QAUmiMoOp_Q@mail.gmail.com
---
.../pg_stat_statements/pg_stat_statements.c | 8 +--
src/backend/nodes/queryjumblefuncs.c | 50 ++++++++++++-------
src/include/nodes/queryjumble.h | 4 +-
src/include/parser/analyze.h | 2 +-
4 files changed, 41 insertions(+), 23 deletions(-)
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index c9338442d96..386a1419232 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -333,7 +333,7 @@ static void pgss_shmem_request(void);
static void pgss_shmem_startup(void);
static void pgss_shmem_shutdown(int code, Datum arg);
static void pgss_post_parse_analyze(ParseState *pstate, Query *query,
- JumbleState *jstate);
+ const JumbleState *jstate);
static PlannedStmt *pgss_planner(Query *parse,
const char *query_string,
int cursorOptions,
@@ -357,7 +357,7 @@ static void pgss_store(const char *query, int64 queryId,
const BufferUsage *bufusage,
const WalUsage *walusage,
const struct JitInstrumentation *jitusage,
- JumbleState *jstate,
+ const JumbleState *jstate,
int parallel_workers_to_launch,
int parallel_workers_launched,
PlannedStmtOrigin planOrigin);
@@ -833,7 +833,7 @@ error:
* Post-parse-analysis hook: mark query with a queryId
*/
static void
-pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate)
+pgss_post_parse_analyze(ParseState *pstate, Query *query, const JumbleState *jstate)
{
if (prev_post_parse_analyze_hook)
prev_post_parse_analyze_hook(pstate, query, jstate);
@@ -1290,7 +1290,7 @@ pgss_store(const char *query, int64 queryId,
const BufferUsage *bufusage,
const WalUsage *walusage,
const struct JitInstrumentation *jitusage,
- JumbleState *jstate,
+ const JumbleState *jstate,
int parallel_workers_to_launch,
int parallel_workers_launched,
PlannedStmtOrigin planOrigin)
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index c997a1ef609..af9417797a3 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -789,8 +789,10 @@ CompLocation(const void *a, const void *b)
}
/*
- * Given a valid SQL string and an array of constant-location records,
- * fill in the textual lengths of those constants in JumbleState.
+ * Given a valid SQL string and an array of constant-location records, return
+ * the textual lengths of those constants in a newly allocated LocationLen
+ * array, or NULL if there are no constants. It is the caller's responsibility
+ * to pfree the result, if necessary.
*
* The constants may use any allowed constant syntax, such as float literals,
* bit-strings, single-quoted strings and dollar-quoted strings. This is
@@ -808,9 +810,9 @@ CompLocation(const void *a, const void *b)
* a negative numeric constant. This precludes there ever being another
* reason for a constant to start with a '-'.
*/
-static void
-SetConstantLengths(JumbleState *jstate, const char *query,
- int query_loc)
+LocationLen *
+ComputeConstantLengths(const JumbleState *jstate, const char *query,
+ int query_loc)
{
LocationLen *locs;
core_yyscan_t yyscanner;
@@ -818,14 +820,20 @@ SetConstantLengths(JumbleState *jstate, const char *query,
core_YYSTYPE yylval;
YYLTYPE yylloc;
+ if (jstate->clocations_count == 0)
+ return NULL;
+
+ /* Copy constant locations to avoid modifying jstate */
+ locs = palloc_array(LocationLen, jstate->clocations_count);
+ memcpy(locs, jstate->clocations, jstate->clocations_count * sizeof(LocationLen));
+
/*
* Sort the records by location so that we can process them in order while
* scanning the query text.
*/
if (jstate->clocations_count > 1)
- qsort(jstate->clocations, jstate->clocations_count,
+ qsort(locs, jstate->clocations_count,
sizeof(LocationLen), CompLocation);
- locs = jstate->clocations;
/* initialize the flex scanner --- should match raw_parser() */
yyscanner = scanner_init(query,
@@ -910,6 +918,8 @@ SetConstantLengths(JumbleState *jstate, const char *query,
}
scanner_finish(yyscanner);
+
+ return locs;
}
/*
@@ -920,7 +930,7 @@ SetConstantLengths(JumbleState *jstate, const char *query,
* the result string length on exit. The resulting string might be longer
* or shorter depending on what happens with replacement of constants.
*
- * Similar to SetConstantLengths, we must also translate the provided location
+ * Similar to ComputeConstantLengths, we must also translate the provided location
* to compensate for multi-statement strings.
*
* It is the caller's job to ensure that the string is a valid SQL statement
@@ -931,7 +941,7 @@ SetConstantLengths(JumbleState *jstate, const char *query,
* Returns a palloc'd string.
*/
char *
-GenerateNormalizedQuery(JumbleState *jstate, const char *query,
+GenerateNormalizedQuery(const JumbleState *jstate, const char *query,
int query_loc, int *query_len_p)
{
char *norm_query;
@@ -943,12 +953,14 @@ GenerateNormalizedQuery(JumbleState *jstate, const char *query,
last_off = 0, /* Offset from start for previous tok */
last_tok_len = 0; /* Length (in bytes) of that tok */
int num_constants_replaced = 0;
+ LocationLen *locs = NULL;
/*
- * Set constants' lengths in JumbleState, as only locations are set during
- * DoJumble(). Note this also ensures the items are sorted by location.
+ * Determine constants' lengths, which are not set during DoJumble(), and
+ * return a sorted copy of jstate's LocationLen data with lengths filled
+ * in.
*/
- SetConstantLengths(jstate, query, query_loc);
+ locs = ComputeConstantLengths(jstate, query, query_loc);
/*
* Allow for $n symbols to be longer than the constants they replace.
@@ -974,15 +986,15 @@ GenerateNormalizedQuery(JumbleState *jstate, const char *query,
* the parameter in the next iteration (or after the loop is done),
* which is a bit odd but seems to work okay in most cases.
*/
- if (jstate->clocations[i].extern_param && !jstate->has_squashed_lists)
+ if (locs[i].extern_param && !jstate->has_squashed_lists)
continue;
- off = jstate->clocations[i].location;
+ off = locs[i].location;
- /* Adjust the constant's location (see SetConstantLengths) */
+ /* Adjust the constant's location (see ComputeConstantLengths) */
off -= query_loc;
- tok_len = jstate->clocations[i].length;
+ tok_len = locs[i].length;
if (tok_len < 0)
continue; /* ignore any duplicates */
@@ -1001,7 +1013,7 @@ GenerateNormalizedQuery(JumbleState *jstate, const char *query,
*/
n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d%s",
num_constants_replaced + 1 + jstate->highest_extern_param_id,
- jstate->clocations[i].squashed ? " /*, ... */" : "");
+ locs[i].squashed ? " /*, ... */" : "");
num_constants_replaced++;
/* move forward */
@@ -1010,6 +1022,10 @@ GenerateNormalizedQuery(JumbleState *jstate, const char *query,
last_tok_len = tok_len;
}
+ /* Clean up temporary location array before any assertions */
+ if (locs)
+ pfree(locs);
+
/*
* We've copied up until the last ignorable constant. Copy over the
* remaining bytes of the original query string.
diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index e354f857c37..8364d58e4cb 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -91,8 +91,10 @@ extern PGDLLIMPORT int compute_query_id;
extern const char *CleanQuerytext(const char *query, int *location, int *len);
-extern char *GenerateNormalizedQuery(JumbleState *jstate, const char *query,
+extern char *GenerateNormalizedQuery(const JumbleState *jstate, const char *query,
int query_loc, int *query_len_p);
+extern LocationLen *ComputeConstantLengths(const JumbleState *jstate, const char *query,
+ int query_loc);
extern JumbleState *JumbleQuery(Query *query);
extern void EnableQueryId(void);
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index e10270ff0ff..b2db6fa4e8c 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -21,7 +21,7 @@
/* Hook for plugins to get control at end of parse analysis */
typedef void (*post_parse_analyze_hook_type) (ParseState *pstate,
Query *query,
- JumbleState *jstate);
+ const JumbleState *jstate);
extern PGDLLIMPORT post_parse_analyze_hook_type post_parse_analyze_hook;
--
2.47.3
view thread (35+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: Refactor query normalization into core query jumbling
In-Reply-To: <CAA5RZ0unJX_EwepME9NAuB=u8LjtRCU67FW6yMP07tFkJE5yRA@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox