The pgrust Query Executor Architecture: Safe Rust Implementation of PostgreSQL's Engine

The pgrust query executor reimplements PostgreSQL's execution engine in safe Rust using an owned QueryDesc handle that bundles all executor state, eliminating C-style lifetime issues while preserving the node-based plan execution model.

The pgrust project (malisper/pgrust) provides a complete Rust-based reimplementation of PostgreSQL's backend, with its query executor architecture representing a fundamental shift from the original C implementation. By centralizing execution state within a single owned handle and leveraging Rust's memory safety guarantees, pgrust eliminates the scattered context objects and complex lifetime management that characterize the traditional PostgreSQL executor. This architecture maintains full compatibility with PostgreSQL's plan node system while providing modern memory safety and extension mechanisms.

Core Architecture Components

The QueryDesc Owner Handle

At the center of the pgrust executor architecture lies the QueryDesc struct, defined in crates/_support/types/nodes/src/querydesc.rs. This structure serves as the single ownership handle for all executor state, replacing PostgreSQL's C-style approach of passing multiple pointers and context objects.

The QueryDesc contains:

  • operation – the command type (SELECT, INSERT, UPDATE, DELETE)
  • snapshot and crosscheck_snapshot – active read snapshots for MVCC
  • dest – the destination receiver for result tuples
  • params – external parameter bindings
  • instrument_options – flags for query instrumentation
  • already_executed – a guard preventing multiple ExecutorRun calls
  • work – a McxOwned<QueryWorkState> bundle that owns the entire executor working set

The work field contains the critical execution state: EStateData (per-query executor state including memory contexts and plan counters), the immutable PlannedStmt produced by the planner, the original source_text for debugging, and the initialized planstate tree.

Memory Management via McxOwned

Unlike PostgreSQL's C implementation that manually manages multiple memory contexts with complex lifetimes, pgrust wraps all per-query state in a McxOwned<QueryWorkState> bundle. This approach leverages Rust's ownership system to ensure that when the QueryDesc drops, the entire per-query memory context and all associated executor structures free in a single, deterministic operation.

Query Execution Lifecycle

Initialization with ExecutorStart

The execution lifecycle begins with CreateQueryDesc, implemented via QueryDesc::create. This method:

  1. Allocates a dedicated per-query "ExecutorState" memory context
  2. Instantiates an empty EState inside that context
  3. Copies read-only inputs (PlannedStmt, source text, snapshots) into the owned bundle

The ExecutorStart phase, driven by the execMain seam implementation in crates/backend/executor/execMain/src/lib.rs, calls ExecInitNode on the top plan node. This recursively builds the full plan-state tree inside the same memory context, populating QueryDesc::work.planstate and establishing the result TupleDesc.

Execution with ExecutorRun

The ExecutorRun phase implements the core driver loop. The executor repeatedly:

  1. Pulls the next tuple from the plan tree via node-specific ExecProcNode implementations
  2. Sends each tuple to the DestReceiver held in QueryDesc::dest

Individual plan nodes reside in dedicated crates such as crates/backend/executor/nodeSeqscan, crates/backend/executor/nodeIndexscan, and crates/backend/executor/nodeModifyTable. These modules implement the standard PostgreSQL ExecProcNode contract, but return Result<PgResult<...>, PgError> instead of using C's longjmp for error handling.

Cleanup and Resource Management

Execution concludes with ExecutorEnd and FreeQueryDesc. When the QueryDesc handle drops, the McxOwned bundle automatically frees the per-query memory context and all associated structures, eliminating the risk of memory leaks or use-after-free errors common in the C implementation.

Seam-Based Extensibility

The execMain_seams Namespace

The pgrust executor implements a dense set of seams (hook points) collected under the execMain_seams namespace in crates/backend/executor/execMain/src/lib.rs. Each seam is a thin wrapper that external crates can override without modifying core executor code.

Key seams include:

  • exec_check_permissions_select – permission validation before SELECT execution
  • exec_foreign_update – foreign-data-wrapper update handling
  • exec_partition_check – partition-specific constraint validation
  • exec_init_result_rel – result-relation initialization for modifications

