Rust persistence and transactions¶
Model::new() creates an in-memory dataset. Model::open() and
Model::open_with() create or open a local Fjall-backed format-v1 store and
load an in-memory RDF working set for queries.
Open a store¶
```rust,no_run use oxiland::{Model, OpenOptions};
let development = Model::open("./data/catalog")?;
let production = Model::open_with( OpenOptions::fjall("/srv/app-data/catalog") .create(false) .read_only(false), )?;
let inspection = Model::open_with( OpenOptions::fjall("/srv/app-data/catalog") .create(false) .read_only(true), )?;
Ok::<(), oxiland::Error>(())¶
`Model::open(path)` allows initialization when the path is missing. Prefer
`create(false)` in production when a missing or incorrect mount must fail
instead of creating an empty dataset.
`capabilities()` reports the backend, persistence, transaction, sync, and
read-only properties. Mutating a read-only model returns `Error::Unsupported`.
## Frozen backend discovery
`supported_backends()` returns descriptors for the 1.0-intent matrix even when
an optional adapter is disabled in the current build. Each descriptor includes
the canonical name, Cargo feature, compiled state, durability, and
`LayoutReaderPolicy`. `compiled_backends()` returns only adapters usable by the
current binary. This distinction lets configuration validators recognize
`rocksdb`, for example, while still returning an explicit error when
`storage-rocksdb` was not compiled.
The supported identities are `memory`, `fjall`, `redb`, `rocksdb`, `sqlite`,
and `lmdb`. All durable adapters retain a format-v1 reader/export path;
standards RDF is the portable cross-backend format. The physical custom-backend
adapter remains sealed for 1.0 (ADR-024).
## Transaction contract
```rust,no_run
use oxiland::terms::{Literal, Triple, named_node};
use oxiland::Model;
fn activate(model: &Model) -> oxiland::Result<()> {
model.transaction(|tx| {
tx.add(Triple::new(
named_node("https://example.com/alice")?,
named_node("https://example.com/status")?,
Literal::new_simple_literal("active"),
))?;
Ok(())
})
}
- Returning
Okcommits the complete operation set. - Returning
Errrolls back. - Persistent commits synchronize durable state.
- Nested transactions return
Error::Unsupported. - Auto-commit mutation while a transaction is open is rejected.
- Same-thread reads during the callback see the last committed working set, not uncommitted changes.
The transaction handle supports add, insert, remove, clear-graph, and full clear operations. Keep all mutations for the logical write on that handle.
Atomic imports¶
```rust,no_run
use oxiland::{GraphTarget, Parser, Syntax};
use oxiland::Model;¶
let model = Model::new()?;¶
Parser::for_syntax(Syntax::NQuads) .graph_target(GraphTarget::Dataset) .load_path_transactional(&model, "snapshot.nq")?;
Ok::<(), oxiland::Error>(())¶
`load_transactional` parses the complete input and commits it in one
transaction. `load_collecting` also parses first but uses best-effort removal if
an insertion later fails. `load_into` is progressive and can leave durable
partial data. See [RDF input and output](io.md#load-into-a-model).
## Sync and durability
Persistent auto-commit writes and successful transactions update the durable
store. `Model::sync()` provides an explicit durability boundary and surfaces a
storage failure to the caller. Call it before controlled shutdown, backups, and
handoffs where the application needs an explicit acknowledgment.
An in-memory model accepts `sync()` as a no-op through the same interface.
## Backup and restore
N-Quads is the portable archival format because it preserves graph names:
```rust,no_run
use oxiland::Model;
fn backup(model: &Model) -> oxiland::Result<()> {
model.sync()?;
model.export_nquads_to_path("./backups/catalog.nq")
}
fn restore() -> oxiland::Result<Model> {
let model = Model::open("./data/restored")?;
let processed = model.import_nquads_from_path("./backups/catalog.nq")?;
eprintln!("restored {processed} quad(s)");
Ok(model)
}
Import merges with existing data and does not clear first. Restore into a new store or deliberately clear the selected target when replacement is required. Store backup files outside the live database directory and test restore regularly.
Format compatibility¶
Format v1 stores metadata beside durable N-Quads keys. Patch releases in
0.4.x–0.13.x reopen format v1 without migration. Export N-Quads before any
future format-v2 migration. Pre-0.4 experimental directories without metadata
require Model::migrate_legacy_store(path).
Standards RDF—not a copied Fjall directory—is the archival continuity contract across future major-format changes. Read the changelog before minor upgrades and follow the production upgrade runbook.
Capacity and process ownership¶
Persistent models retain a complete in-memory query working set in addition to durable files. Plan both memory and disk capacity. Streaming reads avoid a second collection but do not turn the model into a disk-only database.
Treat one persistent path as application-owned local mutable state. Do not rely on multiple independent processes opening the same writable directory as a coordination protocol. Use one owning service when multiple network clients must share the store.
Failure handling¶
| Error | Response |
|---|---|
Error::OpenStore { path, message } |
Fail readiness and inspect path, permissions, mount, and format |
Error::Storage(message) |
Stop assuming the write or sync succeeded; preserve context and alert |
Error::Unsupported(message) |
Correct read-only, nesting, backend, or operation configuration |
Error::Io(error) |
Inspect import/export path and filesystem state |
Never fall back silently from a configured persistent model to memory. Preserve error chaining and avoid logging sensitive RDF values.
See Rust production operations for deployment, concurrency, observability, security, and upgrade guidance.