Skip to content

Commit ab13c46

Browse files
committed
Further refine _SPI_execute_plan's rule for atomic execution.
Commit 2dc1dea turns out to have been still a brick shy of a load, because CALL statements executing within a plpgsql exception block could still pass the wrong snapshot to stable functions within the CALL's argument list. That happened because standard_ProcessUtility forces isAtomicContext to true if IsTransactionBlock is true, which it always will be inside a subtransaction. Then ExecuteCallStmt would think it does not need to push a new snapshot --- but _SPI_execute_plan didn't do so either, since it thought it was in nonatomic mode. The best fix for this seems to be for _SPI_execute_plan to operate in atomic execution mode if IsSubTransaction() is true, even when the SPI context as a whole is non-atomic. This makes _SPI_execute_plan have the same rules about when non-atomic execution is allowed as _SPI_commit/_SPI_rollback have about when COMMIT/ROLLBACK are allowed, which seems appropriately symmetric. (If anyone ever tries to allow COMMIT/ROLLBACK inside a subtransaction, this would all need to be rethought ... but I'm unconvinced that such a thing could be logically consistent at all.) For further consistency, also check IsSubTransaction() in SPI_inside_nonatomic_context. That does not matter for its one present-day caller StartTransaction, which can't be reached inside a subtransaction. But if any other callers ever arise, they'd presumably want this definition. Per bug #18656 from Alexander Alehin. Back-patch to all supported branches, like previous fixes in this area. Discussion: https://postgr.es/m/18656-cade1780866ef66c@postgresql.org
1 parent 5c1ed0a commit ab13c46

File tree

3 files changed

+47
-4
lines changed

3 files changed

+47
-4
lines changed

src/backend/executor/spi.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,13 +334,13 @@ _SPI_rollback(bool chain)
334334
{
335335
MemoryContext oldcontext = CurrentMemoryContext;
336336

337-
/* see under SPI_commit() */
337+
/* see comments in _SPI_commit() */
338338
if (_SPI_current->atomic)
339339
ereport(ERROR,
340340
(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
341341
errmsg("invalid transaction termination")));
342342

343-
/* see under SPI_commit() */
343+
/* see comments in _SPI_commit() */
344344
if (IsSubTransaction())
345345
ereport(ERROR,
346346
(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
@@ -593,8 +593,11 @@ SPI_inside_nonatomic_context(void)
593593
{
594594
if (_SPI_current == NULL)
595595
return false; /* not in any SPI context at all */
596+
/* these tests must match _SPI_commit's opinion of what's atomic: */
596597
if (_SPI_current->atomic)
597598
return false; /* it's atomic (ie function not procedure) */
599+
if (IsSubTransaction())
600+
return false; /* if within subtransaction, it's atomic */
598601
return true;
599602
}
600603

@@ -2418,9 +2421,12 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
24182421

24192422
/*
24202423
* We allow nonatomic behavior only if options->allow_nonatomic is set
2421-
* *and* the SPI_OPT_NONATOMIC flag was given when connecting.
2424+
* *and* the SPI_OPT_NONATOMIC flag was given when connecting and we are
2425+
* not inside a subtransaction. The latter two tests match whether
2426+
* _SPI_commit() would allow a commit; see there for more commentary.
24222427
*/
2423-
allow_nonatomic = options->allow_nonatomic && !_SPI_current->atomic;
2428+
allow_nonatomic = options->allow_nonatomic &&
2429+
!_SPI_current->atomic && !IsSubTransaction();
24242430

24252431
/*
24262432
* Setup error traceback support for ereport()

src/pl/plpgsql/src/expected/plpgsql_call.out

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,26 @@ NOTICE: f_get_x(1)
585585
NOTICE: f_print_x(1)
586586
NOTICE: f_get_x(2)
587587
NOTICE: f_print_x(2)
588+
-- test in non-atomic context, except exception block is locally atomic
589+
DO $$
590+
BEGIN
591+
BEGIN
592+
UPDATE t_test SET x = x + 1;
593+
RAISE NOTICE 'f_get_x(%)', f_get_x();
594+
CALL f_print_x(f_get_x());
595+
UPDATE t_test SET x = x + 1;
596+
RAISE NOTICE 'f_get_x(%)', f_get_x();
597+
CALL f_print_x(f_get_x());
598+
EXCEPTION WHEN division_by_zero THEN
599+
RAISE NOTICE '%', SQLERRM;
600+
END;
601+
ROLLBACK;
602+
END
603+
$$;
604+
NOTICE: f_get_x(1)
605+
NOTICE: f_print_x(1)
606+
NOTICE: f_get_x(2)
607+
NOTICE: f_print_x(2)
588608
-- test in atomic context
589609
BEGIN;
590610
DO $$

src/pl/plpgsql/src/sql/plpgsql_call.sql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,23 @@ BEGIN
545545
END
546546
$$;
547547

548+
-- test in non-atomic context, except exception block is locally atomic
549+
DO $$
550+
BEGIN
551+
BEGIN
552+
UPDATE t_test SET x = x + 1;
553+
RAISE NOTICE 'f_get_x(%)', f_get_x();
554+
CALL f_print_x(f_get_x());
555+
UPDATE t_test SET x = x + 1;
556+
RAISE NOTICE 'f_get_x(%)', f_get_x();
557+
CALL f_print_x(f_get_x());
558+
EXCEPTION WHEN division_by_zero THEN
559+
RAISE NOTICE '%', SQLERRM;
560+
END;
561+
ROLLBACK;
562+
END
563+
$$;
564+
548565
-- test in atomic context
549566
BEGIN;
550567

0 commit comments

Comments
 (0)