HeliosDB Nano

Nano is a single self-contained binary that you can link into your application, run as an interactive REPL, or start as a PostgreSQL- and MySQL-speaking server against the same data directory. Relational SQL and HNSW vector search share one engine, so retrieval-heavy applications do not have to run and synchronise a separate vector service.

Written in Rust, Apache 2.0 licensed. Database branching, time-travel queries, and AES-256-GCM encryption are part of the engine rather than add-ons. Nano is young — the changelog shows an active correctness campaign — so this page states the current limits alongside the capabilities.

A real embedded Nano instance per browser session — no signup, no install.

36-44 MB Self-contained binary
~15-17 MB Compressed download
Apache 2.0 License
PG + MySQL Wire protocols

Both figures are the current release binaries, uncompressed and as compressed downloads, and vary by platform; they are default-feature builds, so measure your own build if you add features.

Rust
// Embedded: open a data directory in-process
use heliosdb_nano::EmbeddedDatabase;

let db = EmbeddedDatabase::new("./mydata")?;

db.execute("CREATE TABLE docs (
    id INTEGER PRIMARY KEY,
    content TEXT,
    embedding VECTOR(768)
)")?;

// Relational + vector in one query
let rows = db.query_params("
    SELECT content, embedding <=> $1::vector(768) AS dist
    FROM docs ORDER BY dist LIMIT 10
", &[query_vec])?;
Agent-Ready

Wire Nano into Claude Code & Codex CLI

Ships an AGENTS.md aggregate plus a .claude/skills/ catalogue covering the CLI, REPL meta-commands, public API, and MCP tools. Read the guide →

cargo install heliosdb-nano

Should you run Nano embedded or as a server?

Run Nano embedded when one process owns the data, and as a server when several callers need their own sessions or network access. Nano separates two independent choices: where the data lives (storage mode) and how callers reach it (access mode). Both are chosen when the database is opened or the server is started.

Access mode How you start it What it is for
Embedded library EmbeddedDatabase::new("./mydata") One process owns the data. No network hop, no daemon to supervise.
Interactive REPL heliosdb-nano repl --data-dir ./mydata Ad-hoc SQL, inspection, migrations. Single-user, direct access.
Foreground server heliosdb-nano start --data-dir ./mydata Several clients over PostgreSQL wire (add --mysql for MySQL). Each connection gets its own session.
Background daemon heliosdb-nano start --daemon --pid-file ./heliosdb.pid Same as above, detached. heliosdb-nano status / stop manage it.

Storage mode is a separate axis

Persistent (a data directory on disk) or in-memory (--memory, or EmbeddedDatabase::new_in_memory()). Any access mode can use either. In-memory can still be dumped on shutdown with --dump-on-shutdown so an ephemeral database survives as a file.

Modes are not a live cutover

One data directory is owned by one process at a time. Moving from embedded to server means stopping the embedded process and starting a server against the same directory — not running both at once. Keep the Nano version identical across the switch, and add a PostgreSQL or MySQL client to your application; the embedded calls do not carry over unchanged.

Planning that transition? See the migration guides and the concurrency and transactions deep dive.

What is HeliosDB Nano good at?

Nano is good at four things: local AI memory, agent experiments on database branches, historical investigation with time-travel queries, and serving one dataset to both PostgreSQL and MySQL clients.

Local AI memory

Relational tables and HNSW vector search live in the same engine, and the vector-search feature is in the default build — so the join between metadata and embeddings is an ordinary SQL join, with no separate vector service to run and keep in sync. Product Quantization is in the default build too: add WITH (quantization='product') to an HNSW index. Restart-surviving PQ-HNSW indexes need the vector-persist build; the default build rebuilds the index in memory.

Agent experiments

Branching is real and cheap: CREATE DATABASE BRANCH, USE, MERGE, DROP, scoped to the whole database. MERGE moves rows but has no conflict detection — it is last-writer-wins. For anything conflict-sensitive, fork, inspect, then discard and re-apply validated SQL to main rather than merging.

Historical investigation

Any read can be anchored to a past moment with AS OF TIMESTAMP. By default Nano keeps all history; set a retention window (for example version_retention = "7d") and a watermark-based collector reclaims older versions, with VACUUM VERSIONS to force a pass. Time travel answers "what did this look like then" within the retained window. It is not a backup.

Shared services

PostgreSQL and MySQL wire compatibility is about client connectivity — your existing drivers, ORMs and admin tools connect — not full semantic parity with either server. Two concrete gaps today: collation is byte-order (C) only, with no locale support; and in the stock server row-level security is enforced through the embedded API and the REPL, while enforcement for wire and REST clients is planned. See current limitations.

How does vector search work in Nano?

Vector search is part of Nano's SQL engine rather than an extension: vector columns, HNSW indexes and distance operators use the same implementation on the embedded and server paths. The vector-search feature ships in the default build, and so does Product Quantization: add WITH (quantization='product') to an HNSW index. Indexes are unquantized unless you ask, and durable restart-surviving PQ-HNSW indexes need the vector-persist build.

  • HNSW indexing with configurable M and ef_construction parameters
  • Cosine similarity, L2 distance, and inner product metrics
  • Stores vectors from any embedding model your application calls (OpenAI, Cohere, HuggingFace); built-in AI provider routes for chat, embeddings and NL-to-SQL are planned (roadmap)

Durable PQ-HNSW (vector-persist build):

  • Restart-survivable — index restores from RocksDB on open(); no rebuild after a process restart
  • ~16× less resident RAM at recall-safe defaults — PQ codes in RAM, full vectors on disk, exact two-stage rerank (recall@10 0.987 vs 0.989; internal tests, Nano 3.32.1 vs an unquantized index, see benchmarks.html)
  • Online deletes with neighbour repair — remove() + compact() keep recall stable under churn
  • Filtered KNN in one traversal — search_filtered() evaluates row predicates inside the graph walk, not after
  • F32 / F16 / I8 rerank dial — trade recall for rerank-store footprint

Throughput depends on dimensionality, index parameters and hardware — measure on your own fixtures. Deep dive: Building a Persistent, Quantized, Deletable HNSW in Rust · benchmarks: vector benchmarks · how-to: Build a RAG App on Nano.

SQL
-- Create a table with vector column
CREATE TABLE documents (
    id INTEGER PRIMARY KEY,
    title TEXT,
    content TEXT,
    embedding VECTOR(768)
);

-- Create an HNSW index. For Product Quantization
-- add quantization = 'product' to the WITH list
CREATE INDEX idx_docs_embedding
ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);