Integration Examples

Extensions like pg_stat_statements and plpgsql hook into these seams to intercept execution events. For example, crates/pl/plpgsql/src/plpgsql_exec_seams/src/lib.rs consumes executor seams to integrate the PL/pgSQL interpreter, while crates/contrib/pg_stat_statements/src/store.rs hooks into execution points to collect query statistics.

Plan Node Implementation

Each plan node type lives in its own crate under crates/backend/executor/, maintaining the classic PostgreSQL node-based architecture while adding Rust's type safety. Nodes access the current executor state through the execMain_seams bundle, ensuring a single source of truth for the EState and plan-state tree.

The node implementations follow the standard ExecProcNode contract:

  • Initialization: ExecInitNode builds the node-specific state
  • Execution: ExecProcNode returns the next tuple or indicates completion
  • Cleanup: ExecEndNode releases node-specific resources

Practical Example

use pgrust::exec::execmain::Executor;
use pgrust::_support::types::nodes::QueryDesc;
use pgrust::_support::types::destreceiver::DestReceiverHandle;
use pgrust::_support::types::params::ParamListInfo;

// Create the owned query descriptor with all execution state
let mut qdesc = QueryDesc::create(
    &MemoryContext::Current,
    &planned_stmt,
    "SELECT id FROM my_table",
    None,
    None,
    DestReceiverHandle::null(),
    ParamListInfo::empty(),
    0,
).expect("failed to create QueryDesc");

// Execute the standard lifecycle
Executor::executor_start(&mut qdesc).expect("ExecutorStart failed");
Executor::executor_run(&mut qdesc, 0).expect("ExecutorRun failed");
Executor::executor_end(&mut qdesc).expect("ExecutorEnd failed");

// Cleanup occurs automatically when qdesc drops

This example demonstrates the high-level flow: create a QueryDesc that owns all state, run the executor lifecycle, and rely on Rust's drop mechanics for cleanup.

Summary

  • Single ownership model: The QueryDesc handle in crates/_support/types/nodes/src/querydesc.rs owns all executor state via McxOwned<QueryWorkState>, eliminating lifetime issues.
  • Safe error handling: All executor functions return Result types instead of using C-style longjmp, preventing undefined behavior.
  • Seam-based extensions: The execMain_seams namespace provides clean hook points for extensions without core code modification.
  • Node-based execution: Individual crates like nodeSeqscan, nodeIndexscan, and nodeModifyTable implement the standard ExecProcNode contract in safe Rust.
  • Deterministic cleanup: The McxOwned bundle ensures all per-query resources free automatically when execution completes.

Frequently Asked Questions

How does pgrust handle executor memory management differently from PostgreSQL?

PostgreSQL's C implementation uses multiple memory contexts with manual management and complex pointers-as-handles, requiring careful tracking of which context owns which object. Pgrust consolidates all per-query state into a single McxOwned<QueryWorkState> bundle owned by QueryDesc, leveraging Rust's ownership system to ensure automatic, deterministic cleanup when the handle drops.

What are executor seams in pgrust?

Executor seams are hook points defined in the execMain_seams namespace that allow external crates to intercept and modify executor behavior. These seams wrap core operations like permission checks, foreign table updates, and result relation initialization, enabling extensions such as pg_stat_statements and plpgsql to integrate without modifying the executor's core logic.

Which file defines the core execution handle in pgrust?

The core execution handle QueryDesc is defined in crates/_support/types/nodes/src/querydesc.rs. This file contains the struct definition that owns the entire execution state, including the EStateData, PlannedStmt, and plan state tree, all wrapped in the McxOwned<QueryWorkState> bundle.

How does pgrust handle errors in the executor without using longjmp?

Instead of PostgreSQL's longjmp-based error handling, pgrust node implementations return Result<PgResult<...>, PgError> types. This allows the executor to use Rust's standard error propagation mechanisms, ensuring that resources clean up properly through the Drop trait implementation on QueryDesc and its contained McxOwned bundle, even when errors occur during execution.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →