Rust
The namidb crate is the stable umbrella API. It re-exports the
types you reach for most often from namidb-core, namidb-storage,
namidb-graph and namidb-query.
[dependencies]namidb = "0.4"tokio = { version = "1", features = ["full"] }object_store = "0.13"anyhow = "1"The pinned workspace toolchain is stable (Rust >= 1.85). The
individual crates are also published, but linking only against
namidb is the supported surface — the four namespaces below are the
contract.
namidb::core
namidb-core types: identifiers, runtime values, schema descriptors.
Most used:
namidb::core::id::NamespaceId— the unique identifier for a namespace inside a bucket. Built viaNamespaceId::new("demo")?, which validates the name.- Runtime value types — what nodes, edges and Cypher results look like in memory.
namidb::storage
The on-bucket layout, the writer session, and the URI parser.
WriterSession::open(store, paths) -> WriterSession— opens a writer for a namespace. Single writer per namespace; later commits from a stale epoch are fenced via manifest CAS.NamespacePaths::new("tenants", namespace_id)— resolves the prefix layout under the bucket root.parse_uri("s3://...?ns=prod®ion=us-east-1") -> (Arc<dyn ObjectStore>, NamespacePaths)— same parser the Python client and CLI use.Snapshot— read-only point-in-time view; obtained from aWriterSessionvia.snapshot().
use namidb::storage::{parse_uri, WriterSession};
let (store, paths) = parse_uri("s3://my-bucket/data?ns=prod®ion=us-east-1")?;let mut writer = WriterSession::open(store, paths).await?;namidb::graph
Property columns + CSR adjacency helpers on the read side. Use this when you want to walk neighbors without going through Cypher (e.g. inside a graph algorithm).
namidb::query
Parser, planner, executor.
use namidb::query::{execute, lower, parse, Params};
let snap = writer.snapshot();let q = parse("MATCH (a:Person) RETURN count(*) AS n")?;let plan = lower(&q)?;let rows = execute(&plan, &snap, &Params::new()).await?;The triplet:
parse(&str) -> Result<Query, Vec<ParseError>>— lex + parse to AST. Errors come with span info viaariadne.lower(&Query) -> Result<LogicalPlan, LowerError>— turn AST into the logical plan and run the optimizer.execute(&LogicalPlan, &Snapshot, &Params) -> Result<Vec<Row>, ExecError>— run a read plan. Write plans useexecute_writeagainst a&mut WriterSession(see Writing data).
Params::new() is empty; Params::from(...) accepts a map for
parameter binding.
End-to-end (memory://)
use std::sync::Arc;
use namidb::core::id::NamespaceId;use namidb::query::{execute, lower, parse, Params};use namidb::storage::{NamespacePaths, WriterSession};use object_store::{memory::InMemory, ObjectStore};
#[tokio::main]async fn main() -> anyhow::Result<()> { let store: Arc<dyn ObjectStore> = Arc::new(InMemory::new()); let paths = NamespacePaths::new("tenants", NamespaceId::new("demo")?); let mut writer = WriterSession::open(store, paths).await?;
// ... upsert nodes / edges, then commit_batch + flush ...
let snap = writer.snapshot(); let q = parse("MATCH (a:Person) RETURN count(*) AS n")?; let plan = lower(&q)?; let rows = execute(&plan, &snap, &Params::new()).await?;
println!("{rows:?}"); Ok(())}Write path
Two ways to issue writes from Rust:
- Cypher writes via
execute_write(&plan, &mut writer, ¶ms)— for plans that came fromCREATE/MERGE/SET/DELETE/REMOVE. Auto-commit at the end. - Typed staging API —
writer.upsert_node,writer.upsert_edge,writer.tombstone_node,writer.tombstone_edgestage mutations. Callwriter.commit_batch().awaitto make them durable (WAL + manifest CAS), thenwriter.flush().awaitto push the memtable into L0 SSTs.
See crates/namidb-storage/src/lib.rs for the staging surface.
What’s next
- Embedded (Rust) — the same topic with more on backend selection and concurrency.
- Reading data — read clauses.
- Writing data — write clauses and the auto-commit model.
- Storage backends — URI grammar
for
parse_uri.