From d102aafb6259a6a412803d4b1d8c4f00aa17f67e Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 22 Jun 2021 17:48:39 -0400 Subject: [PATCH] Restore the portal-level snapshot for simple expressions, too. Commits 84f5c2908 et al missed the need to cover plpgsql's "simple expression" code path. If the first thing we execute after a COMMIT/ROLLBACK is one of those, rather than a full-fledged SPI command, we must explicitly do EnsurePortalSnapshotExists() to make sure we have an outer snapshot. Note that it wouldn't be good enough to just push a snapshot for the duration of the expression execution: what comes back might be toasted, so we'd better have a snapshot protecting it. The test case demonstrating this fact cheats a bit by marking a SQL function immutable even though it fetches from a table. That's nothing that users haven't been seen to do, though. Per report from Jim Nasby. Back-patch to v11, like the previous fix. Discussion: https://postgr.es/m/378885e4-f85f-fc28-6c91-c4d1c080bf26@amazon.com --- .../src/expected/plpgsql_transaction.out | 18 ++++++++++++++++ src/pl/plpgsql/src/pl_exec.c | 10 +++++++++ .../plpgsql/src/sql/plpgsql_transaction.sql | 21 +++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out b/src/pl/plpgsql/src/expected/plpgsql_transaction.out index 76cbdca0c5..57ab0bc0e7 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_transaction.out +++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out @@ -430,6 +430,24 @@ SELECT * FROM test1; ---+--- (0 rows) +-- detoast result of simple expression after commit +CREATE TEMP TABLE test4(f1 text); +ALTER TABLE test4 ALTER COLUMN f1 SET STORAGE EXTERNAL; -- disable compression +INSERT INTO test4 SELECT repeat('xyzzy', 2000); +-- immutable mark is a bit of a lie, but it serves to make call a simple expr +-- that will return a still-toasted value +CREATE FUNCTION data_source(i int) RETURNS TEXT LANGUAGE sql +AS 'select f1 from test4' IMMUTABLE; +DO $$ +declare x text; +begin + for i in 1..3 loop + x := data_source(i); + commit; + end loop; + raise notice 'length(x) = %', length(x); +end $$; +NOTICE: length(x) = 10000 -- operations on composite types vs. internal transactions DO LANGUAGE plpgsql $$ declare diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 0ce382e123..96bb77e0b1 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -38,6 +38,7 @@ #include "plpgsql.h" #include "storage/proc.h" #include "tcop/cmdtag.h" +#include "tcop/pquery.h" #include "tcop/tcopprot.h" #include "tcop/utility.h" #include "utils/array.h" @@ -5958,6 +5959,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, expr->expr_simple_lxid == curlxid) return false; + /* + * Ensure that there's a portal-level snapshot, in case this simple + * expression is the first thing evaluated after a COMMIT or ROLLBACK. + * We'd have to do this anyway before executing the expression, so we + * might as well do it now to ensure that any possible replanning doesn't + * need to take a new snapshot. + */ + EnsurePortalSnapshotExists(); + /* * Check to see if the cached plan has been invalidated. If not, and this * is the first use in the current transaction, save a plan refcount in diff --git a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql index cc26788b9a..8e4783c9a5 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql @@ -354,6 +354,27 @@ $$; SELECT * FROM test1; +-- detoast result of simple expression after commit +CREATE TEMP TABLE test4(f1 text); +ALTER TABLE test4 ALTER COLUMN f1 SET STORAGE EXTERNAL; -- disable compression +INSERT INTO test4 SELECT repeat('xyzzy', 2000); + +-- immutable mark is a bit of a lie, but it serves to make call a simple expr +-- that will return a still-toasted value +CREATE FUNCTION data_source(i int) RETURNS TEXT LANGUAGE sql +AS 'select f1 from test4' IMMUTABLE; + +DO $$ +declare x text; +begin + for i in 1..3 loop + x := data_source(i); + commit; + end loop; + raise notice 'length(x) = %', length(x); +end $$; + + -- operations on composite types vs. internal transactions DO LANGUAGE plpgsql $$ declare