-- Semantic similarity search.
-- A bound parameter needs an explicit dimension:
-- $1::vector(768), not a bare ::vector.
SELECT title, content,
       embedding <=> $1::vector(768) AS distance
FROM documents
ORDER BY distance
LIMIT 10;

How does database branching work in Nano?

A Nano branch is a copy-on-write clone of the whole database, made at the storage layer, so you can test a schema change, run an A/B experiment or give an agent a sandbox without duplicating the dataset.

  • Copy-on-write branch creation
  • CREATE DATABASE BRANCH, USE, MERGE, DROP SQL syntax
  • Branch from another branch, optionally anchored with AS OF
  • Schema and migration rehearsal away from main

MERGE has no conflict detection. It moves rows, but where both sides changed the same row the result is last-writer-wins, with no conflict report. The conflict_resolution= option now fails loudly rather than silently doing nothing. For anything conflict-sensitive, treat a branch as disposable: fork, inspect, discard, then re-apply the SQL you validated to main.

A branch is not a security boundary. Branching isolates storage through copy-on-write. It does not sandbox an agent's external side effects — network calls, file writes, API requests — and nothing in the engine claims an isolation guarantee beyond storage.

SQL
-- Fork main into a disposable sandbox
CREATE DATABASE BRANCH feature_auth
FROM main;

USE DATABASE BRANCH feature_auth;

-- Try the change
ALTER TABLE users
ADD COLUMN role TEXT DEFAULT 'user';

INSERT INTO users (name, role)
VALUES ('admin', 'superuser');

-- Inspect, then throw the branch away and
-- re-apply the validated SQL to main.
-- Safer than MERGE, which is last-writer-wins.
USE DATABASE BRANCH main;
DROP DATABASE BRANCH feature_auth;

How far back can a Nano time-travel query read?

A time-travel query reaches back as far as the row versions Nano still retains: all history by default, or the retention window you set (for example version_retention = "7d") once version GC is reclaiming older versions. Anchor a read to a past moment with the AS OF clause. Useful for audit questions, for finding out what changed before a bug, and for pulling back rows an UPDATE or DELETE overwrote — as long as the versions are still retained.

  • AS OF NOW, AS OF TIMESTAMP, AS OF TRANSACTION, and AS OF SCN anchors
  • Timestamp literals are read as UTC unless they carry an explicit offset
  • Watermark-based version GC — off by default (all history kept); set version_retention to turn it on
  • VACUUM VERSIONS forces a reclaim pass
  • Audit and "what changed" investigation without standing up a separate history table

Time travel does not replace backups. It can only reach versions the engine still retains, and it lives inside the same store as your live data — so it does not survive losing that store. Keep a real backup using heliosdb-nano dump and restore. The two solve different problems.

SQL
-- Query data as it existed earlier today
SELECT * FROM orders
AS OF TIMESTAMP '2026-09-11 09:00:00';

-- Or with an explicit offset
SELECT * FROM orders
AS OF TIMESTAMP '2026-09-11 09:00:00 -03:00';

-- Compare current against a past snapshot
SELECT a.id, a.balance,
       b.balance AS prev_balance
FROM accounts a
JOIN (
    SELECT id, balance FROM accounts
    AS OF TIMESTAMP '2026-09-10 09:00:00'
) b ON a.id = b.id;

-- Reclaim superseded versions
VACUUM VERSIONS;

BM25-Ranked Full-Text + Vector Search

HeliosDB Nano ranks full-text matches with BM25 through ts_rank_cd and can order the same rows by vector distance in one SQL query. A standalone BM25 index and hybrid search with Reciprocal Rank Fusion (RRF) or Maximal Marginal Relevance (MMR) ship as MCP tools in the mcp-endpoint source build; SQL functions for BM25 indexes and hybrid fusion, and PREPARE COMPILED, are planned.

  • BM25 ranking in SQL — ts_rank_cd (and ts_rank) score a tsvector against a tsquery with BM25.
  • Keyword + vector in one query — filter with @@ and order by embedding <=> $1, or blend ts_rank_cd with vector similarity in the ORDER BY.
  • Hybrid fusion as MCP tools — heliosdb_bm25_index builds an in-memory BM25 index and heliosdb_hybrid_search fuses BM25 and vector hits with RRF, MMR or weighted-linear fusion (mcp-endpoint source build).
  • Planned — SQL functions for BM25 indexes, RRF/MMR fusion and graph traversal, and PREPARE COMPILED for cached hot-path plans.
  • PostgreSQL-style full-text search — tsvector, tsquery, @@ and ts_rank / ts_rank_cd with BM25 ranking. Matching today is any-term over lowercase tokens; language stemming, boolean tsquery operators and phrase/proximity search are planned.
SQL
-- Keyword search ranked with BM25
SELECT title,
       ts_rank_cd(to_tsvector(content), to_tsquery('database')) AS rank
FROM articles
WHERE to_tsvector(content) @@ to_tsquery('database')
ORDER BY rank DESC LIMIT 10;

-- Keyword + vector in one query
SELECT title,
       0.7 * (1.0 - (embedding <=> $1::vector(768)))
     + 0.3 * ts_rank_cd(to_tsvector(content), plainto_tsquery($2)) AS score
FROM articles
ORDER BY score DESC LIMIT 10;

-- RRF / MMR fusion: heliosdb_hybrid_search MCP tool
-- (mcp-endpoint build); SQL functions planned

Graph Traversal Today

Nano's graph operations ship as MCP tools in the mcp-endpoint source build, and graph expansion over tables ships with Graph-RAG in the graph-rag source build. SQL functions for neighbourhood, shortest-path and connected-component queries are planned.

  • Graph tools over MCP — heliosdb_graph_add_edge adds directed, labelled edges to an in-memory graph store; heliosdb_graph_traverse walks it breadth-first with a depth bound, an edge-label filter and a direction.
  • Shortest paths — heliosdb_graph_path finds a path with BFS, Dijkstra or bidirectional BFS.
  • Graphs stored as tables — with Graph-RAG, nodes and edges live in the ordinary tables _hdb_graph_nodes and _hdb_graph_edges, and WITH CONTEXT (HOPS n, EDGES a|b) expands SQL seed rows over typed edges (see Graph-RAG below).
  • Planned — graph traversal and shortest-path functions callable from SQL.
MCP tools/call
# mcp-endpoint source build: graph tools
# (in-memory store; node ids are UUIDs)
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"heliosdb_graph_traverse",
         "arguments":{"start":"<node-uuid>",
                      "edge_label":"CALLS",
                      "direction":"out","depth":2}}}'

# Shortest path: bfs, dijkstra or bidi
{"jsonrpc":"2.0","id":2,"method":"tools/call",
 "params":{"name":"heliosdb_graph_path",
   "arguments":{"from":"<uuid-a>","to":"<uuid-b>",
                "algorithm":"dijkstra"}}}

Code-Graph — Your Source as a Queryable Database

Most code-search tools live outside the database, with their own storage to scan and re-index. Nano makes the AST a first-class table: CREATE EXTENSION hdb_code; CREATE AST INDEX src_ast ON repo (content) USING tree_sitter(lang);, and your codebase becomes queryable in SQL. Build from source with --features code-graph to enable it and the heliosdb-nano code-graph subcommand; the prebuilt binaries and the GHCR image do not include it.

  • SQL-callable LSP — lsp_definition, lsp_references, lsp_call_hierarchy, lsp_hover, lsp_document_symbols, helios_lsp_rename_preview / helios_lsp_rename_apply. The last two land write-side refactors with sha256 conflict checking.
  • Multi-language — Rust, Python, TypeScript, TSX, JavaScript, Go, Markdown, SQL out of the box, plus a runtime grammar registry (register_grammar + register_extractor) for plugging in any tree-sitter grammar.
  • Time-travel + branch on every call — ON BRANCH '<name>' per-call override and AS OF combine in either order. RAII branch guard restores state on every early-return path.
  • Auto-reparse + git-hook CLI — tree stays current as you commit. hdb_code.pause() / resume() for migrations.
  • In-process embeddings — build with --features code-embed and Nano embeds locally with fastembed-rs + BGESmallENV15 (384-dim, ~30 MB model cache, no on-disk impact on the binary). External HttpEmbedder remains an option.
  • Diff helpers — helios_lsp_references_diff, helios_lsp_body_diff, helios_ast_diff. Accept any AS OF ref: {"now": true}, {"commit": "sha"}, {"timestamp": "iso"}.
SQL
-- Index your repo as a queryable AST
CREATE EXTENSION hdb_code;
CREATE AST INDEX src_ast ON repo (content)
  USING tree_sitter(lang);

-- Where is this function defined?
SELECT path, line FROM lsp_definition('execute_query');

-- Time-travel + branch on the same call
SELECT * FROM lsp_definition('execute_query')
  ON BRANCH 'feat/refactor'
  AS OF TIMESTAMP '2026-04-15T00:00:00Z';

-- Rename refactor with conflict check
SELECT * FROM helios_lsp_rename_preview(
  'old_name', 'new_name'
);

-- Diff helpers
SELECT * FROM helios_lsp_references_diff(
  'main', 'feat/refactor'
);

-- Languages registered (system view)
SELECT name, source FROM hdb_code_languages;

Graph-RAG — Grounded Retrieval in SQL

RAG pipelines usually glue together a vector store, a graph database, an entity linker, and a re-ranker — four services to deploy and keep in sync. Nano collapses them into one engine. WITH CONTEXT is a SQL clause; _hdb_graph_nodes and _hdb_graph_edges are ordinary, queryable tables; code symbols are projected into the graph as they are indexed, and the entity linkers add MENTIONS edges. Build from source with --features graph-rag; the prebuilt binaries and the GHCR image do not include it.

  • Cross-modal graph — code symbols, doc paragraphs, email threads, and issue comments live in the same node/edge tables and join naturally.
  • Centrality re-ranking — centrality_rerank in the Rust API boosts hits by (1 + α·centrality). RERANK BY in WITH CONTEXT is parsed today; applying it in SQL is planned.
  • Semantic-Merkle invalidation — CREATE SEMANTIC HASH INDEX builds subtree hashes over the code-graph symbols, so only what changed needs re-embedding.
  • Eight ingestion adapters (Rust API) — graph_rag_ingest_docs, _email, _issues, _qa, plus _pdf, _office, _audio and _image through an external Docling service. They are EmbeddedDatabase methods, not SQL functions.
  • Vector-similar entity linker — the Rust method graph_rag_link_vector emits MENTIONS edges with weight = similarity; threshold-gated cosine top-k.
Rust API + SQL
// Rust API: ingest a Markdown table, then link
db.graph_rag_ingest_docs(&IngestDocsOptions {
    source_table: "docs".into(),
    id_col: "id".into(),
    text_col: "body".into(),
    title_col: Some("title".into()),
    chunk_by: ChunkStrategy::Headings,
})?;
db.graph_rag_link_exact(&[])?; // MENTIONS edges

-- SQL: select seed rows, then expand the graph
SELECT node_id, node_kind, title
FROM _hdb_graph_nodes
WHERE title LIKE '%auth%'
WITH CONTEXT (
    HOPS 3,
    EDGES IMPORTS|REFERENCES|MENTIONS,
    DIRECTION both,
    LIMIT 50
);

-- Incremental re-embed via semantic-Merkle
CREATE SEMANTIC HASH INDEX IF NOT EXISTS src_merkle;

Native MCP Server for Coding Agents

Coding agents — Claude Code, Cursor, Continue, Codex, Aider — speak Model Context Protocol. Nano serves it from the same process: tool discovery (tools/list), execution (tools/call), and a JSON-RPC 2.0 handshake, without a wrapper process or glue server. Build from source with --features mcp-endpoint; the prebuilt binaries and the GHCR image do not include it.

  • Three transports — POST /mcp (HTTP JSON-RPC), /mcp/ws (WebSocket), /mcp/sse (Server-Sent Events). For stdio, use the separate HeliosDB CodeKB MCP binary; the engine has no stdio subcommand.
  • Hardened defaults — bearer-token auth (--mcp-token); a non-loopback bind is refused without auth unless you pass --allow-remote-mcp.
  • Auto-registered tool catalogue — mcp_tool! macro registers via the inventory crate. BM25, hybrid search, graph operations, and helios_graphrag_search surface automatically.
  • Streaming progress — notifications/progress messages flow over WS when the client opts in via _meta.progressToken.
  • Single-shot discovery — tools/list?verbose=true, helios/info JSON-RPC method, GET /mcp/info route. One round-trip for serverInfo + capabilities + verbose tool catalogue + resource list.
  • HTTP POST + SSE pairing — process-static session table keyed by Mcp-Session-Id. POSTs paired with an open SSE channel get progress events forwarded while the POST returns the final response.
MCP transports
# Discover tools (verbose)
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list",
       "params":{"verbose":true},"id":1}'

# helios/info: one-shot discovery
GET http://localhost:8080/mcp/info

# Streaming progress over WebSocket
ws://localhost:8080/mcp/ws
> {"jsonrpc":"2.0","method":"tools/call",
   "params":{"name":"helios_graphrag_search",
             "arguments":{"seed_text":"auth flow"},
             "_meta":{"progressToken":42}},"id":2}
< notifications/progress {"token":42, "message":"seeding"}
< tools/call response <final>

# Or via Claude Code / Cursor MCP config
{
  "mcpServers": {
    "heliosdb-nano": {
      "url": "http://localhost:8080/mcp",
      "auth": {"type":"bearer","token":"$HDB_TOKEN"}
    }
  }
}

Which backend services are built into Nano?

Nano's binary includes a PostgREST-style REST API, email and OAuth sign-in with JWT sessions, and a realtime WebSocket, so the backend runs in the same process as the database with no external service to deploy. The /rest/v1 table routes are a known issue in the current release. Nano accepts realtime WebSocket subscriptions in the Supabase message shape. Known issue: realtime events are not delivered in the current release; tracked by the Nano team. File storage (buckets and signed URLs) and edge functions are on the HeliosDB roadmap; Nano stores data in its data directory.

  • REST filters: eq, neq, gt, gte, lt, lte, like, ilike, is and in, with not. negation; cs, cd, ov, full-text and or/and filters are planned
  • Built-in Auth: email/password signup, JWT sessions, token refresh
  • OAuth2: Google and GitHub login with PKCE and automatic user creation
  • Realtime WebSocket: accepts INSERT/UPDATE/DELETE subscriptions. Known issue: realtime events are not delivered in the current release; tracked by the Nano team.
  • Argon2id password hashing
  • Planned: file storage (buckets and signed URLs), edge functions and API-key authentication for the HTTP API
  • Swagger UI at /docs + OpenAPI 3.0 spec at /openapi.json

In the stock Nano server, row-level security is enforced through the embedded API and the REPL; SQL CREATE POLICY and enforcement for REST and wire clients are planned. Enforce per-user access in your service layer — see current limitations.

