v0.11.0 · stable
Elyra SQL Anywhere · The Engine

SQLite, everywhere
your app runs .

SQL Anywhere is a fork of SQLite that keeps full file-format and API compatibility while adding what you need to run SQLite beyond a single local file: embedded replicas, a network server, and continuous backup to object storage.

Reads
<1ms
Compat
100%
License
MIT
main.rs
// An embedded replica that syncs in the background
let db = Builder::new_local_replica("local.db")
    .build().await?;

db.sync().await?;        // pull latest frames
let conn = db.connect()?; // reads served locally

conn.query("SELECT email FROM users", ())
    .await?;
Download · sqld v0.11.0

Prebuilt server binaries

Grab the sqld server for your platform, or build from source.

All releases

Using prebuilt extensions? 0.6.0 changes their ABI once — re-download them.

0.6.0 gives the loadable-extension ABI a version field it never had. A prebuilt extension built against 0.5.2 or earlier — the crsqlite archives on every release from v0.3.1 through v0.5.2 — reads that new interface-version integer where it expects a function pointer and calls it: loading one into 0.6.0 or newer kills the process (verified: SIGBUS). Use the v0.6.0 or later builds.

Nothing else moves — the SQLite C API is unchanged and database files stay byte-compatible with stock SQLite, so only loadable extensions are affected. And this is the last time the layout can shift: from 0.6.0 an extension detects a mismatch instead of crashing on it, and the release manifest records the version each artifact was built against, so a bad pairing is caught before the file is ever loaded.

S.01 — What it does

The SQLite you know,
ready for the network.

Start with the embedded Rust API for sub-millisecond local reads. Promote it to a replica, expose it over HTTP with the server, and stream the WAL to S3 — without ever leaving the SQLite file format.

Embedded replicas

Keep a live, local copy of a remote database inside your process. Reads are served locally in sub-milliseconds; writes sync in the background.

Replication

Ship the write-ahead log from a primary to replicas with frame injection — the building blocks for read replicas and embedded replicas alike.

Server mode (sqld)

Expose SQLite over HTTP and WebSockets so remote clients talk to it like PostgreSQL or MySQL — with namespaces and an admin API. Since 0.11.0 sqld has a Grafana dashboard (statements, errors, per-namespace load and size, WAL checkpoints, the tokio runtime) and a compose file that runs sqld, Prometheus and Grafana with it loaded, and every metric it exports is described with its type, labels and unit. Until 0.11.0 two sqld could serve the same data directory, and the replication log's frame numbers diverged under replicas and backups; a server now takes an exclusive flock on the data directory before it writes anything there, and a second one exits naming the process that holds it. The sqlanywhere_server_user_http_fault label protcol is now protocol, so a query that grouped by the old name needs the new one.

Multitenancy & namespaces

Serve many independent databases from a single sqld instance, with path-based routing and wildcard domains for local development. Until 0.10.0 a transaction that wrote to main and read an attached namespace aborted sqld: the attached database gave back the write slot main's write was holding, and INSERT INTO main.t SELECT … FROM attached.t in a transaction was enough. Only the WAL that took the slot now gives it back.

Bottomless storage

Continuously stream the write-ahead log to S3-compatible object storage for durable, point-in-time-recoverable databases. Until 0.10.0 a bottomless backup could only be verified on a machine of the byte order that made it; each generation's .meta object now records the order, and a restore verifies in it.

Encryption

At-rest and in-transit encryption built in, so sensitive data stays protected on disk and across the wire. Until 0.9.0 a primary encrypted at rest could not restart after a crash that left frames in its WAL: the checkpoint sqld runs at startup opened the database without its cipher and key and failed with file is not a database. It now opens it like every other connection to an encrypted database.

Offline writes

Accept writes while disconnected and reconcile when the connection returns — ideal for edge and mobile.

Search is one engine

Full-text (FTS5), faceted, vector and hybrid search are all the same database file and ordinary SQL — no separate search product to deploy, sync or keep consistent. Vector runs on a built-in, quantizable DiskANN index. Since 0.10.0 hybrid search is one call: hybrid_top_k(vector_index, fts_table, vector, query, k [, rrf_k]) fuses a DiskANN vector ranking and an FTS5 keyword ranking with Reciprocal Rank Fusion, and replaces the 15-line CTE the docs recommended, returning exactly what that query does. Since 0.9.0 vector_index_stats(index) describes a DiskANN index as JSON — its parameters with defaults filled in, how many nodes it holds, how many neighbours they have on average, how many have none or are full, and how many bytes its node blocks take — and with rows_read and a recall@k query in plain SQL that is the tuning view. And vector_top_k takes its query vector from another table, a top-k per row, where FROM q, vector_top_k(idx, q.v, 10) failed with xBestIndex malfunction. Since 0.11.0 vector search is something to tune with numbers instead of guesses: search_l is set per query, so recall is traded for speed per query without rebuilding the index; recall measures recall@k against an exact search over a sample of the table's own rows, recall::tune returns the smallest search_l that reaches a target, and tune_compression finds the smallest index that still reaches it — on 2000 vectors, 1.44 MB at recall 1.0 uncompressed, 0.80 MB at 0.999 with float8. hybrid_top_k weights one ranking over the other with vector_weight and fts_weight. And a vector_top_k asking for more rows than search_l now gets k of them, where it returned search_l + 1 without a word.

