diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 22dd55c378..5756365c8f 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -228,8 +228,13 @@ SPI_commit(void) _SPI_current->internal_xact = true; - if (ActiveSnapshotSet()) + /* + * Before committing, pop all active snapshots to avoid error about + * "snapshot %p still active". + */ + while (ActiveSnapshotSet()) PopActiveSnapshot(); + CommitTransactionCommand(); MemoryContextSwitchTo(oldcontext); diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out b/src/pl/plpgsql/src/expected/plpgsql_transaction.out index 7f008ac57e..274b2c6f17 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_transaction.out +++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out @@ -432,6 +432,25 @@ END; $$; ERROR: EXECUTE of transaction commands is not implemented CONTEXT: PL/pgSQL function inline_code_block line 3 at EXECUTE +-- snapshot handling test +TRUNCATE test2; +CREATE PROCEDURE transaction_test9() +LANGUAGE SQL +AS $$ +INSERT INTO test2 VALUES (42); +$$; +DO LANGUAGE plpgsql $$ +BEGIN + ROLLBACK; + CALL transaction_test9(); +END +$$; +SELECT * FROM test2; + x +---- + 42 +(1 row) + DROP TABLE test1; DROP TABLE test2; DROP TABLE test3; diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 66ecf5eb55..e39f7357bd 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -2075,6 +2075,7 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) ParamListInfo paramLI; LocalTransactionId before_lxid; LocalTransactionId after_lxid; + bool pushed_active_snap = false; int rc; if (expr->plan == NULL) @@ -2090,6 +2091,7 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) /* * The procedure call could end transactions, which would upset the * snapshot management in SPI_execute*, so don't let it do it. + * Instead, we set the snapshots ourselves below. */ expr->plan->no_snapshots = true; } @@ -2098,6 +2100,16 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) before_lxid = MyProc->lxid; + /* + * Set snapshot only for non-read-only procedures, similar to SPI + * behavior. + */ + if (!estate->readonly_func) + { + PushActiveSnapshot(GetTransactionSnapshot()); + pushed_active_snap = true; + } + PG_TRY(); { rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI, @@ -2126,12 +2138,22 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s", expr->query, SPI_result_code_string(rc)); - /* - * If we are in a new transaction after the call, we need to reset some - * internal state. - */ - if (before_lxid != after_lxid) + if (before_lxid == after_lxid) { + /* + * If we are still in the same transaction after the call, pop the + * snapshot that we might have pushed. (If it's a new transaction, + * then all the snapshots are gone already.) + */ + if (pushed_active_snap) + PopActiveSnapshot(); + } + else + { + /* + * If we are in a new transaction after the call, we need to reset + * some internal state. + */ estate->simple_eval_estate = NULL; plpgsql_create_econtext(estate); } diff --git a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql index eddc518bb6..1624aed6ec 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql @@ -354,6 +354,26 @@ BEGIN END; $$; + +-- snapshot handling test +TRUNCATE test2; + +CREATE PROCEDURE transaction_test9() +LANGUAGE SQL +AS $$ +INSERT INTO test2 VALUES (42); +$$; + +DO LANGUAGE plpgsql $$ +BEGIN + ROLLBACK; + CALL transaction_test9(); +END +$$; + +SELECT * FROM test2; + + DROP TABLE test1; DROP TABLE test2; DROP TABLE test3;