Skip to main content

Database

Struct Database 

Source
pub struct Database { /* private fields */ }
Expand description

Embedded SPG database handle. Owns an Engine + provides ergonomic wrappers around execute and query. Drops the engine on Drop — no WAL flush / fsync, because v6.10.3 is in-memory only.

Implementations§

Source§

impl Database

Source

pub fn open_in_memory() -> Self

Open a fresh in-memory database. No WAL, no catalog snapshot on disk — perfect for tests + short-lived CLI tools.

Source

pub fn open_path(db_path: impl AsRef<Path>) -> Result<Self, EngineError>

v7.1 — Open or create a persistent database backed by the file at db_path. The WAL lives at db_path + “.wal” (e.g. ./data/spg.db./data/spg.db.wal). Boot path:

  1. If db_path exists, restore the catalog snapshot.
  2. If the WAL exists, replay every record into the restored engine — the same recovery story spg-server uses.
  3. Open the WAL in append+sync mode so subsequent execute() writes durably commit (one fsync per mutation).

Drop writes a final catalog snapshot + truncates the WAL — operators that need a sync barrier at a specific point use checkpoint() explicitly.

Source

pub fn freeze_oldest_to_cold( &mut self, table_name: &str, index_name: &str, max_rows: usize, ) -> Result<FreezeReport, EngineError>

v7.1.4 — freeze the oldest max_rows of table_name’s hot tier into a brand-new cold-tier segment + persist it to disk. Same semantics as spg-server’s freezer thread; embedded just runs the freeze synchronously on the caller’s thread. Persistence + manifest update happen as part of the next checkpoint() (or on Drop).

Source

pub fn set_checkpoint_threshold_bytes(&mut self, bytes: u64)

v7.1 — override the auto-checkpoint WAL-size ceiling for this Database instance. Default is SPG_EMBEDDED_CHECKPOINT_BYTES env (4 MiB if unset); the setter wins. No-op when the database is in-memory.

Source

pub fn memory_stats(&self) -> MemoryStats

v7.31 (memory campaign, round-26 ask 1/ask 4) — per-bucket memory snapshot for the embedding host. Poll it from prod to see where resident bytes live (rows / representation / indexes per table) and to drive host-side shedding before the kernel does it. Same numbers as the server path’s SELECT * FROM spg_memory_stats.

Source

pub fn checkpoint(&mut self) -> Result<(), EngineError>

v7.1 — flush a fresh catalog snapshot to db_path and rotate the WAL. Idempotent; cheap when nothing has happened since the last checkpoint. No-op when the database is in-memory.

CoW-2 (v7.34): the heavy half (serialize + tmp+rename + fsync + marker enqueue + chunk rotation) runs on a dedicated worker thread so the caller’s engine borrow is released after the cheap capture step. This entry point keeps the synchronous contract — it waits for the worker to finish before returning — so existing callers, tests, and operator scripts see no behaviour change; they just pay one extra hop. The non-blocking variant lives at trigger_checkpoint, used by the auto-checkpoint hot path so the write that crossed SPG_EMBEDDED_CHECKPOINT_BYTES doesn’t stall on disk IO.

Called automatically when:

  • the WAL grows past SPG_EMBEDDED_CHECKPOINT_BYTES (default 4 MiB) at the end of an execute() (via trigger_checkpoint, non-blocking), and
  • Drop runs (synchronous; best-effort, failures logged).
Source

pub fn restore(snapshot: &[u8]) -> Result<Self, EngineError>

Restore a database from a previously-captured catalog snapshot. Pairs with Database::snapshot() for round-tripping in-memory state without going through the spg-server WAL.

Source

pub fn snapshot(&self) -> Vec<u8>

Take a catalog snapshot suitable for Database::restore. The bytes are SPG’s canonical catalog envelope (FILE_MAGIC

  • version + payload); round-trips through every released SPG version per the STABILITY contract.
Source

pub fn explain(&self, sql: &str) -> Result<Vec<String>, EngineError>

v7.36 (mailrs ask #4) — programmatic EXPLAIN over sql, returning each line of the QUERY PLAN as an owned String. Skips the WAL (EXPLAIN is read-only) and runs against the engine’s live catalog. Dogfood callers can attach the plan to a report or assert on its shape from a test without having to parse a tabular result themselves.

sql is the inner SELECT (no EXPLAIN prefix); the helper adds it. For SQL with $N placeholders, substitute them into the SQL string before calling — programmatic placeholder-aware EXPLAIN is on the v7.37 plan.

§Errors

Propagates parse errors on sql, plus any engine error the EXPLAIN itself raises (table not found, column not found).

Source

pub fn execute(&mut self, sql: &str) -> Result<QueryResult, EngineError>

Write-side single-statement execute. Runs the SQL through the buffered group-commit pipeline and blocks until the resulting batch’s WAL fsync returns. Read-only statements (SELECT / SHOW / EXPLAIN / BEGIN-COMMIT-ROLLBACK / CHECKPOINT / COMPACT etc.) skip the WAL entirely.

Source

pub fn execute_buffered( &mut self, sql: &str, ) -> Result<(QueryResult, Option<WalTicket>), EngineError>

v7.20 P2 — group-commit write entry. Runs the engine mutation + encodes/enqueues the WAL record, then RETURNS WITHOUT waiting for the fsync. The caller must call WalTicket::wait before treating the write as durable — crucially, the caller can (and should) drop whatever lock guards this Database first, so the next writer’s mutation overlaps this batch’s fsync.

None ticket = nothing hit the WAL (read-only statement, no-op DDL, or in-memory database) — the result is final as returned.

§Errors

Engine errors propagate unchanged. Auto-checkpoint (when the active chunk crosses the threshold) runs inline and may surface IO errors.

Source

pub fn query_typed<T: FromSpgRow>( &mut self, sql: &str, ) -> Result<Vec<T>, EngineError>

v7.3.0 — typed-row variant of Database::query. Each row decodes into a T: FromSpgRow so callers don’t pattern-match on Value themselves. Use spg_row! to generate the impl, or write it by hand.

Source

pub fn query(&mut self, sql: &str) -> Result<Vec<Vec<Value>>, EngineError>

Run a SELECT and return rows as a Vec<Vec<Value>> — strips the column-schema metadata for read-side ergonomics. Errors on non-Rows results (DML / DDL statements should go through execute instead).

Source

pub fn query_with_columns( &mut self, sql: &str, ) -> Result<(Vec<ColumnSchema>, Vec<Vec<Value>>), EngineError>

v7.16.0 — column-aware variant of Self::query. Returns the column schema vec alongside the rows so adapters (the spg-sqlx Row impl most notably) can drive name + type-based column lookups. Errors on non-Rows results identically to query.

Source

pub fn query_prepared_with_columns( &mut self, stmt: &Statement, params: &[Value], ) -> Result<(Vec<ColumnSchema>, Vec<Vec<Value>>), EngineError>

v7.16.0 — column-aware variant of Self::query_prepared. Same shape as query_with_columns but driven from a prepared statement + bound params.

Source

pub const fn engine(&self) -> &Engine

Borrow the underlying engine. Escape hatch for callers that need access to spg-engine APIs not yet surfaced here (transactions, EXPLAIN ANALYZE, etc.).

Source

pub const fn engine_mut(&mut self) -> &mut Engine

Mutable borrow of the underlying engine. Same intent as engine() but for write-side APIs (e.g. inserting directly through Catalog::insert for high-throughput bulk loads that bypass SQL parsing).

Source

pub fn prepare(&mut self, sql: &str) -> Result<Statement, EngineError>

v7.16.0 — parse + plan a SQL string ONCE so subsequent execute_prepared / query_prepared calls can re-bind parameters without re-parsing. The returned Statement is a thin handle around the AST + cached source SQL; it’s Clone so the same plan can drive many bind calls concurrently (each call clones the AST and runs placeholder substitution on the clone — the cached plan stays intact).

Plan caching follows the engine’s existing version-aware rule: a prepared Statement whose statistics version has rolled (ANALYZE ran between prepare and execute) will silently re-prepare under the hood. Callers don’t need to detect this.

Placeholders in the SQL use PG’s $1, $2, … convention. bind-time Values are passed as a slice; arity mismatches surface as EvalError::PlaceholderOutOfRange at execute_prepared time, not here.

§Errors

Surfaces EngineError (parse error / plan rewrite failure) from the underlying Engine::prepare.

Source

pub fn describe( &mut self, sql: &str, ) -> Result<(Vec<u32>, Vec<ColumnSchema>), EngineError>

v7.17.0 Phase 3.P0-66 — describe a SQL string without executing. Returns (parameter_oid_count, output_columns) where output_columns is empty for non-SELECT statements or for SELECT shapes the describe planner can’t resolve (JOIN / subquery / unknown table). Wraps Engine::describe_prepared so the spg-sqlx bridge can surface PG-shape Describe replies for sqlx::query!() compile-time validation.

§Errors

Propagates parse errors from the underlying prepare path.

Source

pub fn execute_prepared( &mut self, stmt: &Statement, params: &[Value], ) -> Result<QueryResult, EngineError>

v7.16.0 — execute a prepared statement with bound parameters. Mirrors Engine::execute_prepared: clones the AST, substitutes $1..$Nparams[0..N-1], runs.

Persistence (WAL fsync + auto-checkpoint) follows the same rules as execute(sql): mutating statements get a WAL record AFTER the in-memory exec succeeds. The WAL record carries the substituted, bind-final SQL, so replay reconstructs the same row state without needing the original prepared Statement to still be alive.

§Errors

Propagates engine errors. Param arity mismatch surfaces as EvalError::PlaceholderOutOfRange.

Source

pub fn execute_prepared_buffered( &mut self, stmt: &Statement, params: &[Value], ) -> Result<(QueryResult, Option<WalTicket>), EngineError>

v7.20 P2 — group-commit variant of Database::execute_prepared. Same contract as Database::execute_buffered: mutation + enqueue happen here; the caller waits on the ticket AFTER releasing whatever lock guards this Database.

§Errors

Engine errors propagate unchanged; inline auto-checkpoint may surface IO errors.

Source

pub fn query_prepared( &mut self, stmt: &Statement, params: &[Value], ) -> Result<Vec<Vec<Value>>, EngineError>

v7.16.0 — run a prepared SELECT with bound params and return rows as Vec<Vec<Value>>, matching query() shape. SELECTs are read-only so this never writes the WAL.

§Errors

Returns Unsupported if the prepared statement isn’t a SELECT (use execute_prepared for DML/DDL).

Source

pub fn prepare_on_snapshot( snapshot: &CatalogSnapshot, sql: &str, ) -> Result<Statement, EngineError>

v7.18 — parse + plan a SQL string against a CatalogSnapshot. Mirror of Database::prepare for the readonly fan-out path: no writer lock taken, no WAL write, no plan-cache mutation. Static-on-Self so callers can dispatch against a snapshot without an &mut Database borrow — AsyncReadHandle::prepare in spg-embedded-tokio is the load-bearing consumer.

§Errors

Propagates EngineError::Parse from the parser.

Source

pub fn execute_prepared_on_snapshot( snapshot: &CatalogSnapshot, stmt: &Statement, params: &[Value], ) -> Result<QueryResult, EngineError>

v7.18 — execute a prepared Statement against a CatalogSnapshot with bound params. Mirror of Database::execute_prepared on the readonly path: writes / DDL hit EngineError::WriteRequired. No WAL write, no writer lock, multiple snapshots can run concurrently — the snapshot is immutable from prepare time.

§Errors

Surfaces EngineError::WriteRequired for non-readonly statements; propagates other engine errors.

Source

pub fn execute_prepared_on_snapshot_with_budget( snapshot: &CatalogSnapshot, stmt: &Statement, params: &[Value], budget_us: u64, ) -> Result<QueryResult, EngineError>

v7.28 (round-22) — deadline-bounded variant of Database::execute_prepared_on_snapshot. Returns EngineError::Cancelled once the budget elapses; the sqlx driver uses this to keep readonly-INLINE execution from monopolising the caller’s async runtime (four slow inbox queries saturated mailrs’s whole tokio pool) and re-runs over the blocking pool on timeout.

§Errors

EngineError::Cancelled on budget expiry; engine errors otherwise.

Source

pub fn describe_on_snapshot( snapshot: &CatalogSnapshot, sql: &str, ) -> Result<(Vec<u32>, Vec<ColumnSchema>), EngineError>

v7.18 — describe a SQL string against a CatalogSnapshot. Mirror of Database::describe on the readonly path. Pure function on the snapshot’s catalog; safe to call from any thread.

§Errors

Propagates EngineError::Parse from the parser.

Source

pub fn execute_script( &mut self, sql: &str, ) -> Result<Vec<QueryResult>, EngineError>

v7.21 (round-12 polish) — run a multi-statement SQL script with PG simple-query semantics: the statements execute in order inside ONE implicit transaction, so a mid-script error rolls back the whole script (PG wraps every simple-query message in an implicit transaction). Three exceptions, all PG-faithful:

  • a script that carries its OWN transaction control (BEGIN / COMMIT / …) runs statement-by-statement — the script owns its boundaries;
  • a script run while the caller already has a transaction open joins that transaction (no nested BEGIN), and the caller’s COMMIT / ROLLBACK decides its fate;
  • a single-statement script is plain auto-commit.

Returns one QueryResult per executed statement. This is the engine behind sqlx::raw_sql (mailrs feeds whole init-schema.sql files through it) and spgctl import.

§Errors

The first failing statement’s error propagates after the implicit ROLLBACK; nothing from the script remains applied.

Source

pub fn execute_dump_statement( &mut self, stmt: &str, ) -> Result<QueryResult, EngineError>

v7.22 (round-13 T2) — execute one split_statements chunk, lowering a COPY … FROM stdin; block (statement + its data lines, as one chunk) to per-row INSERTs through the shared spg_engine::copy helpers. Default-format pg_dump emits COPY blocks, so the zero-change import promise needs this on the embed path; non-COPY statements pass straight through to Self::execute. Public so spgctl import can keep its per-statement error indexing while sharing the lowering.

§Errors

Engine errors propagate; for COPY the failing row’s INSERT error carries the synthesized statement context.

Source

pub fn with_transaction<R, F>(&mut self, body: F) -> Result<R, EngineError>
where F: FnOnce(&mut Self) -> Result<R, EngineError>,

v7.2.0 — run body inside an implicit BEGIN / COMMIT pair. The body receives &mut Database so it can execute() / query() like any other code path; the only difference is that every write in the body lands inside one transaction, and a returned Err from the body triggers ROLLBACK before the error propagates.

Nested calls are not supported — SPG’s transaction model is single-writer with explicit BEGIN / COMMIT / ROLLBACK, and a nested with_transaction would hit EngineError::Unsupported("nested transaction") at the inner BEGIN.

Source§

impl Database

Source

pub fn cold_segment_count(&self) -> usize

v7.7.4 — observe the catalog’s cold-segment count. Useful for tests + dashboards that want to verify auto-compaction is firing.

Source

pub fn metrics(&self) -> EmbeddedMetrics

v7.7.5 — observability snapshot. Returns a point-in-time view of the engine + persistence counters. Cheap (no locks beyond the existing &self borrow), so safe to call from a hot metrics-scrape path.

Fields mirror the operational dashboard spg-server exposes, minus the network counters that don’t apply to embedded.

Source

pub fn spawn_background_freezer( db: Arc<Mutex<Database>>, opts: FreezerOptions, ) -> FreezerHandle

v7.2.1 — spawn a background thread that periodically runs freeze_oldest_to_cold when the catalog-wide hot tier exceeds opts.hot_tier_bytes. The Arc<Mutex<_>> pattern matches the v7.2 sharing story: callers wrap their Database in Arc::new(Mutex::new(db)) once, then clone the Arc for the worker + for foreground access. Return value is a handle whose Drop joins the worker.

Picks the freeze target the same way spg-server’s freezer does: largest-hot_bytes user table with at least one BTree integer-PK index. Tables without a freezable index are skipped silently.

Source§

impl Database

Source

pub fn force_unlock(db_path: impl AsRef<Path>) -> Result<(), EngineError>

v7.17.0 Phase 6.2 — clear a stale cross-process lock. Use when a previous process crashed mid-session and left <db_path>.lock behind. Operators should confirm no other process is currently using the database before calling this — SPG cannot fingerprint stale-vs-live without a libc dep, which would violate spg-embedded’s zero-deps charter.

Trait Implementations§

Source§

impl Debug for Database

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Database

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Drop for Database

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.