At Tue, 29 Mar 2022 17:06:21 +0900 (JST), Kyotaro Horiguchi <horikyota....@gmail.com> wrote in > At Mon, 28 Mar 2022 18:36:46 +0900 (JST), Kyotaro Horiguchi > <horikyota....@gmail.com> wrote in > Then, I found that portal->holdSnapshot is that. I came up with the > attached. It does the follows: > > 1. Teach PlannedStmtRequiresSnapshot() to return true for FetchStmt. > > 2. Use holdSnapshot in RunFromStore if any. > > > The rerpducer is reduced to as small as the following. > > CREATE TABLE t (a text); > INSERT INTO t VALUES('some random text'); > BEGIN; > DECLARE c CURSOR FOR SELECT * FROM t; > FETCH ALL IN c; > > But I haven't come up with a reasonable way to generate the 'some > random text' yet.
I gave up and took a straightforward way to generate one. I don't like that it uses a fixed length for the random text, but anyway it works for now... regards. -- Kyotaro Horiguchi NTT Open Source Software Center
>From 669e72feb29262110fb7085601d1042b781d3a2b Mon Sep 17 00:00:00 2001 From: Kyotaro Horiguchi <horikyota....@gmail.com> Date: Tue, 29 Mar 2022 17:53:39 +0900 Subject: [PATCH v1] Appropriately set snapshot on cursor fetch 84f5c2908d thought that FETCH doesn't need a snapshot, but it is actually needed. FETCH can crash when accessing a toast value. To fix this, teach PlannedStmtRequiresSnapshot that FETCH needs a snapshot then tell RunFromStore to use the snapshot. Reported-by: Erik Rijkers <e...@xs4all.nl> --- src/backend/tcop/pquery.c | 17 ++++++++++++++++- src/test/regress/expected/portals.out | 20 ++++++++++++++++++++ src/test/regress/sql/portals.sql | 22 ++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 5aa5a350f3..08b37fb63d 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -1068,6 +1068,15 @@ RunFromStore(Portal portal, ScanDirection direction, uint64 count, dest->rStartup(dest, CMD_SELECT, portal->tupDesc); + /* Valid holdSnapshot here means it should be used to read this store. */ + if (portal->holdSnapshot) + { + Assert(portal->portalSnapshot == 0); + PushActiveSnapshotWithLevel(portal->holdSnapshot, + portal->createLevel); + portal->portalSnapshot = GetActiveSnapshot(); + } + if (ScanDirectionIsNoMovement(direction)) { /* do nothing except start/stop the destination */ @@ -1114,6 +1123,13 @@ RunFromStore(Portal portal, ScanDirection direction, uint64 count, dest->rShutdown(dest); + if (portal->holdSnapshot) + { + Assert(portal->portalSnapshot == GetActiveSnapshot()); + PopActiveSnapshot(); + portal->portalSnapshot = NULL; + } + ExecDropSingleTupleTableSlot(slot); return current_tuple_count; @@ -1756,7 +1772,6 @@ PlannedStmtRequiresSnapshot(PlannedStmt *pstmt) IsA(utilityStmt, VariableShowStmt) || IsA(utilityStmt, ConstraintsSetStmt) || /* efficiency hacks from here down */ - IsA(utilityStmt, FetchStmt) || IsA(utilityStmt, ListenStmt) || IsA(utilityStmt, NotifyStmt) || IsA(utilityStmt, UnlistenStmt) || diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out index 9da74876e1..fd56859370 100644 --- a/src/test/regress/expected/portals.out +++ b/src/test/regress/expected/portals.out @@ -1536,3 +1536,23 @@ fetch backward all in c2; (3 rows) rollback; +-- Check if snapshot is available for cursor while accessing toast value +begin; +create function r (l int) returns text as $$ +DECLARE + i int; + s text; +BEGIN + s := ''; + for i in 0..l loop + s := s || chr((random() * 254)::int + 1); + end loop; + return s; +END; +$$ language plpgsql; +create table t(a text); +insert into t values(r(4192)); +declare c cursor for select a from t; +-- the following shouldn't crash +fetch all in c \gset result +rollback; diff --git a/src/test/regress/sql/portals.sql b/src/test/regress/sql/portals.sql index eadf6ed942..0dfe0ad981 100644 --- a/src/test/regress/sql/portals.sql +++ b/src/test/regress/sql/portals.sql @@ -581,3 +581,25 @@ declare c2 scroll cursor for select generate_series(1,3) as g; fetch all in c2; fetch backward all in c2; rollback; + +-- Check if snapshot is available for cursor while accessing toast value +begin; +create function r (l int) returns text as $$ +DECLARE + i int; + s text; +BEGIN + s := ''; + for i in 0..l loop + s := s || chr((random() * 254)::int + 1); + end loop; + return s; +END; +$$ language plpgsql; + +create table t(a text); +insert into t values(r(4192)); +declare c cursor for select a from t; +-- the following shouldn't crash +fetch all in c \gset result +rollback; -- 2.27.0