Re: postgres_fdw: transaction mode inheritance corner cases

From: Etsuro Fujita <etsuro(dot)fujita(at)gmail(dot)com>
To: Matheus Alcantara <matheusssilv97(at)gmail(dot)com>
Cc: Nikolay Samokhvalov <nik(at)postgres(dot)ai>, Fujii Masao <masao(dot)fujii(at)gmail(dot)com>, PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Re: postgres_fdw: transaction mode inheritance corner cases
Date: 2026-10-13 14:54:13
Message-ID: CAPmGK16ednZrKCzXEiKgcXWVbunnnQwa0wmgExMux5i5Mf149Q@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Sat, Oct 3, 2026 at 6:38 PM Matheus Alcantara
<matheusssilv97(at)gmail(dot)com> wrote:
> 1: the check in 0001 doesn't cover sibling savepoints
>
> Since created_at only keeps the nesting depth, a cursor declared in one
> savepoint and first fetched in a sibling savepoint at the same depth
> passes the check:
>
> begin;
> savepoint s1;
> declare c cursor for select * from ft;
> release s1;
> savepoint s2;
> fetch 1 from c;
> rollback to s2;
> fetch all from c;
> commit;
> ERROR: 34000: cursor "c1" does not exist
> CONTEXT: remote SQL command: CLOSE c1

As I said upthread, I noticed this after sending the patch.

> 2: it rejects cases that work on master
>
> A PL/pgSQL refcursor that is opened outside an exception block and first
> fetched inside it works on master, but fails with 0001 with the new
> error. It also fails if the exception handler swallows errors: the new
> error is hidden inside the block, the portal is left failed, and the
> later fetch outside the block fails with a confusing 'portal "<unnamed
> portal 2>" cannot be run'. On master this works because no remote
> savepoint exists yet, so the remote cursor is created at remote level 1.

This isn't great; I think we should support this as much as possible.

> I'm attaching a prototype 0003 on top of 0001 and 0002 that fixes both
> issues that I've mention. The idea is that the real problem is not the
> local nesting depth, but whether the remote savepoint depth at the time
> of the DECLARE is deeper than the level where the local cursor lives,
> since rolling back a remote savepoint at or below that depth destroys
> the remote cursor. So 0003 declares the remote cursor before
> synchronizing the remote savepoint level, and raises the error only if
> the current remote depth is deeper than the level of the local cursor.
> To know that level it records the subtransaction ID and the nesting
> level when the scan is created; if that subtransaction was already
> released, it conservatively assumes the cursor lives at the top level.

As demonstrated by Nikolay, the 0003 patch doesn't guarantee the
local/remote cursor-level synchronization, still causing an error:
"cursor "cN" does not exist", which isn't good. In addition to that,
I think the proposed handling using the subtransaction ID makes the
fetchability of remote cursors restrictive, reducing the compatibility
with the previous behavior than necessary. So sorry, I don't think
0003 is the right way to go.

I updated the 0001 patch to suppress the "cursor "cN" does not exist"
error while keeping the previous behavior as much as possible,
allowing cursors opened before a savepoint to be first fetched after
that savepoint. Attached is an updated patch. I'm wondering if the
patch should be master-only, though, because 1) it is rather
large/invasive and 2) there is no field report about this open-cursor
issue IIRC. Anyway I'll add more test cases next.

Best regards,
Etsuro Fujita

Attachment Content-Type Size
v3-0001-Fix-open-cursor-handling.patch application/octet-stream 20.4 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message harrisontbooth 2026-10-13 15:03:36 CF6204 test module: explicitly mark the Windows fixture sparse
Previous Message Tomas Vondra 2026-10-13 14:40:36 Re: index prefetching