Rust guide

Use parameters, transactions, runtime modes, extensions, and backups in Rust.

These recipes use the synchronous native API from the Rust quickstart. Its async counterpart provides the same application operations with .await.

Query application data

With an open mutable db, create a table and insert a parameterized value:

db.execute("CREATE TABLE IF NOT EXISTS notes (id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, body text NOT NULL)")?;
let inserted = db
    .sql("INSERT INTO notes (body) VALUES ($1) RETURNING id")
    .bind("First note")
    .query()?;
let id: i64 = inserted.rows()[0].try_get("id")?;

Use query for rows and execute for command metadata. sql(...).bind(...) builds typed parameters; query_with_params and execute_with_params are also available. Match Rust types to PostgreSQL result types when calling try_get.

Run a transaction

Use the callback's transaction handle for all statements:

db.transaction(|tx| {
    tx.execute_with_params("INSERT INTO notes (body) VALUES ($1)", ["First"])?;
    tx.execute_with_params("INSERT INTO notes (body) VALUES ($1)", ["Second"])?;
    Ok::<_, oliphaunt::Error>(())
})?;

Returning Ok commits; returning Err rolls back. Callback errors can use your own type if it implements From<oliphaunt::Error>. Do not send manual transaction-lifecycle SQL inside the callback. Use the transaction's rollback API; savepoints are supported.

For the async API, use an async closure:

db.transaction(async |tx| {
    tx.execute("INSERT INTO notes (body) VALUES ('Async note')").await?;
    Ok::<_, oliphaunt::Error>(())
}).await?;

This fragment requires an AsyncOliphaunt handle. Keep unrelated network work outside a transaction so it does not hold the database session unnecessarily.

Choose a runtime mode

Direct mode runs in your process. Broker mode moves the database into a helper process:

let mut db = Oliphaunt::builder()
    .storage(DatabaseStorage::Directory("./app-data/main.oliphaunt".into()))
    .broker()
    .open()?;

For an ORM or connection pool, start a server and keep the returned handle alive:

use oliphaunt::OliphauntServer;

let mut server = OliphauntServer::builder()
    .storage(DatabaseStorage::Directory("./app-data/server.oliphaunt".into()))
    .start()?;
println!("{}", server.connection_string());
// Connect a PostgreSQL driver here. Close clients before closing server.
server.close()?;

The server lifecycle handle does not run SQL itself. Use AsyncOliphauntServer for async server ownership. See runtime modes for failure and concurrency behavior.

Select extensions

Add the extension crate to Cargo.toml:

oliphaunt-extension-vector = "0.3.0"

Select it before opening, then enable it with SQL:

let mut db = Oliphaunt::builder()
    .direct()
    .extension(oliphaunt_extension_vector::VECTOR)
    .open()?;
db.execute("CREATE EXTENSION IF NOT EXISTS vector")?;

Use the same selection when reopening a database that depends on the extension. See Extensions.

Back up and restore

A native backup contains PostgreSQL data and required WAL. Restore into a new or empty root:

let archive = db.backup()?;
db.close()?;
Oliphaunt::restore("./app-data/restored.oliphaunt", &archive)?;
let mut restored = Oliphaunt::builder()
    .storage(DatabaseStorage::Directory("./app-data/restored.oliphaunt".into()))
    .broker()
    .open()?;
restored.close()?;

The restored handle uses broker mode because direct mode remains bound to its original root for the lifetime of the process, even after close. Reapply required extension selections when opening restored data. Native and WASIX archives are separate families. Server applications use PostgreSQL tools through their endpoint; the optional oliphaunt-tools crate supplies pg_dump and non-interactive psql.

Cancel and close

Before starting a long synchronous query, obtain db.cancel_handle() and pass that cancellation handle to the thread that may interrupt it. The async API exposes cancel().await. Cancellation requests do not replace checking the operation's eventual result.

Close explicitly to observe shutdown errors. A failed close leaves the handle terminal; do not retry database work on it. If a broker process dies, close the handle and reopen the persistent root. Check application state before retrying a write with an unknown outcome.

Troubleshooting

SymptomCheck
Async executor stallsUse AsyncOliphaunt instead of synchronous calls on the executor
Cannot share a synchronous handleIt is not Sync; use an exclusive owner or the async handle
PostgreSQL rejects a valueCheck SQLSTATE and the parameter/result PostgreSQL type
Root is already ownedClose the existing owner before opening another
Restored data cannot openRuntime compatibility and required extensions