Terminal
# Auth — built-in signup and JWT
$ curl -X POST localhost:8080/auth/v1/signup \
  -d '{"email":"alice@example.com","password":"s3cret"}'

# OAuth — redirect to Google login
$ curl localhost:8080/auth/v1/authorize?provider=google

# Realtime — WebSocket subscription
wscat -c ws://localhost:8080/realtime/v1/websocket
> {"event":"phx_join","topic":"realtime:public:orders"}

Is HeliosDB Nano encrypted at rest by default?

No. Encryption is compiled into the default build, but it is off until you enable [encryption] in the configuration and supply a key. Installing Nano does not encrypt your data on its own — that is a deliberate activation step.

  • Transparent Data Encryption (TDE) with AES-256-GCM — compiled in by default, disabled at runtime by default, activated by setting enabled = true under [encryption] in the config file and supplying a key
  • Key sources — the HELIOSDB_ENCRYPTION_KEY environment variable (the default source; 64 hex characters) or a key file; cloud KMS key sources are planned
  • Zero-Knowledge Encryption (ZKE) — HeliosDB Nano ships zero-knowledge encryption: keys are derived and data encrypted on the client, the server never sees plaintext keys
  • FIPS 140-3 mode — opt-in via the fips compile-time feature
  • Authentication — trust, password, md5, and SCRAM-SHA-256 for the PostgreSQL listener; the MySQL listener is trust-only and does not verify the user name, so keep it on a trusted network
  • TLS — --tls-cert / --tls-key for the PostgreSQL listener and --mysql-tls-cert / --mysql-tls-key for MySQL; the PostgreSQL listener offers TLS but cannot require it yet. When TLS is on, both listeners offer the X25519MLKEM768 hybrid post-quantum key exchange by default (--tls-post-quantum / --mysql-tls-post-quantum)
  • Row-Level Security — in the stock server, enforced through the embedded API and the REPL; SQL CREATE POLICY and enforcement for REST and wire clients are planned
Terminal
# Encryption is compiled in but off by default.
# Enable it in the config file, supply a key.
$ openssl rand -hex 32 > tde.key && chmod 600 tde.key
$ cat > heliosdb.toml <<'EOF'
[encryption]
enabled = true
key_source = { Environment = "HELIOSDB_ENCRYPTION_KEY" }
EOF

# The key: 64 hex characters
$ export HELIOSDB_ENCRYPTION_KEY="$(cat ./tde.key)"
$ heliosdb-nano start --config ./heliosdb.toml \
    --data-dir ./mydata \
    --auth scram-sha-256 --password "$PGPASS" \
    --tls-cert ./server.crt --tls-key ./server.key

# FIPS 140-3 mode is a build-time choice
$ cargo install heliosdb-nano --features fips

Which PostgreSQL and MySQL clients can connect to Nano?

Standard PostgreSQL and MySQL clients, drivers, ORMs and admin tools connect to Nano directly, because it implements both wire protocols natively. This is client connectivity, not full semantic parity with PostgreSQL or MySQL — expect to test your queries rather than assume every behaviour matches.

  • PostgreSQL wire protocol over TCP and Unix socket, with trust / password / md5 / SCRAM-SHA-256 auth
  • MySQL wire protocol behind --mysql, with 5.7/8.0 compatibility modes and prepared statements
  • Works with psql, DBeaver, pgAdmin, DataGrip, and the standard MySQL clients
  • Driver and ORM support: psycopg2, SQLAlchemy, Diesel, SQLx, Prisma, GORM, node-postgres, JDBC
  • SHOW DATABASES / TABLES / COLUMNS / VARIABLES and information_schema for tool compatibility
  • JSONB document type with GIN indexes; window functions and CTEs
  • TLS (STARTTLS) using the same certificates for both listeners

For the current state of SQL-surface compatibility, see the compatibility documentation rather than a single headline percentage.

Terminal
# Start one server with both listeners
$ heliosdb-nano start --data-dir ./mydata --mysql

# Connect with a PostgreSQL client
$ psql -h 127.0.0.1 -p 5432

# Connect with a MySQL client — same server,
# same tables, a different protocol
$ mysql -h 127.0.0.1 -P 3306

mysql> SELECT * FROM users;
+----+-------+-------------------+
| id | name  | email             |
+----+-------+-------------------+
|  1 | Alice | alice@example.com |
+----+-------+-------------------+

One Server, Many Drivers

A single running Nano server accepts PostgreSQL and MySQL clients at the same time, against the same tables. That is genuinely useful for mixed-language teams — and it is a different thing from running the embedded API alongside a server.

  • Mixed-stack teams — a Python service on psycopg2 and a PHP service on mysqli can talk to one server concurrently, each with its own session.
  • CMS ecosystem — WordPress, Drupal, Joomla, Laravel, Symfony, Rails and Django all speak MySQL or PostgreSQL, so they connect without middleware.
  • Changing driver is not a data migration — but it is not free either: SQL dialect differences still surface, so re-test your queries rather than assuming a connection-string swap is the whole job.
  • Embedded is an alternative, not an addition — the embedded API and a running server are two ways to reach one data directory, used one at a time. Two processes opening the same directory is a locking hazard, not a supported topology.
One server, two protocols
# One server process owns ./mydata
$ heliosdb-nano start --data-dir ./mydata --mysql

# Concurrent clients, independent sessions
$ psql  -h 127.0.0.1 -p 5432
$ mysql -h 127.0.0.1 -P 3306

# The embedded API is the OTHER way to open
# the same directory — stop the server first.
let db = EmbeddedDatabase::new("./mydata")?;

