Skip to main content

MarsDB

CI Crates.io docs.rs codecov License openCypher TCK

An embeddable property-graph database with an openCypher query subset: single binary, single file, optional in-memory mode. Full manual: knoguchi.github.io/marsdb.

$ marsdb :memory:
MarsDB graph database. Enter Cypher statements terminated by `;`. Ctrl-D to exit.
marsdb> CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'});
marsdb> MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name;
a.name | b.name
Alice | Bob

Install

Requires Rust 1.82+ (MSRV, verified via clippy's own incompatible_msrv lint — not tested against every toolchain above that, CI just runs each runner's current stable).

CLI — installs the marsdb binary:

cargo install marsdb-cli

Or on macOS/Linux via Homebrew (tap):

brew install knoguchi/marsdb/marsdb

Rust library:

cargo add marsdb
let db = marsdb::Database::in_memory()?; // or Database::open("path/to.db")
db.execute("CREATE (a:Person {name: 'Alice'})")?;
let result = db.execute("MATCH (n:Person) RETURN n.name")?;

// Bound work from untrusted callers. A CancellationToken can also be
// cloned and cancelled from another thread.
let options = marsdb::ExecutionOptions {
    max_intermediate_rows: Some(100_000),
    max_result_rows: Some(10_000),
    max_relationship_expansions: Some(1_000_000),
    timeout: Some(std::time::Duration::from_secs(5)),
    ..Default::default()
};
let result = db.execute_with_options("MATCH (n) RETURN n", &options)?;

// Group statements into one atomic unit. Reads through `tx` see its earlier
// writes; any statement error aborts and closes the whole transaction.
let mut tx = db.begin_transaction()?;
tx.execute("CREATE (:Person {name: 'Bob'})")?;
tx.execute("CREATE (:Person {name: 'Carol'})")?;
tx.commit()?;

// Operational checks and crash-consistent backup. The backup destination
// must be new, so an existing file is never overwritten.
db.backup_to("path/to-backup.db")?;
let mut db = db;
let report = db.check_integrity()?;
assert_eq!(report.nodes, 3);

// Or run a `;`-separated batch, one transaction per statement, one
// QueryResult per statement back:
let results = db.execute_batch("CREATE (a:Person {name: 'Alice'}); CREATE (b:Person {name: 'Bob'})")?;

ExecutionOptions::observer accepts an ExecutionObserver callback for dependency-free telemetry. Events contain duration, outcome category, read/write classification, result-row count, and relationship expansions; they deliberately exclude query text and error messages. Syntax and missing- parameter rejections are reported too, and observer panics are contained.

More: cargo run -p marsdb --example task_tracker (CRUD + aggregation), --example social_graph (variable-length traversal, MATCH...CREATE), or --example params_and_batch ($parameters, execute_batch) — full source in marsdb/examples/. Each also writes an SVG chart of its query result (via plotters) to the current directory.

Python:

pip install marsdb
import marsdb
db = marsdb.Database.in_memory()  # or .open(path)
db.execute("CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})")
db.execute("MATCH (n:Person) RETURN n.name")
# -> [{'n.name': 'Alice'}, {'n.name': 'Bob'}]

Prebuilt wheels cover macOS (arm64, x86_64) and Linux (x86_64, manylinux); other platforms install from the source distribution and need a Rust toolchain. To build from source directly:

cd marsdb-python
python3 -m venv .venv && source .venv/bin/activate
pip install maturin && maturin develop

Go:

go get github.com/knoguchi/marsdb/marsdb-go

Go modules resolve straight from the public Git host — no separate registry step. Bindings live in marsdb-go (cgo, via a C ABI crate, marsdb-capi); see that README for the two-step build (Rust cdylib, then go build) and a full example.

db, _ := marsdb.InMemory() // or marsdb.Open(path)
db.Execute("CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})")
rows, _ := db.Execute("MATCH (n:Person) RETURN n.name AS name")
// rows -> []map[string]any{{"name": "Alice"}, {"name": "Bob"}}

CLI usage

marsdb                                  # in-memory REPL
marsdb mydata.db                        # file-backed REPL
marsdb mydata.db "MATCH (n) RETURN n"   # run one query, exit
marsdb :memory: "..."                   # explicit in-memory, one-shot
marsdb mydata.db "CREATE (a); CREATE (b); MATCH (n) RETURN n"  # ;-separated batch

Natural language -> Cypher

marsdb-nl2cypher translates an English question into Cypher against a database's actual schema (labels/relationship-types/properties in use, introspected automatically), validates its syntax and variable/type binding, and retries once with the exact validation error fed back if the first attempt is invalid. No HTTP/LLM-SDK dependency in the core crate — bring your own LlmClient:

use marsdb::Database;
use marsdb_nl2cypher::{translate_and_run, LlmClient};

