# How pgrust Manages Database Transactions: A Layered Approach to PostgreSQL Integration

> Discover how pgrust manages database transactions with a layered approach for seamless PostgreSQL integration. Safely control atomic operations in Rust and PL/pgSQL.

- Repository: [Michael Malis/pgrust](https://github.com/malisper/pgrust)
- Tags: internals
- Published: 2026-07-13

---

**pgrust implements a layered transaction model that mirrors PostgreSQL's native transaction and sub-transaction semantics while exposing them to Rust through dependency-injection seams, enabling safe control over atomic operations from within PL/pgSQL functions.**

pgrust is a Rust implementation of PostgreSQL's PL/pgSQL runtime that requires sophisticated transaction management to maintain database consistency. The project exposes PostgreSQL's transaction engine to Rust through carefully designed *seams*—dependency-injection points that bridge Rust's type system with PostgreSQL's C-based transaction management in [`xact.c`](https://github.com/malisper/pgrust/blob/main/xact.c). This architecture enables safe handling of nested subtransactions, exception blocks, and explicit commit/rollback operations while preserving PostgreSQL's atomicity guarantees.

## The Layered Architecture of pgrust Transaction Management

pgrust organizes transaction control into four distinct layers, each abstracting PostgreSQL's native functionality into Rust-friendly interfaces.

### Executor Layer (pl_exec)

The PL/pgSQL executor drives SQL work via SPI and manages transaction contexts through the **seams** defined in [`crates/pl/plpgsql/src/plpgsql_exec_seams/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/crates/pl/plpgsql/src/plpgsql_exec_seams/src/lib.rs). This layer provides the primary interface for starting, committing, and aborting transaction contexts within stored procedures.

The executor exposes three critical subtransaction control functions:
- `begin_internal_subtransaction()` – starts a nested (savepoint) transaction
- `release_current_subtransaction()` – commits the nested transaction on the no-error path  
- `rollback_and_release_current_subtransaction()` – aborts the nested transaction when an exception is raised

For explicit transaction control, the executor also provides `spi_commit(chain)` and `spi_rollback(chain)`, which commit or rollback the *outer* transaction from non-atomic SPI blocks (corresponding to PL/pgSQL `COMMIT` and `ROLLBACK` statements).

### Transaction Engine

The transaction engine contains thin wrappers around PostgreSQL's native [`xact.c`](https://github.com/malisper/pgrust/blob/main/xact.c) functions, installed by the handler to eliminate direct C dependencies. Located in [`crates/backend/access/transam/transam_xact/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/crates/backend/access/transam/transam_xact/src/lib.rs) (lines 203-210), this layer maps Rust calls to PostgreSQL's internal transaction primitives:
- `BeginInternalSubTransaction(name)` – low-level start of a subtransaction
- `ReleaseCurrentSubTransaction()` – low-level commit
- `RollbackAndReleaseCurrentSubTransaction()` – low-level abort

### SPI Interface

The SPI interface provides commit and rollback primitives that operate when PL/pgSQL runs inside non-atomic SPI contexts. The `spi_commit(chain)` function maps to `SPI_commit()` or `SPI_commit_and_chain()`, while `spi_rollback(chain)` maps to `SPI_rollback()` or `SPI_rollback_and_chain()`, implementing the optional `AND CHAIN` clause syntax.

### Client-Side Status

External callers inspect transaction state through the libpq façade in [`crates/interfaces/libpq/fe/src/client.rs`](https://github.com/malisper/pgrust/blob/main/crates/interfaces/libpq/fe/src/client.rs) (lines 91-100). The `Client::transaction_status()` method returns a `PgTransactionStatusType` enum with variants `Idle`, `Intrans`, `Inerror`, and `Unknown`, correlating directly with PostgreSQL's `PQTRANS_*` constants.

## How Transaction Control Works in pgrust

The transaction management system handles three distinct operational modes: outer transactions for standard execution, subtransactions for exception handling, and snapshot management for consistency.

### Managing Outer Transactions

When a PL/pgSQL function executes under a normal `BEGIN … END` block, the executor operates within the current PostgreSQL transaction. If the function issues a `COMMIT` or `ROLLBACK` statement, the executor calls the `spi_commit` or `spi_rollback` seams, which invoke PostgreSQL's `SPI_commit` or `SPI_rollback` functions. The `chain` boolean parameter implements the optional `AND CHAIN` clause, allowing the next statement to start a new transaction automatically.

### Handling Sub-transactions and Exception Blocks

PL/pgSQL `BEGIN … EXCEPTION … END` blocks compile to internal subtransactions managed through the executor seams. The implementation follows this sequence:

1. Before executing the block body, the executor calls `begin_internal_subtransaction()` to establish a savepoint
2. If the block completes without error, the executor calls `release_current_subtransaction()` to commit the nested transaction
3. If an exception occurs, the executor calls `rollback_and_release_current_subtransaction()` to abort the subtransaction and restore state

Throughout the block, the executor saves the current `ResourceOwner` via `current_resource_owner` and `set_current_resource_owner` seams, ensuring proper resource cleanup after the subtransaction ends.

### Maintaining Transaction Snapshots

Before any SPI call, the executor snapshots the current transaction state using `GetTransactionSnapshot` via the snapshot manager seams (such as `PushActiveSnapshot` and `PopActiveSnapshot` in `backend/utils/time/snapmgr_seams`). This guarantees that read-only queries see a consistent view even if the outer transaction later rolls back, preventing phantom reads and ensuring ACID compliance.

## Code Examples: Implementing Transaction Control

The following examples demonstrate practical transaction management in pgrust applications.

### Explicit COMMIT from PL/pgSQL

To commit the current transaction from within a stored procedure:

```rust
use plpgsql_exec_seams::spi_commit;

// Commit without chaining
spi_commit(false)?;   // Maps to SPI_commit()

// Commit with AND CHAIN
spi_commit(true)?;    // Maps to SPI_commit_and_chain()

```

### Exception Handling with Subtransactions

Implementing safe exception blocks requires explicit subtransaction management:

```rust
use plpgsql_exec_seams::{
    begin_internal_subtransaction,
    release_current_subtransaction,
    rollback_and_release_current_subtransaction,
};

fn run_exception_block() -> PgResult<()> {
    // Start a nested subtransaction
    begin_internal_subtransaction()?;

    // Execute statements that might error
    let result = some_sql_execution();

    match result {
        Ok(_) => {
            // No error – commit the subtransaction
            release_current_subtransaction()?;
        }
        Err(e) => {
            // Error occurred – roll back and propagate
            rollback_and_release_current_subtransaction()?;
            return Err(e);
        }
    }
    Ok(())
}

```

### Checking Transaction Status from Rust Clients

Applications using the libpq façade can monitor connection state:

```rust
use libpq_fe::client::Client;
use libpq_fe::result::PgTransactionStatusType;

let conn = Client::connect("host=localhost dbname=mydb")?;
match conn.transaction_status() {
    PgTransactionStatusType::Idle => println!("Not in a transaction"),
    PgTransactionStatusType::Intrans => println!("Inside a transaction"),
    PgTransactionStatusType::Inerror => println!("Transaction is aborted"),
    _ => println!("Unknown state"),
}

```

## Summary

pgrust delivers a **transaction-aware execution engine** that bridges Rust's memory safety with PostgreSQL's battle-tested transaction semantics:

- **Layered architecture** separates concerns between the executor, transaction engine, SPI interface, and client visibility
- **Seam-based design** in [`crates/pl/plpgsql/src/plpgsql_exec_seams/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/crates/pl/plpgsql/src/plpgsql_exec_seams/src/lib.rs) eliminates direct C dependencies while preserving PostgreSQL compatibility
- **Subtransaction support** enables proper exception handling through `begin_internal_subtransaction()` and `rollback_and_release_current_subtransaction()`
- **Explicit transaction control** via `spi_commit()` and `spi_rollback()` allows stored procedures to manage transaction boundaries
- **Status visibility** through `Client::transaction_status()` exposes real-time transaction state to calling applications

## Frequently Asked Questions

### How does pgrust handle nested subtransactions?

pgrust handles nested subtransactions through the executor seams in [`crates/pl/plpgsql/src/plpgsql_exec_seams/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/crates/pl/plpgsql/src/plpgsql_exec_seams/src/lib.rs). When entering an exception block, the executor calls `begin_internal_subtransaction()` to create a savepoint. If an error occurs, `rollback_and_release_current_subtransaction()` restores the state to the pre-block condition. This implementation mirrors PostgreSQL's native savepoint mechanism while providing Rust-safe error handling.

### Can Rust code in pgrust execute explicit COMMIT statements?

Yes, Rust code can execute explicit commits through the SPI interface. The `spi_commit(chain)` function in the executor seams maps directly to PostgreSQL's `SPI_commit()` or `SPI_commit_and_chain()` functions. This allows PL/pgSQL functions implemented in Rust to execute `COMMIT` and `COMMIT AND CHAIN` operations even when running inside non-atomic SPI contexts.

### What transaction status information is available to clients?

Clients can query transaction status through the `Client::transaction_status()` method exposed in [`crates/interfaces/libpq/fe/src/client.rs`](https://github.com/malisper/pgrust/blob/main/crates/interfaces/libpq/fe/src/client.rs) (lines 91-100). This returns a `PgTransactionStatusType` enum with four states: `Idle` (not in a transaction), `Intrans` (inside a transaction), `Inerror` (transaction aborted), and `Unknown` (connection failed). These values map directly to PostgreSQL's `PQTRANS_*` constants.

### How does pgrust ensure consistency during exception handling?

pgrust ensures consistency by combining subtransaction rollback with resource owner management. When an exception occurs, the executor calls `rollback_and_release_current_subtransaction()` to abort the subtransaction, then restores the saved `ResourceOwner` via the `set_current_resource_owner` seam. Additionally, the system captures transaction snapshots using `GetTransactionSnapshot` before SPI calls, ensuring read-only queries maintain a consistent view regardless of subsequent rollback operations.