Measured on Fixtures

Internal tests, Nano 3.19 built with --features code-graph,graph-rag vs the acceptance targets, see benchmarks.html. Single-machine, release build, criterion methodology. These are acceptance thresholds from the Graph-RAG work, not a competitive benchmark — reproduce them on your own hardware and report variance.

Workload Target Measured
WITH CONTEXT mean (10k-node fixture, 100 queries) ≤ 500 ms 62 ms
Entity linker precision (hand-labelled fixture) ≥ 80 % 100 %

Source: tests/with_context_bench.rs, tests/linker_precision.rs.

Can WordPress run on Nano without a MySQL server?

Yes. WordPress expects to talk to MySQL, and Nano serves the MySQL wire protocol from the same binary that holds the data, so a WordPress install can run against it without a separate MySQL server: 37/37 tests pass, no db.php drop-in, standard wpdb. The MySQL listener is trust-only, so keep it on a trusted network.

One less moving part

Deploy WordPress with one database process instead of a separate MySQL instance to provision, credential and supervise.

Branching for staging

CREATE DATABASE BRANCH staging gives a copy-on-write clone for testing plugins and themes without a mysqldump-and-restore cycle. Discard it when you are done — remember MERGE is last-writer-wins.

Time-travel investigation

SELECT * FROM wp_posts AS OF TIMESTAMP '...' reads a post or setting as it was, within the retention window. Useful for finding out what a plugin changed — not a substitute for backups.

Semantic content search

Native HNSW vector search means WordPress content can be searched by meaning without adding Elasticsearch or Algolia to the stack.

Encryption at rest

AES-256-GCM TDE is handled by the database, so no WordPress plugin is involved. It is off until an operator enables it in the configuration and supplies a key — it is not automatic for every install.

Backup with dump / restore

Back up with heliosdb-nano dump (zstd by default, --append for incrementals, or --dump-schedule on a cron expression) and recover with restore --verify. Copying a live data directory is not a consistent backup — use the dump path.

Frameworks That Connect

Any framework that speaks MySQL or PostgreSQL can point its connection string at a Nano server.

Framework Protocol HeliosDB Nano
WordPress / Drupal / JoomlaMySQL Native
Laravel / Symfony (PHP)MySQL / PG Both
Ruby on RailsMySQL / PG Both
Django (Python)MySQL / PG Both
Express / Fastify (Node.js)PG / MySQL Both
Rust (SQLx / Diesel)PG / Embedded Both

How is Nano different from SQLite?

Both run inside your process; Nano adds built-in vector search, branching, time-travel reads and PostgreSQL and MySQL wire servers, while SQLite brings decades of production use and a ubiquitous file format. SQLite is an excellent, extraordinarily well-tested embedded database, and for a great many applications it is the right answer. Here is the short version of how Nano differs; the full comparison, including when SQLite is the better fit, is on its own page.

Capability SQLite HeliosDB Nano
Server access No built-in network listener; third-party wrappers exist Embedded, plus built-in PostgreSQL and MySQL wire servers
Independent explicit transactions One write transaction at a time; concurrent readers in WAL mode Per-connection in server mode. Not on the plain embedded API — see concurrency
Vector search Not in core; extensions exist — check the specific extension and version for its index type HNSW in the engine, in the default build, same in both modes, including Product Quantization (WITH (quantization='product')). Restart-surviving PQ-HNSW indexes need the vector-persist build
Branching & historical queries Not built in Copy-on-write branches (MERGE is last-writer-wins) and AS OF within the retention window
File format & ecosystem Its own dialect and a mature, ubiquitous file format with decades of tooling PostgreSQL-flavoured SQL with a SQLite-dialect shim. Not file-format compatible — import via an external converter
Maturity Decades of production use across an enormous install base — a real reason to choose it Young, with an active correctness campaign visible in the changelog. Evaluate it on your workload
Full comparison, including when to keep SQLite →

How do you start using HeliosDB Nano?

Open a database in your own process with the Python binding, or start the server and connect a stock PostgreSQL client. Both examples use the shipped CLI and the published binding — run them against your own install and tell us if anything differs.

A. Embedded — SQL + vector search in-process

The published Python binding is heliosdb-nano-embedded, which imports as heliosdb_nano. It is released separately from the engine and can lag the current engine release (known limitations). No server, no wire protocol — the engine runs inside your process.

Python
# pip install heliosdb-nano-embedded
import heliosdb_nano

db = heliosdb_nano.EmbeddedDatabase("./mydata")

db.execute("""
    CREATE TABLE IF NOT EXISTS docs (
        id        INTEGER PRIMARY KEY,
        title     TEXT,
        embedding VECTOR(4)
    )
""")

db.execute_many(
    "INSERT INTO docs (id, title, embedding) VALUES ($1, $2, $3)",
    [
        (1, "vector search", "[1.0,0.0,0.0,0.0]"),
        (2, "backups",       "[0.0,1.0,0.0,0.0]"),
        (3, "hnsw tuning",   "[0.9,0.1,0.0,0.0]"),
    ],
)

# A bound parameter needs an explicit dimension
rows = db.query("""
    SELECT id, title, embedding <=> $1::vector(4) AS distance
    FROM docs ORDER BY distance LIMIT 3
""", ("[1.0,0.0,0.0,0.0]",))

for r in rows:
    print(r)

