Skip to content

Commit 0d83ced

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 cb988b0 commit 0d83ced

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
@@ -328,13 +328,13 @@ _SPI_rollback(bool chain)
328328
{
329329
MemoryContext oldcontext = CurrentMemoryContext;
330330

331-
/* see under SPI_commit() */
331+
/* see comments in _SPI_commit() */
332332
if (_SPI_current->atomic)
333333
ereport(ERROR,
334334
(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
335335
errmsg("invalid transaction termination")));
336336

337-
/* see under SPI_commit() */
337+
/* see comments in _SPI_commit() */
338338
if (IsSubTransaction())
339339
ereport(ERROR,
340340
(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
@@ -587,8 +587,11 @@ SPI_inside_nonatomic_context(void)
587587
{
588588
if (_SPI_current == NULL)
589589
return false; /* not in any SPI context at all */
590+
/* these tests must match _SPI_commit's opinion of what's atomic: */
590591
if (_SPI_current->atomic)
591592
return false; /* it's atomic (ie function not procedure) */
593+
if (IsSubTransaction())
594+
return false; /* if within subtransaction, it's atomic */
592595
return true;
593596
}
594597

@@ -2212,9 +2215,12 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
22122215

22132216
/*
22142217
* We allow nonatomic behavior only if plan->no_snapshots is set
2215-
* *and* the SPI_OPT_NONATOMIC flag was given when connecting.
2218+
* *and* the SPI_OPT_NONATOMIC flag was given when connecting and we are
2219+
* not inside a subtransaction. The latter two tests match whether
2220+
* _SPI_commit() would allow a commit; see there for more commentary.
22162221
*/
2217-
allow_nonatomic = plan->no_snapshots && !_SPI_current->atomic;
2222+
allow_nonatomic = plan->no_snapshots &&
2223+
!_SPI_current->atomic && !IsSubTransaction();
22182224

22192225
/*
22202226
* 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
@@ -490,6 +490,26 @@ NOTICE: f_get_x(1)
490490
NOTICE: f_print_x(1)
491491
NOTICE: f_get_x(2)
492492
NOTICE: f_print_x(2)
493+
-- test in non-atomic context, except exception block is locally atomic
494+
DO $$
495+
BEGIN
496+
BEGIN
497+
UPDATE t_test SET x = x + 1;
498+
RAISE NOTICE 'f_get_x(%)', f_get_x();
499+
CALL f_print_x(f_get_x());
500+
UPDATE t_test SET x = x + 1;
501+
RAISE NOTICE 'f_get_x(%)', f_get_x();
502+
CALL f_print_x(f_get_x());
503+
EXCEPTION WHEN division_by_zero THEN
504+
RAISE NOTICE '%', SQLERRM;
505+
END;
506+
ROLLBACK;
507+
END
508+
$$;
509+
NOTICE: f_get_x(1)
510+
NOTICE: f_print_x(1)
511+
NOTICE: f_get_x(2)
512+
NOTICE: f_print_x(2)
493513
-- test in atomic context
494514
BEGIN;
495515
DO $$

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,23 @@ BEGIN
460460
END
461461
$$;
462462

463+
-- test in non-atomic context, except exception block is locally atomic
464+
DO $$
465+
BEGIN
466+
BEGIN
467+
UPDATE t_test SET x = x + 1;
468+
RAISE NOTICE 'f_get_x(%)', f_get_x();
469+
CALL f_print_x(f_get_x());
470+
UPDATE t_test SET x = x + 1;
471+
RAISE NOTICE 'f_get_x(%)', f_get_x();
472+
CALL f_print_x(f_get_x());
473+
EXCEPTION WHEN division_by_zero THEN
474+
RAISE NOTICE '%', SQLERRM;
475+
END;
476+
ROLLBACK;
477+
END
478+
$$;
479+
463480
-- test in atomic context
464481
BEGIN;
465482

0 commit comments

Comments
 (0)