Relationships are first-class
Edges are typed, directed, and property-bearing. Traversal is O(degree), not a stack of self-joins.
In-memory · Cypher · Rust
A complete map of the engine — Cypher coverage, surfaces, architecture, and the lines we won’t pretend to cross. Pick what you came here to verify.
Design principles
Edges are typed, directed, and property-bearing. Traversal is O(degree), not a stack of self-joins.
A pragmatic subset of Cypher — MATCH, WITH, WHERE, CREATE, RETURN. Short queries, readable intent.
Add a label, an edge type, or a property by writing it. No ALTER, no migration, no restart.
A compiler-style pipeline of focused crates from parser to executor. If the database matters to your product, you should be able to read it.
Cypher coverage
Pattern matching, writes, aggregation pipelines, paths, temporal and spatial predicates — composed top-to-bottom with WITH.
MATCH (a:Person)-[:KNOWS]->(b:Person)
WHERE a.city = 'Berlin'
RETURN a.name, collect(b.name) AS friendsMATCH referenceMERGE (u:User {email: $email})
ON CREATE SET u.created = datetime()
ON MATCH SET u.last_seen = datetime()MERGE referenceMATCH p = shortestPath(
(a:Stop {code: $from})-[:CONNECTS*..6]->(b:Stop {code: $to})
)
RETURN length(p) AS hops, [n IN nodes(p) | n.code] AS viaPaths referenceMATCH (u:User)-[:PLACED]->(o:Order {status: 'paid'})
WITH u, count(o) AS orders, sum(o.total) AS spend
WHERE orders >= 3
RETURN u.email, orders, spend ORDER BY spend DESCAggregation referenceMATCH (e:Event)
WHERE e.at >= datetime() - duration('P7D')
RETURN date(e.at) AS day, count(*) AS events
ORDER BY dayTemporal typesWITH point({latitude: 52.52, longitude: 13.405}) AS origin
MATCH (s:Store)
WHERE distance(s.loc, origin) < 5000
RETURN s.name, distance(s.loc, origin) AS metres
ORDER BY metresSpatial typesFunctions & types
String, math, list, aggregation, temporal, spatial, vector — shipped with the engine. No procedure plugins to install.
Every value — stored as a property, projected in a RETURN, or bound as a parameter — has one of these types.
Architecture
Every query walks a real pipeline — parser, analyzer, compiler, executor — written from scratch in Rust. Each stage is one crate; the names line up with the source tree.
lora-parserA PEG grammar lifts Cypher text into a typed AST with source spans.
lora-analyzerVariable scoping, label and type validation against live graph state, function resolution.
lora-compilerLower the resolved IR into a logical plan, optimize (filter push-down), then a physical plan.
lora-executorInterpret the physical plan against the in-memory store and project results in the requested shape.
Ways to use it
Pick the surface that fits the host process. Cypher, parameters, and result shapes are identical across all of them.
use lora_database::Database;
let db = Database::in_memory();
db.execute("CREATE (:User {name: 'Ada'})", None)?;
let result = db.execute(
"MATCH (u:User) RETURN u.name",
None,
)?;Operations
Know the boundary
LoraDB is an in-memory engine, not a clustered production database. Below is what to expect — stated up front so you can decide whether the trade-offs match your workload.
The graph lives in process memory. Durability is optional via snapshots and WAL, not a separate persistent storage backend.
Predicates are evaluated by scan. Plenty fast for the workloads above; not a substitute for an indexed planner.
The executor holds one mutex per database. Great for embedded and per-request use; not for high-fan-out concurrency.
lora-server is meant to live behind your own ingress. The crate has no auth surface — you control the host process.
Three lines of Rust, a curl call, or a single import. The fastest way to feel LoraDB is to write a query against it.