0001-Fix-jumbling-of-squashed-lists-with-row-expansion.patch
application/octet-stream
Filename: 0001-Fix-jumbling-of-squashed-lists-with-row-expansion.patch
Type: application/octet-stream
Part: 0
Message:
Re: Bug in pg_stat_statements
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch 0001
Subject: Fix jumbling of squashed lists with row expansion
| File | + | − |
|---|---|---|
| contrib/pg_stat_statements/expected/squashing.out | 43 | 0 |
| contrib/pg_stat_statements/sql/squashing.sql | 18 | 0 |
| src/backend/nodes/queryjumblefuncs.c | 14 | 4 |
| src/include/nodes/queryjumble.h | 3 | 0 |
From 667d50be65dd1f4307e2a46811695d4da92aa16c Mon Sep 17 00:00:00 2001
From: Ubuntu <ubuntu@ip-172-31-46-230.ec2.internal>
Date: Fri, 24 Oct 2025 00:34:54 +0000
Subject: [PATCH 1/1] Fix jumbling of squashed lists with row expansion
Commit 0f65f3eec introduced squashing of constant lists, but did
not handle row expansion of composite values correctly. As a
result, the same location could be recorded multiple times,
leading to assertion failures in pg_stat_statements during
generate_normalized_query.
This fix tracks the start position of the last recorded constant
and skips it if encountered again during _jumbleElements.
Discussion: https://www.postgresql.org/message-id/2b91e358-0d99-43f7-be44-d2d4dbce37b3%40garret.ru
---
.../pg_stat_statements/expected/squashing.out | 43 +++++++++++++++++++
contrib/pg_stat_statements/sql/squashing.sql | 18 ++++++++
src/backend/nodes/queryjumblefuncs.c | 18 ++++++--
src/include/nodes/queryjumble.h | 3 ++
4 files changed, 78 insertions(+), 4 deletions(-)
diff --git a/contrib/pg_stat_statements/expected/squashing.out b/contrib/pg_stat_statements/expected/squashing.out
index f952f47ef7b..af54ee39ba1 100644
--- a/contrib/pg_stat_statements/expected/squashing.out
+++ b/contrib/pg_stat_statements/expected/squashing.out
@@ -809,6 +809,47 @@ SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
select where $1 IN ($2 /*, ... */) | 2
(2 rows)
+-- composite functions with row expansion
+create table test_composite(x integer);
+CREATE FUNCTION composite_f(a integer[], out x integer, out y integer) returns
+record as $$ begin
+ x = a[1];
+ y = a[2];
+ end;
+$$ language plpgsql;
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t
+---
+ t
+(1 row)
+
+SELECT ((composite_f(array[1, 2]))).* FROM test_composite;
+ x | y
+---+---
+(0 rows)
+
+SELECT ((composite_f(array[1, 2, 3]))).* FROM test_composite;
+ x | y
+---+---
+(0 rows)
+
+SELECT ((composite_f(array[1, 2, 3]))).*, 1, 2, 3, ((composite_f(array[1, 2, 3]))).*, 1, 2
+FROM test_composite
+WHERE x IN (1, 2, 3);
+ x | y | ?column? | ?column? | ?column? | x | y | ?column? | ?column?
+---+---+----------+----------+----------+---+---+----------+----------
+(0 rows)
+
+SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
+ query | calls
+-------------------------------------------------------------------------------------------------------------+-------
+ SELECT ((composite_f(array[$1 /*, ... */]))).* FROM test_composite | 2
+ SELECT ((composite_f(array[$1 /*, ... */]))).*, $2, $3, $4, ((composite_f(array[$5 /*, ... */]))).*, $6, $7+| 1
+ FROM test_composite +|
+ WHERE x IN ($8 /*, ... */) |
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t | 1
+(3 rows)
+
--
-- cleanup
--
@@ -818,3 +859,5 @@ DROP TABLE test_squash_numeric;
DROP TABLE test_squash_bigint;
DROP TABLE test_squash_cast CASCADE;
DROP TABLE test_squash_jsonb;
+DROP TABLE test_composite;
+DROP FUNCTION composite_f;
diff --git a/contrib/pg_stat_statements/sql/squashing.sql b/contrib/pg_stat_statements/sql/squashing.sql
index 53138d125a9..6fc9e0e56b2 100644
--- a/contrib/pg_stat_statements/sql/squashing.sql
+++ b/contrib/pg_stat_statements/sql/squashing.sql
@@ -291,6 +291,22 @@ select where '1' IN ('1'::int::text, '2'::int::text);
select where '1' = ANY (array['1'::int::text, '2'::int::text]);
SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
+-- composite functions with row expansion
+create table test_composite(x integer);
+CREATE FUNCTION composite_f(a integer[], out x integer, out y integer) returns
+record as $$ begin
+ x = a[1];
+ y = a[2];
+ end;
+$$ language plpgsql;
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+SELECT ((composite_f(array[1, 2]))).* FROM test_composite;
+SELECT ((composite_f(array[1, 2, 3]))).* FROM test_composite;
+SELECT ((composite_f(array[1, 2, 3]))).*, 1, 2, 3, ((composite_f(array[1, 2, 3]))).*, 1, 2
+FROM test_composite
+WHERE x IN (1, 2, 3);
+SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
+
--
-- cleanup
--
@@ -300,3 +316,5 @@ DROP TABLE test_squash_numeric;
DROP TABLE test_squash_bigint;
DROP TABLE test_squash_cast CASCADE;
DROP TABLE test_squash_jsonb;
+DROP TABLE test_composite;
+DROP FUNCTION composite_f;
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 31f97151977..c8bad6f3c12 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -193,6 +193,7 @@ InitJumble(void)
jstate->highest_extern_param_id = 0;
jstate->pending_nulls = 0;
jstate->has_squashed_lists = false;
+ jstate->last_start_location = 0;
#ifdef USE_ASSERT_CHECKING
jstate->total_jumble_len = 0;
#endif
@@ -656,10 +657,19 @@ _jumbleElements(JumbleState *jstate, List *elements, Node *node)
if (aexpr->list_start > 0 && aexpr->list_end > 0)
{
- RecordConstLocation(jstate,
- false,
- aexpr->list_start + 1,
- (aexpr->list_end - aexpr->list_start) - 1);
+ /*
+ * There are cases where the same location could be reached by
+ * jumbling multiple times. In that case, we don't want to
+ * record it multiple times.
+ */
+ if (aexpr->list_start != jstate->last_start_location)
+ {
+ RecordConstLocation(jstate,
+ false,
+ aexpr->list_start + 1,
+ (aexpr->list_end - aexpr->list_start) - 1);
+ jstate->last_start_location = aexpr->list_start;
+ }
normalize_list = true;
jstate->has_squashed_lists = true;
}
diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index dcb36dcb44f..06bd17984cb 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -64,6 +64,9 @@ typedef struct JumbleState
/* Whether squashable lists are present */
bool has_squashed_lists;
+ /* The last start location recorded */
+ int last_start_location;
+
/*
* Count of the number of NULL nodes seen since last appending a value.
* These are flushed out to the jumble buffer before subsequent appends
--
2.43.0