Pluggable embeddings

Bring your own semantic model via the Embedder trait — plug in a real embedding model for semantic search, or swap providers, without changing your queries. Since 0.7.0 an embedding column keeps a vector column in step with a text column: triggers enqueue a job when the text changes, so a write never waits on a model, and workers embed in the background; a worker writes a vector only if the row still holds the text it embedded, so a slow model cannot leave a stale vector behind an edit, and a column records its model, so a worker running another one is refused.

Cache, queue & pub/sub

Storage primitives as chapters, not products: a KV cache with TTL, an atomic-claim job queue, and a replication-log pub/sub bus — all ordinary SQL, no Redis, SQS or Kafka to run alongside. Since 0.7.0 the contracts compile into a typed Rust API, one function per operation, such as queue::claim and cache::lock, and into contracts.json, the same operations as data for generating clients in other languages; CI fails when a contract changes without the API following. Since 0.9.0 the same documents also generate a PHP client: Queue, Cache and PubSub classes on PDO with install() and one typed static method per operation, multi-statement operations in one transaction that rolls back on failure — a Composer package, sqlanywhere/contracts, checked on PHP 8.1 and 8.4. And since 0.10.0 a TypeScript client too: one module per contract with install, the statements as constants and one typed async function per operation, no runtime dependencies, with adapters for @libsql/client (a sqld server over HTTP, or a file) and node:sqlite. And since 0.11.0 a Python client: the queue, cache and pub/sub contracts as typed Python functions, on the standard library's sqlite3 or on sqld over HTTP, with no dependencies, an operation of several statements one Hrana batch over HTTP — tested on Python 3.9 and 3.13 and against sqld.

Collaborative vector index

The flagship combination of CRDT offline merge × DiskANN × inline embed(): several devices index offline and independently, merge conflict-free, then every device can vector-search over every device's documents (experimental).

CRDT offline merge

The vendored cr-sqlite extension turns tables into conflict-free replicated relations (crsql_as_crr) — edit offline on many devices and merge deterministically by exchanging change rows.

Database branches

Experimental, since 0.7.0: fork a database into an empty one, write the fork freely, and merge it back through cr-sqlite, so the parent is never locked and keeps taking writes while the branch exists. diff is the review step: every row the branch changed beside the parent's current value, the conflicts a merge would hit, and for a vector column the cosine distance the embedding moved. merge never fails on a conflict and reports which side won each one, and merging again sends only what was written since. It is shaped for an AI agent that works on its own branch and is merged after review. Since 0.8.0 a branch carries its jobs and events: a job it enqueues or an event it publishes is listed in the diff and enqueued or published on the parent by the merge, in the same transaction as the data and exactly once, where a merge silently left an agent's queued work behind. And merge_if merges only when the diff passes a MergePolicy — no_conflicts, read_only, no_deletes_in, max_rows, max_vector_distance, no_jobs_on — and otherwise writes nothing and returns the diff with every violation, so an agent's harmless changes merge by themselves while the rest wait, with the reasons, for a person. Since 0.9.0 a lens chooses the rows a branch holds: per tracked table a SQL predicate, relational or semantic, so fork_lens makes a branch with only those rows, each with its whole history, pull_lens keeps them in step with main, and a merge never deletes what the branch does not hold. And fork_file forks a large database by copying the file and giving the copy an identity of its own: 20,000 rows fork in 16 ms instead of 309 ms. Since 0.10.0 a sqld namespace is branched with one admin call and merged back three ways: POST /v1/namespaces/:ns/branch/:name forks the namespace twice, the branch, writable, and the same moment, read-only, and Merge3 applies rows only one side changed, lets updates to different columns of a row both stand, and treats rows changed differently on both sides as conflicts — refuse, keep main, or take the branch. Ordinary tables, no CRDT, no history.

Querying the past

Since 0.7.0 a namespace can be restored from its bottomless backup as of any moment, into a read-only namespace named for that moment, orders@20261001T120000Z, that you ATTACH beside the live tables: what changed is a join, and a deleted row is an INSERT … SELECT. The name is deterministic, so asking twice, or two clients at once, yields one namespace, and with ttl_seconds the primary deletes it, with its backups, that long after it is made. Since 0.9.0 compare says what changed between two versions of a table, and how far its embeddings moved: every row inserted, updated or deleted, the columns that differ with before and after, and the cosine distance for vector columns, with rows listed by how far their vector moved. No CRRs or history needed; it works on a local file and, inside a transaction, on sqld.

Live queries

