Skip to content

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.

Cargo.toml
[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 via NamespaceId::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&region=us-east-1") -> (Arc<dyn ObjectStore>, NamespacePaths) — same parser the Python client and CLI use.
  • Snapshot — read-only point-in-time view; obtained from a WriterSession via .snapshot().
use namidb::storage::{parse_uri, WriterSession};
let (store, paths) = parse_uri("s3://my-bucket/data?ns=prod&region=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 via ariadne.
  • 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 use execute_write against 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, &params) — for plans that came from CREATE / MERGE / SET / DELETE / REMOVE. Auto-commit at the end.
  • Typed staging APIwriter.upsert_node, writer.upsert_edge, writer.tombstone_node, writer.tombstone_edge stage mutations. Call writer.commit_batch().await to make them durable (WAL + manifest CAS), then writer.flush().await to push the memtable into L0 SSTs.

See crates/namidb-storage/src/lib.rs for the staging surface.

What’s next