let db = Database::in_memory()?;
db.execute("CREATE (:Person {name: 'Alice'})-[:KNOWS]->(:Person {name: 'Bob'})")?;

let (cypher, result) = translate_and_run(&db, &my_llm_client, "who does Alice know?")?;

translate_and_run enforces read-only generated Cypher. Model-generated writes are rejected before execution unless the caller explicitly uses translate_and_run_with_policy(..., ExecutionPolicy::AllowWrites) after performing its own authentication and authorization.

A real, runnable example against a local Ollama instance:

ollama serve &
ollama pull llama3.2
cargo run -p marsdb-nl2cypher --example ollama_demo

MarsDB's narrower Cypher subset (vs. full Neo4j Cypher) is a deliberate fit for this — a smaller grammar means fewer ways an LLM can generate something unparseable. The prompt tells the model what's supported and what to avoid (no bare --> shorthand, MERGE capped at one hop, etc.) — see marsdb-nl2cypher/src/lib.rs's CAPABILITIES constant.

Architecture

marsdb-storage   thin trait boundary over redb (file + in-memory backends)
marsdb-graph     property graph model, CRUD, KV/adjacency encoding
marsdb-query     openCypher subset: ANTLR4 grammar -> AST -> IR -> executor
marsdb           embeddable public Rust API (Database::open/in_memory/execute)
marsdb-cli       the `marsdb` binary (REPL + one-shot mode)
marsdb-python    PyO3 bindings, builds via maturin
marsdb-capi      C ABI (opaque handle + JSON results), basis for non-Rust bindings
marsdb-go        Go bindings, via cgo against marsdb-capi
marsdb-nl2cypher natural-language -> Cypher: schema introspection, prompt building, validate-and-repair