Since 0.7.0 a SELECT can keep itself current: each refresh returns the rows inserted, updated and deleted, or nothing when the result is the same, and skips the query entirely when nothing was written since, which costs one local pragma. It follows writes on its own connection, commits by other connections and the frames an embedded replica pulls in on sync, and rows can be keyed so an edit is an update rather than a deletion and an insertion. Since 0.8.0 a live query over tracked tables re-runs only for writes to those tables, on embedded replicas too, so a busy queue in the same file no longer makes every live query re-run. The tables are read from the query's own EXPLAIN, through views, joins and covering indexes; anything that cannot be traced keeps the whole-file watch. And since 0.9.0 a vector can wait for rows to come near it: Interests registers vectors with a cosine distance, and every write that puts a row's vector within reach of one publishes an event on its channel through the pub/sub contract, nearest first. Two triggers do the matching, so it works whoever writes the vector, in the same transaction as the write. Since 0.10.0 a client connected to sqld over HTTP, with no database file of its own, waits with LiveQuery::wait for the server to announce a commit that changes its result and gets the diff, without polling; sqld pushes an event per commit on GET /beta/listen, which until 0.10.0 announced commits before they were done.

Docker images

Multi-arch sqld container images (linux/amd64 + linux/arm64) published to ghcr.io/kwhorne/sqlanywhere-server with each release, alongside the prebuilt binaries. After Debian 11 left LTS at the end of June its package pool dropped packages its index still named, and 12 of the 13 image builds before 0.7.0 failed on that, the v0.6.2 release build among them, so 0.6.2 has no image; 0.7.0 builds on Debian 12, and an already-tagged release can now have its images published after a Dockerfile-only fix.

Pluggable virtual WAL

A virtual write-ahead log interface lets you swap in custom WAL backends for replication and storage.

Drop-in SQLite

Databases that don't use the extra features stay byte-compatible with stock SQLite tooling and the C API.

Extensions

ALTER TABLE type changes, randomized ROWID, WebAssembly UDFs, and xPreparedSql on top of the core engine.

Extensions you can verify

A prebuilt extension is code you download and then run with the full privileges of your database process, and installing one used to mean fetching a shared object from a release page and hoping. Since 0.6.0 every release carries SHA256SUMS, a MANIFEST.json describing each artifact and the ABI version it was built against, and a detached Ed25519 signature over that manifest; verify-extensions checks the signature, then each digest, then that the artifact’s interface version is one the host implements, and an unsigned release cannot pass as a signed one. The ABI describes itself now too: the thunk handed to an extension carries an interface version, so a newer extension loaded by an older host detects the mismatch instead of reading past the end of the structure and calling an integer as a function. And the honest edge is on the page it links rather than hidden — the loader still opens any file you point it at, and enforcing signatures is a policy decision whose open questions are written down instead of pretended away.

Bindings everywhere

Async Rust API plus C and WebAssembly bindings — embed the same engine from native apps to the browser.

vectors.sql
-- A table with a 4-dimensional vector column
CREATE TABLE movies (
  title     TEXT,
  embedding F32_BLOB(4)
);

INSERT INTO movies VALUES
  ('Inception', vector32('[0.1,0.2,0.3,0.4]'));

-- Build an approximate-nearest-neighbour index
CREATE INDEX movies_idx
  ON movies (libsql_vector_idx(embedding));

-- Query the nearest neighbours
SELECT title FROM vector_top_k(
    'movies_idx', vector32('[0.2,0.1,0.4,0.3]'), 5)
  JOIN movies ON movies.rowid = id;
New in 0.3 — vector-native edge

Embeddings are a
first-class column.

Store embeddings directly in vector columns and run fast similarity search with a built-in DiskANN index — no separate vector database, no extension to install. It is just SQL. 0.3 turns it into a full local-first RAG toolkit.

  • Hybrid search — fuse vector similarity with FTS5 keyword search via Reciprocal Rank Fusion
  • Quantized indexes (float16 | float8 | 1-bit) shrink the on-disk graph up to 5.5× for edge devices
  • embed(text, dims) turns text into a vector literal inline — no pre-compute step
  • Approximate nearest-neighbour search via libsql_vector_idx + vector_top_k
Vector search & local RAG guide
Full SQLite compatibility

A superset, not a
rewrite.

SQL Anywhere keeps the SQLite file format and C API intact. Your existing databases, queries, and tooling keep working — you opt into replication, the server, and bottomless storage only when you need them.

  • Byte-compatible with stock SQLite files
  • Same C API — existing bindings keep working
  • Add replication and server mode incrementally
  • Free and open source under the MIT license
Read the docs
shell
# A drop-in SQLite shell
$ ./sqlanywhere mydata.db
SQL Anywhere version 0.11.0
  (based on SQLite version 3.43.0)

# Run it as a network server
$ sqld --http-listen-addr 0.0.0.0:8080
✓ HTTP + WebSocket  (Hrana)
✓ bottomless → S3   (WAL stream)
Colophon — Get started

One engine,
local to global.

Embed it, replicate it, or serve it over the network. Free, open source, and fully SQLite-compatible.

Elyra SQL Anywhere · v0.11.0 Built in Norway · MMXXVI