Build a binding
Prepare storage and manage scheduling, errors, buffers, streaming, and native lifecycle.
A binding translates its language's database API into the C ABI and owns the scheduling around that boundary. Begin with the C example.
Prepare a managed root
Create persistent storage with a native SDK before calling oliphaunt_init. For example, run this separate Rust program using the Rust SDK:
use oliphaunt::{DatabaseStorage, Oliphaunt};
fn main() -> oliphaunt::Result<()> {
let mut db = Oliphaunt::builder()
.storage(DatabaseStorage::Directory("./data/example".into()))
.direct()
.open()?;
db.close()
}After that process exits, pass ./data/example/pgdata to the C example. The surrounding root also contains .oliphaunt.json; retain it. Pointing the ABI at an ordinary empty directory or an arbitrary initdb directory does not establish the managed-root contract.
Serialize database operations
One direct backend resides in the process. Serialize ordinary calls on the handle, including queries, backups, detach, and close. Use an owner thread or queue if your language's callers are concurrent. oliphaunt_cancel can interrupt an active operation from another thread. Token-bound stream input may also come from another thread; follow the streaming contract.
Do not run PostgreSQL work on a UI thread. Keep request buffers and configuration strings alive for the duration of their native call. The high-level SDKs provide broker/server modes when process isolation or multiple sessions are needed.
Own response buffers
Zero-initialize OliphauntResponse. After a buffered query or backup, consume or copy its bytes, then call oliphaunt_free_response exactly once. Do not free the data with your language's allocator or read it after release.
Decode PostgreSQL fields and error messages before releasing the response. Use protocol parameter binding for untrusted values; the simple-query helper accepts SQL text and does not interpolate values safely for you.
Capture errors before switching threads
Use the _with_error variants for async FFI schedulers. They write an OliphauntErrorCapture into caller-owned memory before the native operation returns. The capture remains available after your language resumes on another thread.
For synchronous callers, oliphaunt_copy_last_error can copy the failing operation's error on the same thread. Read it before beginning another fallible operation. Do not retain a borrowed pointer to shared error storage.
Stream protocol responses
oliphaunt_exec_protocol_raw_stream calls your callback with borrowed bytes valid only for that invocation. Copy them if the receiving language needs them later.
Return zero to continue. Returning nonzero stops further callback delivery and asks the runtime to recover the protocol boundary. OLIPHAUNT_STREAM_CALLBACK_ABORTED indicates the callback-stop outcome; a negative result signals a validation, transport, backend, or recovery failure.
Do not query, back up, detach, close, or start another stream from the callback. Out-of-band cancellation and input for the active stream token are allowed. Reuse the database only when recovery is confirmed.
Back up and restore
oliphaunt_backup returns the native physical archive through an owned response buffer. Copy or save the archive before freeing the response.
Pass OliphauntRestoreOptions to oliphaunt_restore. Its destination names a new or empty managed root, not its pgdata child. Supply the ABI version and archive bytes. Restore rejects a nonempty destination.
If backup reports that exiting backup mode is unconfirmed, do not run another query. Close or detach the handle and restart the process before reopening PostgreSQL.
Register extensions
Link the required native extension code and runtime resources. For static linking, call oliphaunt_register_static_extensions before init with the matching descriptors. The registry becomes immutable once startup begins. Enable database-local extension objects through SQL after open.
End ownership safely
oliphaunt_detach ends the logical lease while retaining the resident backend. oliphaunt_close is terminal for that backend's process lifetime; after success, never dereference the handle again.
When independent host environments or finalizers can own cleanup, capture the nonzero value from oliphaunt_logical_generation immediately after init. Retain that token for cleanup and call oliphaunt_close_if_generation. A stale generation cannot close a newer lease.
Keep explicit close as the observable cleanup path. A finalizer can provide a fallback, but it cannot replace reporting teardown errors to the caller.