# How pgrust Handles Query Execution and Expression Evaluation in PL/pgSQL

> Explore how pgrust handles query execution and expression evaluation in PL/pgSQL using a layered seam architecture and bridge functions for efficient processing.

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

---

**pgrust implements PL/pgSQL query execution through a layered "seam" architecture that decouples the executor from PostgreSQL's SPI via runtime-installed bridge functions.**

The `malisper/pgrust` project reimplements PostgreSQL's PL/pgSQL interpreter in Rust, introducing a clean abstraction layer between statement execution and the Server Programming Interface (SPI). This design allows the PL/pgSQL executor to remain agnostic of PostgreSQL internals while still leveraging the native query engine for actual data operations.

## The Seam-Based Architecture

pgrust organizes its execution pipeline into three distinct layers that separate parsing, planning, and physical execution:

- **PL/pgSQL Executor Layer**: Parses statements and builds `PlannedStmt` nodes, but cannot call SPI directly. Instead, it invokes seam functions installed at runtime by the handler.
- **SPI Seam Functions**: Bridge functions declared 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) that translate PL/pgSQL execution requests into SPI calls.
- **SPI Implementation**: The actual PostgreSQL interface logic located in `crates/backend/executor/spi/src/*` that handles plan preparation, parameter binding, and result retrieval.

This architecture ensures that variable scoping, datum conversion, and memory management happen in Rust while the heavy lifting of query execution passes through to PostgreSQL's native engine.

## Query Execution Flow

When executing a static SQL statement from PL/pgSQL, pgrust follows a six-step pipeline that transforms high-level statements into SPI operations.

### Step 1: Statement Compilation

The parser constructs execution nodes such as `PLpgSQL_stmt_execsql` from the PL/pgSQL grammar. These nodes capture the query string, target variables, and execution modifiers.

### Step 2: Seam Invocation

The executor calls `exec_execsql_via_spi` located 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), passing the query and a snapshot of current variable values:

```rust
pub fn exec_execsql_via_spi(
    query: String,
    parse_mode: RawParseMode,
    parse_state: PlpgsqlExprParseState,
    datum_snapshot: Vec<Option<EvalParamValue>>,
    read_only: bool,
    into: bool,
    tcount: i64,
) -> PgResult<ExecsqlResult>;

```

### Step 3: Plan Preparation

