From 0307c294588464b100fa688617a407e52484c1f9 Mon Sep 17 00:00:00 2001 From: jian he Date: Mon, 3 Aug 2026 11:43:28 +0800 Subject: [PATCH] Jumble RPR DEFINE variable names A window's DEFINE clause is stored as a list of TargetEntry whose resname is the pattern variable being defined. resname carries query_jumble_ignore (a column alias must not affect the query id), so two queries that differ only in which variable gets which condition -- e.g. "DEFINE A AS p > 50, B AS p < 50" vs "DEFINE B AS p > 50, A AS p < 50" -- collided on the same query id despite being different queries. Mark WindowClause.defineClause custom_query_jumble and add _jumbleWindowClause_defineClause(), which jumbles the list exactly as the generated code did and then adds each pattern variable name. A window with no DEFINE clause therefore jumbles to the same bytes as before and keeps its query id. TargetEntry.resname is left query_jumble_ignore, so ordinary target lists are unaffected. --- .../pg_stat_statements/expected/select.out | 52 +++++++++++++++++++ contrib/pg_stat_statements/sql/select.sql | 27 ++++++++++ src/backend/nodes/queryjumblefuncs.c | 36 +++++++++++++ src/include/nodes/parsenodes.h | 2 +- 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out index a069119c790..51d0069bd5c 100644 --- a/contrib/pg_stat_statements/expected/select.out +++ b/contrib/pg_stat_statements/expected/select.out @@ -386,6 +386,58 @@ SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; DROP TABLE pgss_a, pgss_b CASCADE; -- +-- queries with a row pattern recognition window +-- +CREATE TABLE pgss_rpr (id integer); +SELECT pg_stat_statements_reset() IS NOT NULL AS t; + t +--- + t +(1 row) + +-- DEFINE lists that differ only in which variable name gets which condition +-- must not collide on one query id +SELECT count(*) OVER w FROM pgss_rpr +WINDOW w AS ( + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + AFTER MATCH SKIP PAST LAST ROW + PATTERN (A B) + DEFINE A AS id > 50, B AS id < 50); + count +------- +(0 rows) + +SELECT count(*) OVER w FROM pgss_rpr +WINDOW w AS ( + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + AFTER MATCH SKIP PAST LAST ROW + PATTERN (A B) + DEFINE B AS id > 50, A AS id < 50); + count +------- +(0 rows) + +SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | query +-------+------------------------------------------------------------------------ + 0 | SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C" + 1 | SELECT count(*) OVER w FROM pgss_rpr + + | WINDOW w AS ( + + | ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + + | AFTER MATCH SKIP PAST LAST ROW + + | PATTERN (A B) + + | DEFINE A AS id > $1, B AS id < $2) + 1 | SELECT count(*) OVER w FROM pgss_rpr + + | WINDOW w AS ( + + | ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + + | AFTER MATCH SKIP PAST LAST ROW + + | PATTERN (A B) + + | DEFINE B AS id > $1, A AS id < $2) + 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t +(4 rows) + +DROP TABLE pgss_rpr; +-- -- access to pg_stat_statements_info view -- SELECT pg_stat_statements_reset() IS NOT NULL AS t; diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql index a10d618c034..d981ef4fbeb 100644 --- a/contrib/pg_stat_statements/sql/select.sql +++ b/contrib/pg_stat_statements/sql/select.sql @@ -126,6 +126,33 @@ SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; DROP TABLE pgss_a, pgss_b CASCADE; +-- +-- queries with a row pattern recognition window +-- +CREATE TABLE pgss_rpr (id integer); + +SELECT pg_stat_statements_reset() IS NOT NULL AS t; + +-- DEFINE lists that differ only in which variable name gets which condition +-- must not collide on one query id +SELECT count(*) OVER w FROM pgss_rpr +WINDOW w AS ( + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + AFTER MATCH SKIP PAST LAST ROW + PATTERN (A B) + DEFINE A AS id > 50, B AS id < 50); + +SELECT count(*) OVER w FROM pgss_rpr +WINDOW w AS ( + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + AFTER MATCH SKIP PAST LAST ROW + PATTERN (A B) + DEFINE B AS id > 50, A AS id < 50); + +SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + +DROP TABLE pgss_rpr; + -- -- access to pg_stat_statements_info view -- diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c index 2ce27b9e552..ce2228c4ed8 100644 --- a/src/backend/nodes/queryjumblefuncs.c +++ b/src/backend/nodes/queryjumblefuncs.c @@ -79,6 +79,9 @@ static void _jumbleVariableSetStmt(JumbleState *jstate, Node *node); static void _jumbleRangeTblEntry_eref(JumbleState *jstate, RangeTblEntry *rte, Alias *expr); +static void _jumbleWindowClause_defineClause(JumbleState *jstate, + WindowClause *wc, + List *defineClause); /* * Given a possibly multi-statement source string, confine our attention to the @@ -776,6 +779,39 @@ _jumbleRangeTblEntry_eref(JumbleState *jstate, JUMBLE_STRING(aliasname); } +/* + * Custom query jumble function for WindowClause.defineClause. + * + * The DEFINE clause is stored as a list of TargetEntry, one per row pattern + * variable, whose resname is the variable being defined. TargetEntry.resname + * is marked as query_jumble_ignore, however in a DEFINE clause, the resname + * is semantically significant: it binds the condition to a PATTERN variable. + * "DEFINE A AS p > 50, B AS p < 50" and "DEFINE B AS p > 50, A AS p < 50" + * are different queries and must get different query ids. Therefore jumble + * TargetEntry.resname is necessary for WindowClause.defineClause. + */ +static void +_jumbleWindowClause_defineClause(JumbleState *jstate, + WindowClause *wc, + List *defineClause) +{ + /* + * Jumble the list as the generated code would, so that a window without a + * DEFINE clause keeps its query id. + */ + _jumbleNode(jstate, (Node *) defineClause); + + /* Then add the variable names, which TargetEntry.resname hides. */ + foreach_node(TargetEntry, tle, defineClause) + { + if (tle->resname) + AppendJumble(jstate, (const unsigned char *) tle->resname, + strlen(tle->resname) + 1); + else + AppendJumbleNull(jstate); + } +} + /* * CompLocation: comparator for qsorting LocationLen structs by location */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 5cf03292a02..84ecc4ddc02 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1756,7 +1756,7 @@ typedef struct WindowClause bool initial; /* true if is * initial */ /* Row Pattern DEFINE clause (list of TargetEntry) */ - List *defineClause; + List *defineClause pg_node_attr(custom_query_jumble); /* Row Pattern PATTERN parse tree */ RPRPatternNode *rpPattern; } WindowClause;