# {'id': 1, 'title': 'vector search', 'distance': 0.0}
# {'id': 3, 'title': 'hnsw tuning', 'distance': 0.0061162710}
# {'id': 2, 'title': 'backups', 'distance': 1.0}

Executed against the heliosdb-nano-embedded package from PyPI; the output above is the real result. The equivalent Rust API is EmbeddedDatabase::new(path) / new_in_memory() with execute, execute_params, query_params.

B. Server — start it, then connect with psql

When callers need independent sessions or network access, run the same binary as a server. Each connection gets its own transaction state.

Terminal
# cargo install heliosdb-nano

# Start a server on the loopback interface
$ heliosdb-nano start --data-dir ./mydata \
    --listen 127.0.0.1 --port 5432

# Or detached, managed by the CLI
$ heliosdb-nano start --data-dir ./mydata --daemon
$ heliosdb-nano status
$ heliosdb-nano stop

# Connect with a stock PostgreSQL client
$ psql -h 127.0.0.1 -p 5432 -U postgres -d heliosdb

heliosdb=> CREATE TABLE docs (
             id INTEGER PRIMARY KEY,
             title TEXT,
             embedding VECTOR(4));
CREATE TABLE

heliosdb=> SELECT id, title,
             embedding <=> '[1,0,0,0]'::vector AS dist
           FROM docs ORDER BY dist LIMIT 3;
 id | title         |    dist
----+---------------+-------------
  1 | vector search |         0.0
  3 | hnsw tuning   | 0.006116271
  2 | backups       |         1.0
(3 rows)

# Back it up — dump, don't copy a live data dir
$ heliosdb-nano dump --data-dir ./mydata \
    --output ./backup.heliodump --compression zstd
$ heliosdb-nano restore --input ./backup.heliodump \
    --target ./restored --verify

Exercised end-to-end with a stock psql client against a locally started server, and the dump/restore round-trip verified into a fresh data directory. A literal vector string can use a bare ::vector cast; a bound parameter needs ::vector(N).

Can several callers run independent transactions on Nano?

Yes, in server mode: each PostgreSQL or MySQL connection gets its own session and transaction state, while callers sharing one embedded handle do not get independent explicit transactions. Picking the wrong one is the most common way to be surprised by this engine, so here is the whole picture.

What works embedded

Storage is MVCC, so concurrent reads are fine, and single-statement autocommit writes are fine. For the large class of applications that read a lot and write one statement at a time, the embedded API is straightforward.

What does not work embedded

Independent, overlapping explicit transactions are not safe on the plain embedded API. Explicit transaction state lives in a single process-global slot per handle, shared across clones — so a second caller that issues BEGIN while another transaction is open does not get its own transaction.

What works in server mode

The PostgreSQL and MySQL wire daemon keeps real per-connection transaction state. Two clients can hold two independent explicit transactions at the same time, each committing or rolling back on its own. This is the supported way to get independent sessions today.

What to do about it

  1. Run the daemon and give each caller its own connection. Unix socket or TCP, PostgreSQL or MySQL — each connection is an independent session. This is the recommended answer whenever more than one caller needs its own transaction.
  2. If the application must share one embedded handle, it has to coordinate around the entire transaction lifetime — from BEGIN through every commit, rollback and error path — not just around the statement that opens it. A lock that covers only the start or only the end of a transaction will not make this safe.
  3. Use single-statement autocommit where that is sufficient. It avoids the problem entirely and is the simplest embedded pattern.

Raw per-session primitives do exist inside the engine, but they are internal building blocks rather than a documented, supported public API — they are not a workaround, and you should not build on them. A proper public embedded Session API is roadmap, not shipped.

Two different things called "WAL"

The local crash-recovery WAL is always on, backed by RocksDB, and is what makes an unclean shutdown recoverable. The logical WAL is the replication stream that feeds standbys. They are separate mechanisms, and only the second one is about replication.

Writes made inside explicit transactions do reach the logical WAL and replicate — this was broken in earlier releases and is fixed. One known limitation remains: a standby can transiently observe a partially-applied transaction, because atomic-apply markers are not emitted yet. If a standby is serving reads that must never see a half-applied transaction, account for that.

Full detail, including worked examples: Concurrency and transactions →

What are HeliosDB Nano's current limitations?

Collation is byte-order only, the stock server enforces row-level security only through the embedded API and the REPL, embedded callers cannot run independent explicit transactions, and branch merges have no conflict detection; the cards below cover these and more. We would rather you hit them on this page than in production. The known limitations page tracks every open issue with its workaround.

Collation is byte-order only

Sorting and comparison use C / byte-order semantics everywhere. There is no locale-aware collation. If your application depends on linguistic sort order, this will be visible.

RLS is not enforced over the wire or REST

In the stock Nano server, row-level security is enforced through the embedded API and the REPL; SQL CREATE POLICY and enforcement for REST and wire clients are planned. Do not treat it as a network-facing tenancy boundary today.

Embedded explicit transactions

Independent overlapping explicit transactions are not safe on the plain embedded API. Use server mode for independent sessions. See concurrency and transactions.

Branch MERGE has no conflict detection

MERGE moves rows, but concurrent edits to the same row resolve last-writer-wins with no conflict report. Prefer discard-and-reapply for anything conflict-sensitive.

No direct SQLite file access

Nano does not open .sqlite files, and the format is not shared. It accepts many SQLite SQL idioms through a dialect compatibility layer, so moving an existing database means exporting it and reloading it into Nano, then verifying the result yourself. See the migration guides.

PL/pgSQL is partial