The seam forwards to `SPI_prepare_plan` in [`crates/backend/executor/spi/src/prepare.rs`](https://github.com/malisper/pgrust/blob/main/crates/backend/executor/spi/src/prepare.rs), which parses the SQL text and generates an execution plan compatible with PostgreSQL's executor.

### Step 4: Parameter Binding

The `datum_snapshot` vector contains the current values of PL/pgSQL scalar variables mapped to `$dno+1` parameters. The seam constructs a `ParamListInfo` structure to bind these values to the prepared plan.

### Step 5: Query Execution

The SPI layer invokes `SPI_execute_plan_with_paramlist` from [`crates/backend/executor/spi/src/execsql.rs`](https://github.com/malisper/pgrust/blob/main/crates/backend/executor/spi/src/execsql.rs), running the query under the current transaction snapshot.

### Step 6: Result Materialization

Execution returns a Rust struct (`ExecsqlResult` or `RunSelectResult`) containing the SPI return code, processed row count, and optional result rows, allowing the PL/pgSQL executor to continue without direct C struct manipulation.

## Expression Evaluation Flow

PL/pgSQL expression evaluation uses a specialized path that wraps expressions in single-row SELECT statements.

### Evaluation Seam Invocation

The executor calls `exec_eval_expr_via_spi` (line 389 in [`plpgsql_exec_seams/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/plpgsql_exec_seams/src/lib.rs)) when computing scalar expressions:

```rust
pub fn exec_eval_expr_via_spi(
    query: String,
    parse_mode: RawParseMode,
    parse_state: PlpgsqlExprParseState,
    datum_snapshot: Vec<Option<EvalParamValue>>,
    maxtuples: i64,
    read_only: bool,
) -> PgResult<EvalExprResult>;

```

### Single-Row Execution

Unlike general queries, expressions set `maxtuples = 1` and wrap the expression text in a `SELECT` statement. The seam delegates to the same SPI execution path (`SPI_execute_plan_with_paramlist`) but configures the portal for singleton retrieval.

### Datum Conversion

The raw datum returned by SPI converts into `EvalExprResult`, exposing the computed value, `isnull` flag, and type information. This allows PL/pgSQL to handle NULL propagation and type coercion according to variable declarations.

## Dynamic SQL Execution

For `EXECUTE ... USING` statements, pgrust provides `exec_dynexecute_via_spi` (line 298 in [`plpgsql_exec_seams/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/plpgsql_exec_seams/src/lib.rs)). This seam accepts a SQL string with parameter placeholders and a vector of `DynUsingParam` structs containing runtime values:

```rust
pub fn exec_dynexecute_via_spi(
    query: String,
    parse_mode: RawParseMode,
    parse_state: PlpgsqlExprParseState,
    using_params: Vec<DynUsingParam>,
    read_only: bool,
    into: bool,
    tcount: i64,
) -> PgResult<DynExecuteResult>;

```

The implementation routes through `SPI_execute_extended` in the backend SPI crate, handling plan caching and parameter binding for dynamically constructed queries.

## Summary

- **Seam abstraction**: pgrust decouples the PL/pgSQL executor from PostgreSQL internals via bridge functions in `plpgsql_exec_seams`.
- **Variable binding**: Scalar datums pass as `datum_snapshot` vectors that convert to PostgreSQL parameter lists at the SPI boundary.
- **Unified SPI path**: Both static queries and expressions ultimately execute through `SPI_execute_plan_with_paramlist` in [`crates/backend/executor/spi/src/execsql.rs`](https://github.com/malisper/pgrust/blob/main/crates/backend/executor/spi/src/execsql.rs).
- **Return type safety**: Results return as Rust structs (`ExecsqlResult`, `EvalExprResult`) that mirror SPI return codes without exposing raw C pointers to the PL/pgSQL layer.
- **Dynamic support**: Runtime SQL strings execute via `exec_dynexecute_via_spi` with explicit parameter binding through `DynUsingParam` structs.

## Frequently Asked Questions

### How does pgrust handle parameter binding for PL/pgSQL variables?

pgrust captures PL/pgSQL variable values into a `datum_snapshot` vector during executor entry. The seam functions translate this snapshot into PostgreSQL's `ParamListInfo` structure before calling SPI, binding variables to `$dno+1` parameter positions. This occurs 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) before the SPI layer receives the request.

### What is the difference between `exec_execsql_via_spi` and `exec_eval_expr_via_spi`?

`exec_execsql_via_spi` handles general SQL statements and returns full result sets via `ExecsqlResult`, while `exec_eval_expr_via_spi` specifically evaluates scalar expressions by wrapping them in single-row SELECT statements and returning `EvalExprResult`. The expression evaluator sets `maxtuples = 1` and focuses on datum extraction rather than row sets.

### Where does the actual query execution happen in pgrust?

The physical execution occurs in [`crates/backend/executor/spi/src/execsql.rs`](https://github.com/malisper/pgrust/blob/main/crates/backend/executor/spi/src/execsql.rs), which implements `SPI_execute_plan_with_paramlist` and `SPI_execute_extended`. These functions interface with PostgreSQL's native executor engine, running plans under the current transaction snapshot and returning results back through the seam layer.

### How does pgrust support dynamic SQL with the `EXECUTE` statement?

pgrust implements dynamic SQL through `exec_dynexecute_via_spi` in the seams crate, which accepts a query string and `DynUsingParam` bindings. This function routes to `SPI_execute_extended` in the backend SPI crate, preparing parametrized plans and binding runtime values supplied via the `USING` clause without exposing raw PostgreSQL plan structures to the PL/pgSQL executor.