From 623417efd4cdbde8cb8f52c294fd24ba639babd1 Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Wed, 9 Sep 2026 12:29:22 +0000 Subject: [PATCH v2 1/1] Fix WAIT FOR rejection errors WAIT FOR is documented as unsupported in transactions above READ COMMITTED, but the implementation only rejected that case after a transaction snapshot had already been taken. As a result, higher isolation levels could reach the generic snapshot-check error instead of failing with an error indicating that the isolation level is not supported. The same "active or registered snapshot" error also included an isolation-level explanation as DETAIL, which is wrong for READ COMMITTED cases such as a held cursor. Reject transaction-snapshot mode explicitly before checking for registered or active snapshots, so REPEATABLE READ and SERIALIZABLE fail with the right message and snapshot-holding READ COMMITTED cases report only the snapshot-specific error. Add recovery coverage for both cases. --- src/backend/commands/wait.c | 11 +++++++++-- src/test/recovery/t/049_wait_for_lsn.pl | 22 ++++++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c index 9ba4c75021e..604e45e36fc 100644 --- a/src/backend/commands/wait.c +++ b/src/backend/commands/wait.c @@ -18,6 +18,7 @@ #include "access/xlog.h" #include "access/xlogrecovery.h" #include "access/xlogwait.h" +#include "access/xact.h" #include "catalog/pg_type_d.h" #include "commands/defrem.h" #include "commands/wait.h" @@ -167,12 +168,18 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, bool isTopLevel, */ InvalidateCatalogSnapshot(); + /* Give up if we are in a transaction that uses a transaction snapshot. */ + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("WAIT FOR cannot be executed within a transaction with an isolation level higher than READ COMMITTED")); + + /* Give up if there is still an active or registered snapshot. */ if (HaveRegisteredOrActiveSnapshot()) ereport(ERROR, errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("WAIT FOR must be called without an active or registered snapshot"), - errdetail("WAIT FOR cannot be executed within a transaction with an isolation level higher than READ COMMITTED.")); + errmsg("WAIT FOR must be called without an active or registered snapshot")); /* * As the result we should hold no snapshot, and correspondingly our xmin diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl index cb7d4d461de..4b6e5194579 100644 --- a/src/test/recovery/t/049_wait_for_lsn.pl +++ b/src/test/recovery/t/049_wait_for_lsn.pl @@ -264,7 +264,7 @@ unlike( # 5. Check mode validation: standby modes error on primary, primary mode errors # on standby, and primary_flush works on primary. Also check that WAIT FOR # triggers an error if called within a function, procedure, anonymous DO block, -# or inside a transaction with an isolation level higher than READ COMMITTED. +# or inside a transaction that uses a transaction snapshot. # Test standby_flush on primary - should error $node_primary->psql( @@ -282,13 +282,31 @@ $node_standby->psql( ok($stderr =~ /recovery is in progress/, "get an error when running primary_flush on the standby"); +$node_standby->psql( + 'postgres', + "BEGIN ISOLATION LEVEL REPEATABLE READ; WAIT FOR LSN '${lsn3}';", + stderr => \$stderr); +ok( $stderr =~ + /WAIT FOR cannot be executed within a transaction with an isolation level higher than READ COMMITTED/, + "get an error when running WAIT FOR in a transaction with an isolation level higher than READ COMMITTED" +); + $node_standby->psql( 'postgres', "BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; WAIT FOR LSN '${lsn3}';", stderr => \$stderr); +ok( $stderr =~ + /WAIT FOR cannot be executed within a transaction with an isolation level higher than READ COMMITTED/, + "get the isolation-level error even after a transaction snapshot has been taken" +); + +$node_standby->psql( + 'postgres', + "BEGIN ISOLATION LEVEL READ COMMITTED; DECLARE c CURSOR FOR SELECT 1; WAIT FOR LSN '${lsn3}';", + stderr => \$stderr); ok( $stderr =~ /WAIT FOR must be called without an active or registered snapshot/, - "get an error when running in a transaction with an isolation level higher than REPEATABLE READ" + "get an error when running WAIT FOR with an active or registered snapshot" ); # Test wrapping WAIT FOR into function, procedure, and anonymous DO block -- -- 2.50.1