Nano runs plain-SQL DO blocks and simple SQL/PL/pgSQL functions; PL/pgSQL control flow and trigger bodies are planned.

No geospatial functions yet

Geospatial functions and indexes (ST_DWithin, ST_Distance) are planned; they are not in the current release.

Full-text search is unstemmed

Full-text matching is any-term over lowercase tokens. Language stemming, boolean tsquery operators and phrase/proximity search are planned.

REST table routes

The /rest/v1 table routes are a known issue in the current release. Reach tables over the PostgreSQL wire protocol. Keep the HTTP and MySQL listeners on loopback (the default): API-key authentication for the HTTP API is planned, and a smaller listening surface is good practice anyway.

Replication has a visibility caveat

Explicit-transaction writes replicate (fixed in an earlier release), but a standby can transiently see a partially-applied transaction because atomic-apply markers are not emitted yet.

Upgrade carefully

Two earlier releases could lose data when they opened a store written by an older release; current releases are fixed, and the CHANGELOG names the affected releases. Take a dump before any version change, and do not combine a version bump with a mode switch.

For the current SQL-surface compatibility detail, see the Nano documentation.

Straight Answers

Is Nano based on SQLite?

No. It is an independent engine written in Rust. It includes a SQL-dialect compatibility layer that accepts many SQLite idioms, which is a different thing from sharing SQLite's code or file format.

Can Nano open an existing SQLite file?

No. There is no direct .sqlite file access and the two file formats are unrelated. Moving an existing database is a one-time migration — export the schema and data from SQLite, reload it into Nano, and verify row counts and checksums on both sides independently. After that the SQLite file is no longer the live database. See the migration guides for the current recommended workflow.

Is Nano multi-user?

Over the wire, yes — the PostgreSQL and MySQL server listeners handle multiple clients, each with its own session and its own transaction state. Embedded is different: explicit transactions are not independent across callers sharing a handle. See concurrency and transactions.

Can embedded threads run independent transactions?

No — not on the plain embedded API. Explicit transaction state is a single process-global slot per handle, shared across clones, so two threads cannot each hold their own open transaction. Concurrent reads and single-statement autocommit writes are fine. For independent transactions, use server mode.

When should I use server mode?

When callers need independent transaction sessions, or when they need to reach the database over a network. The daemon gives you both today. Remember it is a stop-then-start handoff onto the same data directory, not a live switch.

Does PostgreSQL wire compatibility mean full PostgreSQL compatibility?

No. It means your PostgreSQL clients, drivers and tools can connect. Two concrete gaps today: collation is byte-order only with no locale support, and in the stock server row-level security is enforced through the embedded API and the REPL, with enforcement for wire clients planned. Test your queries rather than assuming parity.

Can embedded writes replicate?

Yes. Writes inside explicit transactions reach the logical WAL and replicate — this was broken in earlier releases and has been fixed. The remaining caveat is that a standby can transiently observe a partially-applied transaction, because atomic-apply markers are not emitted yet.

Are branches a security boundary?

No. Branching isolates storage through copy-on-write and nothing more. It does not sandbox an agent's external side effects — network calls, file writes, API requests all still happen. If you need a containment boundary for agent actions, build it outside the database.

Does time travel replace backups?

No. Time travel only reaches versions the engine still retains — all history by default, or the retention window you set, after which version GC reclaims older ones — and it lives inside the same store as your live data, so it does not survive losing that store. Use heliosdb-nano dump and restore for real backups. Copying a live data directory is not a consistent backup.

When should I keep SQLite?

Often. SQLite's maturity, ecosystem and file format are real advantages, and plenty of applications need nothing Nano adds. Rather than compress that into a sentence here, we wrote it out properly: see when SQLite is the better fit →

Who should use HeliosDB Nano?

Use Nano for applications that want a real SQL engine and vector search close to the code, without running a database server for it — and with the option to add one later.

Desktop Applications

Embed a full SQL engine with vector search and branching directly in a desktop app, shipped as part of your binary with no install step for the user.

Edge & IoT

Run on edge devices and constrained hosts with offline-first behaviour. Check the binary size against your own target — measure the build you actually ship.

AI Prototypes & Agent Memory

Build RAG pipelines, semantic search and agent memory without provisioning a vector service. Metadata and embeddings join in ordinary SQL.

Single-Server Deployments

Run a workload on one host with MVCC, crash-recovery WAL and optional encryption, without cluster overhead. Evaluate it against your workload first.

W

WordPress & CMS

Serve a PHP CMS over the MySQL wire protocol from the same process that holds the data, removing the separate MySQL server from the deployment.

Source, Releases & Docs

Nano is Apache 2.0 and developed in the open. The changelog is the most honest record of where the engine is — including its bug fixes.

Source & releases

github.com/HeliosDatabase/HeliosDB-Nano — source, releases and the full CHANGELOG. Current release: v4.41.1.

Install

Rust / CLI: cargo install heliosdb-nano (or cargo add heliosdb-nano to embed it). Python embedded binding: pip install heliosdb-nano-embedded, imported as heliosdb_nano.

Compare & migrate

Nano vs SQLite for the full comparison, comparisons across the tier range, and the migration guides for moving an existing application.

Ready to try HeliosDB?

Start embedded, add server access when you need it. Apache 2.0, source on GitHub.

Or ask your AI coding agent to install it for you

Copy this into Claude Code, Codex CLI, ChatGPT, Gemini, OpenCode, or any agent with shell access.