An embedded SQL engine with native vector search that runs in your process — and speaks the PostgreSQL and MySQL wire protocols when you need server access. Start embedded. Add server access as your application grows. Current source release: v4.31.1.
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.
Size figures are the ones published in the Nano README for the released binary. Build conditions (feature set, target architecture, compression tool) are not yet documented alongside them — measure your own build if the exact number matters to you.
// 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])?;
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
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. |
Persistent (a data directory on disk) or in-memory (--memory, or EmbeddedDatabase::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.
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.
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 and restart-surviving indexes are a separate opt-in build (vector-persist); the default build rebuilds the index in memory.
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.
Any read can be anchored to a past moment with AS OF TIMESTAMP. Old versions are reclaimed by a watermark-based collector — the default minimum retention is one hour, configurable, 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.
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 row-level security is reachable from the embedded and REPL paths but not over the wire. See current limitations.
Vector types and operators are part of the SQL engine rather than an extension, and the same implementation serves the embedded and server paths. The vector-search feature ships in the default build; Product Quantization and on-disk index persistence are an opt-in build (vector-persist).
Durable PQ-HNSW (opt-in vector-persist):
open(); no rebuild after a process restartremove() + compact() keep recall stable under churnsearch_filtered() evaluates row predicates inside the graph walk, not afterThroughput 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.
-- Create a table with vector column
CREATE TABLE documents (
id INTEGER PRIMARY KEY,
title TEXT,
content TEXT,
embedding VECTOR(768)
);
-- Create an HNSW index (PQ requires a
-- vector-persist build)
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;
Git-like branching at the storage layer. Copy-on-write branches let you test a schema change, run an A/B experiment, or give an agent a sandbox without duplicating the dataset. Branch scope is the whole database.
CREATE DATABASE BRANCH, USE, MERGE, DROP SQL syntaxAS OFMERGE 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.
-- 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;
Anchor a read to a past moment with the AS OF clause. Useful for audit questions, for working 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 anchorsVACUUM VERSIONS forces a reclaim passTime 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.
-- 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;
HeliosDB Nano combines keyword search (BM25 scoring) with vector similarity in a single query. Reciprocal Rank Fusion (RRF) and Maximal Marginal Relevance (MMR) merge both result sets in the engine.
WHERE content MATCH 'query' (keyword) with embedding <=> $1 (semantic) in one query.PREPARE COMPILED pre-optimizes hot-path queries. Cached execution plans skip planning overhead on repeated calls.-- Keyword search (BM25)
SELECT title, bm25_score(content, 'database')
FROM articles
ORDER BY bm25_score DESC LIMIT 10;
-- Hybrid: keyword + vector with RRF
SELECT title,
rrf(
bm25_score(content, 'database scaling'),
1.0 - (embedding <=> $query_vec)
) AS score
FROM articles
ORDER BY score DESC LIMIT 10;
-- MMR for diverse results
SELECT * FROM mmr(
'articles', 'embedding',
$query_vec, 10, 0.7
);
Query graph relationships over your relational data without standing up a separate graph database. Nano adds adjacency-list storage and graph traversal functions alongside SQL, vector and full-text in the same binary.
-- Create graph edges
CREATE TABLE edges (
src INT, dst INT,
rel TEXT, weight FLOAT
);
-- Find neighbors
SELECT dst, rel, weight
FROM graph_neighbors('edges', 42);
-- Shortest path
SELECT * FROM graph_shortest_path(
'edges', 1, 99
);
-- GraphRAG: semantic + structure
SELECT n.title, n.embedding <=> $q
FROM graph_neighbors('edges', 42) g
JOIN nodes n ON n.id = g.dst
ORDER BY n.embedding <=> $q
LIMIT 5;
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 ON src/, and your codebase becomes queryable in SQL. Build with --features code-graph to enable it and the heliosdb-nano code-graph subcommand.
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.register_grammar + register_extractor) for plugging in any tree-sitter grammar.ON BRANCH '<name>' per-call override and AS OF combine in either order. RAII branch guard restores state on every early-return path.hdb_code.pause() / resume() for migrations.--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.helios_lsp_references_diff, helios_lsp_body_diff, helios_ast_diff. Accept any AS OF ref: {"now": true}, {"commit": "sha"}, {"timestamp": "iso"}.-- Index your repo as a queryable AST
CREATE EXTENSION hdb_code;
CREATE AST INDEX ON repo (path, lang, content);
-- 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;
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 queryable tables; entity linking and auto-projection happen on insert.
(1−α)·distance − α·centrality. Prefilter-aware HNSW wrapper over-fetches candidates, applies row-level prefilters, then re-scores.CREATE SEMANTIC HASH INDEX exposes subtree hashing at the SQL layer. Only re-embed what changed.graph_rag_ingest_docs, _email, _issues, _qa — plus Docling-backed PDF / Office / audio / image.graph_rag_link_vector emits MENTIONS edges with weight = similarity; threshold-gated cosine top-k.-- Ingest a PDF, an Office doc, audio, an image
SELECT graph_rag_ingest_pdf('/docs/handbook.pdf');
SELECT graph_rag_ingest_office('/contracts/2026-Q1.docx');
SELECT graph_rag_ingest_audio('/calls/standup.m4a');
SELECT graph_rag_ingest_image('/diagrams/topology.png');
-- Ground an LLM query with subgraph context
SELECT answer
FROM ask_llm('What changed in the auth flow last quarter?')
WITH CONTEXT (
seed_text = 'auth flow',
seed_kinds = ['function', 'doc'],
edge_kinds = ['IMPORTS', 'REFERENCES', 'MENTIONS'],
hops = 3,
rerank = 'centrality'
);
-- Incremental re-embed via semantic-Merkle
CREATE SEMANTIC HASH INDEX IF NOT EXISTS
graph_rag_chunks_hash ON _hdb_graph.nodes;
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 with --features mcp-endpoint.
POST /mcp (HTTP JSON-RPC), /mcp/ws (WebSocket), /mcp/sse (Server-Sent Events). Plus stdio for sandboxed agents.mcp_tool! macro registers via the inventory crate. BM25, hybrid search, graph operations, and helios_graphrag_search surface automatically.notifications/progress messages flow over WS and stdio when the client opts in via _meta.progressToken.tools/list?verbose=true, helios/info JSON-RPC method, GET /mcp/info route. One round-trip for serverInfo + capabilities + verbose tool catalogue + resource list.Mcp-Session-Id. POSTs paired with an open SSE channel get progress events forwarded while the POST returns the final response.# 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"}
}
}
}
Nano includes a BaaS layer in the same binary that runs the database: auth, a REST API, realtime subscriptions and file storage, with no external service to deploy.
/docs + OpenAPI 3.0 spec at /openapi.jsonRow-level security policies are enforced on the REST endpoints via JWT claims. Note that RLS is not currently enforced for clients arriving over the PostgreSQL or MySQL wire protocols — see current limitations.
# REST API
$ curl localhost:8080/rest/v1/users?role=eq.admin&select=id,name
# 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"}
Encryption is compiled into the default build, but it is off at runtime until you configure a key. Installing Nano does not encrypt your data on its own — that is a deliberate activation step.
fips compile-time feature--tls-cert / --tls-key, shared by both listeners# Encryption is compiled in, but inactive
# until a key is supplied. Nothing is
# encrypted at rest by default.
# Supply a key, then start the server
$ export HELIOS_ENCRYPTION_KEY="$(cat ./tde.key)"
$ heliosdb-nano start --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
Nano implements both wire protocols natively, so existing clients, drivers, ORMs and admin tools connect to it directly. This is client connectivity, not full semantic parity with PostgreSQL or MySQL — expect to test your queries rather than assume every behaviour matches.
--mysql, with 5.7/8.0 compatibility modes and prepared statementsSHOW DATABASES / TABLES / COLUMNS / VARIABLES and information_schema for tool compatibilityFor the current state of SQL-surface compatibility, see the compatibility documentation rather than a single headline percentage.
# 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 |
+----+-------+-------------------+
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.
# 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")?;
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.
WordPress expects to talk to MySQL. Because Nano serves the MySQL wire protocol from the same binary that holds the data, a WordPress install can run against it without a separate MySQL server: 37/37 tests pass, no db.php drop-in, standard wpdb.
Deploy WordPress with one database process instead of a separate MySQL instance to provision, credential and supervise.
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.
SELECT * FROM wp_posts AS OF TIMESTAMP '...' reads a post or setting as it was, within the retention window. Useful for working out what a plugin changed — not a substitute for backups.
Native HNSW vector search means WordPress content can be searched by meaning without adding Elasticsearch or Algolia to the stack.
AES-256-GCM TDE is handled by the database, so no WordPress plugin is involved. It is off until an operator supplies a key — it is not automatic for every install.
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.
Any framework that speaks MySQL or PostgreSQL can point its connection string at a Nano server.
| Framework | Protocol | HeliosDB Nano |
|---|---|---|
| WordPress / Drupal / Joomla | MySQL | ✓ Native |
| Laravel / Symfony (PHP) | MySQL / PG | ✓ Both |
| Ruby on Rails | MySQL / PG | ✓ Both |
| Django (Python) | MySQL / PG | ✓ Both |
| Express / Fastify (Node.js) | PG / MySQL | ✓ Both |
| Rust (SQLx / Diesel) | PG / Embedded | ✓ Both |
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. Product Quantization and index persistence are an opt-in 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 |
Embedded first, server when you need it. Both examples use the shipped CLI and the published binding — run them against your own install and tell us if anything differs.
The published Python binding is heliosdb-nano-embedded, which imports as heliosdb_nano. It is version-locked to the engine release. No server, no wire protocol — the engine runs inside your process.
# 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 heliosdb-nano-embedded 4.31.1 from PyPI; the output above is the real result. The equivalent Rust API is EmbeddedDatabase::new(path) / in_memory() with execute, execute_params, query_params.
When callers need independent sessions or network access, run the same binary as a server. Each connection gets its own transaction state.
# 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).
Nano's embedded and server paths have genuinely different transaction models. Picking the wrong one is the most common way to be surprised by this engine, so here is the whole picture.
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.
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.
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.
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.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.
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 before v4.8.0 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 →
These are the current constraints as of v4.31.1. We would rather you hit them on this page than in production.
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.
Row-level security is real and works from the embedded, REPL and REST paths — but it is not reachable for clients connecting over the PostgreSQL or MySQL wire protocols in any release to date. Do not treat it as a wire-facing tenancy boundary today.
Independent overlapping explicit transactions are not safe on the plain embedded API. Use server mode for independent sessions. See concurrency and transactions.
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.
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.
The published ~32 MB / ~12 MB compressed figures come from the README without stated build conditions, and the CI size guard is not currently operational. Treat them as indicative and measure your own build if it matters.
Explicit-transaction writes replicate (fixed in v4.8.0), but a standby can transiently see a partially-applied transaction because atomic-apply markers are not emitted yet.
v4.31.1 fixes a data-loss-on-upgrade bug that affected opening a pre-4.31.1 store on 4.30.0 or 4.31.0. 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.
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.
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.
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.
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 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.
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 row-level security is not enforced for wire clients. Test your queries rather than assuming parity.
Yes. Writes inside explicit transactions reach the logical WAL and replicate — this was broken before v4.8.0 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.
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.
No. Time travel only reaches versions the engine still retains — the default minimum retention is one hour, and 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.
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 →
Applications that want a real SQL engine and vector search close to the code, without running a database server for it — and the option to add one later.
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.
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.
Build RAG pipelines, semantic search and agent memory without provisioning a vector service. Metadata and embeddings join in ordinary SQL.
Run a workload on one host with MVCC, crash-recovery WAL and optional encryption, without cluster overhead. Evaluate it against your workload first.
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.
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.
github.com/HeliosDatabase/HeliosDB-Nano — source, tagged releases and the full CHANGELOG. Current release: v4.31.1.
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.
Nano documentation, the quickstart, the concurrency and transactions guide, and the REST API reference.
Nano vs SQLite for the full comparison, comparisons across the tier range, and the migration guides for moving an existing application.
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.