From 425a26fc3a48b21e0a4ad7a85321603cb915d704 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 14 Jan 2026 16:29:25 +0000 Subject: [PATCH v2 1/1] pg_stat_statements: Fix nested tracking for implicitly closed cursors When cursors are implicitly closed during COMMIT, pg_stat_statements incorrectly marks the cursor's underlying query as toplevel=true because PortalCleanup, which triggers ExecutorEnd, runs after ProcessUtility and at which point the nesting_level is 0. Fix by adding an is_txn_end flag to detect COMMIT statements and temporarily adjust nesting_level in ExecutorEnd. Add regression tests for both explicit and implicit cursor closure, with track = 'all' and with and without track_planning. Discussion: https://postgr.es/m/CAA5RZ0t2+GLnE_55L2cfCay+L8yPFpdPRVQo-JswUFgXy-EK5Q@mail.gmail.com --- .../pg_stat_statements/expected/squashing.out | 16 +++++++++++++++- contrib/pg_stat_statements/sql/squashing.sql | 5 +++++ src/backend/parser/parse_expr.c | 13 +++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/contrib/pg_stat_statements/expected/squashing.out b/contrib/pg_stat_statements/expected/squashing.out index d5bb67c7222..13c184d5018 100644 --- a/contrib/pg_stat_statements/expected/squashing.out +++ b/contrib/pg_stat_statements/expected/squashing.out @@ -872,6 +872,18 @@ SELECT (ROW(ARRAY[1, 2], ARRAY[1, $1, 3])).*, 1 \bind 1 {1,2} | {1,1,3} | 1 (1 row) +-- Mixed Const and non-Const expressions in the list will not be squashed +SELECT * FROM test_squash a, test_squash b WHERE a.id IN (1, 2, 3, b.id, b.id + 1); + id | data | id | data +----+------+----+------ +(0 rows) + +SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) \bind 1 2 3 1 +; + id | data | id | data +----+------+----+------ +(0 rows) + SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; query | calls -------------------------------------------------------------------------------------------------------------+------- @@ -884,8 +896,10 @@ SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; SELECT (ROW(ARRAY[$1 /*, ... */])).* | 1 SELECT (ROW(ARRAY[$1 /*, ... */], ARRAY[$2 /*, ... */])).* | 1 SELECT (ROW(ARRAY[$1 /*, ... */], ARRAY[$2 /*, ... */])).*, $3 | 1 + SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) | 1 + SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) | 1 SELECT pg_stat_statements_reset() IS NOT NULL AS t | 1 -(8 rows) +(10 rows) -- -- cleanup diff --git a/contrib/pg_stat_statements/sql/squashing.sql b/contrib/pg_stat_statements/sql/squashing.sql index 03b0515f872..c3f2f506955 100644 --- a/contrib/pg_stat_statements/sql/squashing.sql +++ b/contrib/pg_stat_statements/sql/squashing.sql @@ -313,6 +313,11 @@ SELECT (ROW(ARRAY[1, 2], ARRAY[1, 2, 3])).*; SELECT 1, 2, (ROW(ARRAY[1, 2], ARRAY[1, 2, 3])).*, 3, 4; SELECT (ROW(ARRAY[1, 2], ARRAY[1, $1, 3])).*, 1 \bind 1 ; + +-- Mixed Const and non-Const expressions in the list will not be squashed +SELECT * FROM test_squash a, test_squash b WHERE a.id IN (1, 2, 3, b.id, b.id + 1); +SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) \bind 1 2 3 1 +; SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; -- diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 56826db4c26..c964386ccb2 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1132,6 +1132,7 @@ transformAExprIn(ParseState *pstate, A_Expr *a) List *rnonvars; bool useOr; ListCell *l; + bool has_rvars = false; /* * If the operator is <>, combine with AND not OR. @@ -1160,7 +1161,10 @@ transformAExprIn(ParseState *pstate, A_Expr *a) rexprs = lappend(rexprs, rexpr); if (contain_vars_of_level(rexpr, 0)) + { rvars = lappend(rvars, rexpr); + has_rvars = true; + } else rnonvars = lappend(rnonvars, rexpr); } @@ -1225,10 +1229,15 @@ transformAExprIn(ParseState *pstate, A_Expr *a) newa->element_typeid = scalar_type; newa->elements = aexprs; newa->multidims = false; - newa->list_start = a->rexpr_list_start; - newa->list_end = a->rexpr_list_end; newa->location = -1; + /* + * Mark the list as non-squashable if it contains Vars. Jumble + * should only squash lists containing non-Vars. + */ + newa->list_start = has_rvars ? -1 : a->rexpr_list_start; + newa->list_end = has_rvars ? -1 : a->rexpr_list_end; + result = (Node *) make_scalar_array_op(pstate, a->name, useOr, -- 2.43.0