Storage runs on redb, a pure-Rust single-file MVCC embedded KV engine. Query execution compiles Cypher to a small Gremlin-shaped logical IR (AllNodesScan, NodeByLabelScan, Seed, Expand, VarExpand, Filter) so a future Gremlin frontend can target the same executor. Every Cypher statement runs inside one transaction — a read-only MATCH ... RETURN opens a ReadTransaction (a consistent snapshot that runs alongside other concurrent readers or a concurrent writer without contending for redb's single-writer lock), everything else opens a WriteTransaction, committed or aborted as a whole. Database::begin_transaction lets callers explicitly extend that atomic boundary across multiple statements. MarsDB records its own table/record format version in metadata when the file is created or first opened by a version-aware build, and refuses to open a database written by a newer unsupported format.

Numbers: BENCHMARKS.md.

The logical read plan runs as a pull-based row stream through node-ID scans, filters, and relationship expansions. A non-aggregating, non-distinct RETURN ... LIMIT k without ORDER BY stops that pipeline after k rows, so downstream limits avoid unnecessary expansions. Clause boundaries and inherently blocking operations still materialize: WITH, optional-match reconciliation, variable-length traversal results for each input row, aggregation, DISTINCT, mutations, and the public QueryResult. Use ExecutionOptions to put hard ceilings on intermediate rows, result rows, relationship expansions, and elapsed time.

There is not yet a general cost-based optimizer. Two targeted optimizations complement streaming: a direct MATCH (n[:Label]) RETURN ... LIMIT k scan pushes the limit into storage; and every ORDER BY ... LIMIT k site (WITH's own, non-aggregating RETURN's, aggregating RETURN's) uses a top-k partial selection (slice::select_nth_unstable_by + a sort of just the k-sized prefix) instead of a full sort of every row.

Cypher coverage

Full breakdown — every supported clause/expression/temporal-type shape, the error taxonomy, and a real, measured openCypher TCK conformance table by category — lives in CYPHER_COVERAGE.md.

Short version: 3878/3880 TCK scenarios pass (99.9%), 0 wrong-result scenarios. The only 2 non-passing scenarios need dates at year ±999,999,999 — a real storage/library range limitation, not a bug (see CYPHER_COVERAGE.md for the exact explanation). Recent changes: CHANGELOG.md.

Roadmap

  • From-scratch storage engine (page format, B-tree, crash recovery) as an alternate marsdb-storage backend, independent of redb
  • Gremlin frontend targeting the existing IR
  • Composite indexes (single-property indexes already exist) with transactional maintenance
  • Real query optimizer: cardinality estimates and cost-based join/traversal ordering (beyond the existing index-seek fusion and top-k ORDER BY ... LIMIT selection)
  • Node/relationship-valued $parameters (map-valued already works)

Testing

cargo test --workspace                                             # ~8s
cargo test -p marsdb-graph --test stress -- --ignored --nocapture  # ~15s, large-scale
cargo test -p marsdb-crash-harness -- --ignored --nocapture        # ~7s/30 runs, SIGKILL-and-verify
cargo bench -p marsdb-graph
cargo bench -p marsdb

marsdb-crash-harness is a process-crash durability check, not a full power-loss test (OS stays up, page cache intact — a real power-loss test needs fault injection like dm-flakey, not just SIGKILL): it spawns a child process committing one transaction at a time, SIGKILLs it at an unpredictable point, reopens the file fresh, and asserts every acknowledged commit survived intact with no gaps or duplicates. CRASH_TEST_RUNS=200 (default 30) for more confidence at the cost of runtime.

The Cypher parser (the one part of MarsDB that takes raw, untrusted string input directly) is fuzzed via cargo-fuzz — needs nightly:

cargo install cargo-fuzz
cd marsdb-query && cargo +nightly fuzz run parse -- -max_total_time=120

Only claim: never panics. A parse error (Result::Err) is the expected, correct outcome for most fuzzer-generated input. Keep at least one real Cypher string in fuzz/corpus/parse/ — running against a genuinely empty corpus made libFuzzer's own startup/seed-bootstrapping phase (not anything in MarsDB) take upwards of an hour instead of seconds on this target; a seeded corpus doesn't hit it.

marsdb-tck runs a real subset of the openCypher TCK (pulled in as a git submodule pinned to a fixed commit — see marsdb-tck/VENDOR.md; 220 .feature files, 3880 scenarios) against MarsDB, via a purpose-built Gherkin-subset parser and structural result comparison (not string matching):

git submodule update --init marsdb-tck/openCypher
cargo run --release -p marsdb-tck

Attempts every scenario in the vendored TCK — currently 3878/3880 pass, and the 2 that don't (dates far outside the representable range, see CYPHER_COVERAGE.md) report as a distinct "unexpected" outcome, not lumped in with genuine wrong answers. 0 scenarios currently return the wrong answer — the real, checked-for-real signal this exists to produce, not the flat pass-rate over the whole suite. Side-effect assertions and the TCK's typed error taxonomy aren't checked (see the crate's doc comments for why).

Contributing

See CONTRIBUTING.md for the local checks to run before opening a PR, and CODE_OF_CONDUCT.md for community expectations. Found a security issue? See SECURITY.md — please don't open a public issue for it.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

marsdb-0.7.0.tar.gz (488.2 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

marsdb-0.7.0-cp314-cp314-macosx_11_0_arm64.whl (2.1 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

marsdb-0.7.0-cp314-cp314-macosx_10_12_x86_64.whl (2.2 MB view details)

Uploaded CPython 3.14macOS 10.12+ x86-64

marsdb-0.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.5 MB view details)

Uploaded CPython 3.9manylinux: glibc 2.17+ x86-64

File details

Details for the file marsdb-0.7.0.tar.gz.

File metadata

  • Download URL: marsdb-0.7.0.tar.gz
  • Upload date:
  • Size: 488.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for marsdb-0.7.0.tar.gz
Algorithm Hash digest
SHA256 eb1e740fe213796ce58c21e590fa82107887ab231dfb54c916d6e960fb77370b
MD5 6396f4d526a5eb593a8b9968d59833a7
BLAKE2b-256 22270778476555f349634d0314a126c8afcb4bf2c57ec423fafd62ce55fba1da

See more details on using hashes here.

File details

Details for the file marsdb-0.7.0-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for marsdb-0.7.0-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 7e8b3c7ae7fd9f3b0bcc5245d63c2affa02b32262b07665bec4a92a3432eb4d8
MD5 193f067a6f8c5927b6fbc2e469677ef5
BLAKE2b-256 719c2ad7f7641028bd6fec351d0fa870f62c257c94cfc824edae0dad18161214

See more details on using hashes here.

File details

Details for the file marsdb-0.7.0-cp314-cp314-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for marsdb-0.7.0-cp314-cp314-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 659bd09b8b2b3e765f7eb4dec1aaf31a9a659a7688390867368692b120d07b01
MD5 0b39dc40aefb719285abead1d811ce36
BLAKE2b-256 8d1926a5fca5fe244e1220c27c57198a6dcd21994bfc30a8ba9fb911dac42bb6

See more details on using hashes here.

File details

Details for the file marsdb-0.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for marsdb-0.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 3941695a74292ec736b800c98778860ee9ca69573fb44a67180bef16272bb6f7
MD5 913ac8a0737ed554ed30c301b4d4a4e5
BLAKE2b-256 4190f649789e4f9fd3eef1e3d8b6285c3533b3f30d3c46abd9f147f539ca1946

See more details on using hashes here.

Release history Release notifications | RSS feed

0.9.1

4 files

0.9.0

4 files

0.8.0

4 files

0.7.1

4 files

This release

0.7.0 This release

4 files

0.6.0

4 files

0.5.0

4 files

0.4.0

4 files

0.3.0

4 files

0.2.0

4 files

0.1.0

3 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page