| From: | Martin Huang <jjja5555(at)gmail(dot)com> |
|---|---|
| To: | Sami Imseih <samimseih(at)gmail(dot)com> |
| Cc: | PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org> |
| Subject: | Re: pg_stat_statements: Fix nested tracking for implicitly closed cursors |
| Date: | 2026-01-09 22:41:46 |
| Message-ID: | CANWoa2Atk3TTf2bXqyz8vJxD__vrpTxhothFv1VVOyDp+CekYg@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi Sami,
Please add a `PG_TRY` and `PG_FINALLY` to make sure we always reset the
nesting_level.
Also this will break the following scenario
```
BEGIN;
COMMIT;
SELECT 1; -- This will be stored as inner level because COMMIT sets
is_txn_end flag
```
Can we reset is_txn_end at executorStart to solve the problem?
--
Martin Huang
Amazon Web Services
On Fri, Jan 9, 2026 at 1:02 PM Sami Imseih <samimseih(at)gmail(dot)com> wrote:
> Hi,
>
> It was brought to my attention that there is pg_stat_statements
> behavior where an implicitly closed cursor, via COMMIT or END,
> is stored as toplevel for both the utility DECLARE CURSOR
> statement and the underlying query.
>
> ```
> BEGIN;
> DECLARE FOOCUR CURSOR FOR SELECT * from stats_track_tab;
> FETCH FORWARD 1 FROM foocur;
> x
> ---
> (0 rows)
>
> COMMIT;
> SELECT toplevel, calls, query FROM pg_stat_statements
> ORDER BY query COLLATE "C";
> toplevel | calls | query
>
> ----------+-------+----------------------------------------------------------
> t | 1 | BEGIN
> t | 1 | COMMIT
> t | 1 | DECLARE FOOCUR CURSOR FOR SELECT * from stats_track_tab
> t | 1 | DECLARE FOOCUR CURSOR FOR SELECT * from
> stats_track_tab;
> t | 1 | FETCH FORWARD $1 FROM foocur
> t | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
> (6 rows)
> ```
>
> Also, with track_planning enabled, the underlying query is
> stored with toplevel = false for the plans counter and with
> toplevel = true for the calls counter, resulting in an
> additional entry.
>
> ```
> SELECT toplevel, calls, plans, query FROM pg_stat_statements
> ORDER BY query COLLATE "C";
> toplevel | calls | plans | query
>
> ----------+-------+-------+--------------------------------------------------------------
> t | 1 | 0 | BEGIN
> t | 1 | 0 | COMMIT
> t | 1 | 0 | DECLARE FOOCUR CURSOR FOR SELECT * from
> stats_track_tab
> t | 1 | 0 | DECLARE FOOCUR CURSOR FOR SELECT * from
> stats_track_tab;
> f | 0 | 1 | DECLARE FOOCUR CURSOR FOR SELECT * from
> stats_track_tab;
> t | 1 | 0 | FETCH FORWARD $1 FROM foocur
> t | 1 | 0 | SELECT pg_stat_statements_reset() IS NOT NULL
> AS t
> t | 0 | 1 | SELECT toplevel, calls, plans, query FROM
> pg_stat_statements+
> | | | ORDER BY query COLLATE "C"
> ```
>
> The reason this occurs is because PortalCleanup, which triggers
> ExecutorEnd, runs after the ProcessUtility hook. At that point,
> nesting_level has already been reset back to 0.
>
> I am not sure how common this pattern is, but it is probably
> worth fixing. At a minimum, we need tests to show this behavior,
> but we can do better by checking whether we just processed a
> COMMIT statement and setting a flag to let ExecutorEnd increment
> nesting_level. There should be no other way to reach ExecutorEnd
> after a COMMIT besides PortalCleanup, AFAICT. I could be proven
> wrong.
>
> The attached patch fixes this as described above.
>
> Note that, due to f85f6ab051b7, there is a separate issue that
> should be improved. Tracking the underlying SQL of a utility
> statement using the utility statement itself is confusing
> and should be fixed. That is a separate point, but I am
> mentioning it here for clarity.
>
>
> --
> Sami Imseih
> Amazon Web Services
>
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Sami Imseih | 2026-01-10 00:16:26 | Re: explain plans for foreign servers |
| Previous Message | Masahiko Sawada | 2026-01-09 22:08:19 | Re: Refactor replication origin state reset helpers |