Saltearse al contenido

Rust

El crate namidb es la API paraguas estable. Re-exporta los tipos que vas a tocar más seguido de namidb-core, namidb-storage, namidb-graph y namidb-query.

Cargo.toml
[dependencies]
namidb = "0.4"
tokio = { version = "1", features = ["full"] }
object_store = "0.13"
anyhow = "1"

El toolchain anclado del workspace es stable (Rust >= 1.85). Los crates individuales también se publican, pero enlazar solo contra namidb es la superficie soportada — los cuatro namespaces de abajo son el contrato.

namidb::core

Tipos de namidb-core: identificadores, runtime values, descriptores de schema.

Los más usados:

  • namidb::core::id::NamespaceId — el identificador único de un namespace dentro de un bucket. Se construye con NamespaceId::new("demo")?, que valida el nombre.
  • Tipos de runtime values — cómo se ven los nodos, aristas y resultados de Cypher en memoria.

namidb::storage

El layout en el bucket, el writer session, y el parser de URI.

  • WriterSession::open(store, paths) -> WriterSession — abre un writer para un namespace. Un writer por namespace; commits posteriores desde una época vieja quedan fenced vía manifest CAS.
  • NamespacePaths::new("tenants", namespace_id) — resuelve el layout de prefijos bajo la raíz del bucket.
  • parse_uri("s3://...?ns=prod&region=us-east-1") -> (Arc<dyn ObjectStore>, NamespacePaths) — el mismo parser que usan el cliente Python y la CLI.
  • Snapshot — vista read-only en un punto en el tiempo; se obtiene del WriterSession vía .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

Columnas de properties + helpers de adyacencia CSR del lado de lectura. Úsalo cuando quieras recorrer vecinos sin pasar por Cypher (por ejemplo, dentro de un algoritmo de grafos).

namidb::query

Parser, planificador, 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?;

El triplet:

  • parse(&str) -> Result<Query, Vec<ParseError>> — tokeniza + parsea a AST. Los errores vienen con info de span vía ariadne.
  • lower(&Query) -> Result<LogicalPlan, LowerError> — convierte AST en el plan lógico y ejecuta el optimizador.
  • execute(&LogicalPlan, &Snapshot, &Params) -> Result<Vec<Row>, ExecError> — ejecuta un plan de lectura. Los planes de escritura usan execute_write contra un &mut WriterSession (consulta Escribir datos).

Params::new() está vacío; Params::from(...) acepta un map para el binding de parámetros.

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 de nodos / aristas, después 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(())
}

Camino de escritura

Dos formas de emitir escrituras desde Rust:

  • Escrituras Cypher vía execute_write(&plan, &mut writer, &params) — para planes que vinieron de CREATE / MERGE / SET / DELETE / REMOVE. Auto-commit al final.
  • API de staging tipadawriter.upsert_node, writer.upsert_edge, writer.tombstone_node, writer.tombstone_edge ponen las mutaciones en stage. Llama writer.commit_batch().await para hacerlas persistentes (WAL + manifest CAS), después writer.flush().await para empujar el memtable a SSTs L0.

Consulta crates/namidb-storage/src/lib.rs para la superficie de staging.

Siguientes pasos