InlaySQL keeps SQLite's model — one file, no server, embed it anywhere — and adds concurrent writers, native vector and BM25 retrieval, and a MySQL wire protocol your ORM already speaks.
Still in development, and under heavy testing right now. The on-disk format is pre-1.0 (recreate, not migrate) and the MySQL server's wire parser is young — it now refuses a bind that would expose the database by accident, but its fuzzing campaigns have only just been written and have not yet run. See What this is not below before you rely on it for anything real. If the idea here is one you'd use, tell us in Issues — what you'd need first is exactly what decides what gets built next.
A vector column and a full-text index live in the same file, in the same transaction, behind the same SQL. Fusing them is one statement — not two queries and a client-side merge.
-- A vector column is a column. An index is an index. CREATE TABLE docs ( id INTEGER PRIMARY KEY, body TEXT, embedding VECTOR(384) ); CREATE INDEX docs_body ON docs (body); -- BM25 CREATE INDEX docs_embedding ON docs (embedding); -- HNSW -- Hybrid search, ranked by reciprocal rank fusion, in the engine. SELECT id, body, fuse(vector_score(embedding, ?), bm25_score(body, ?)) AS score FROM docs ORDER BY score DESC LIMIT 10;
The engine below is compiled to WebAssembly and running in this tab — no server, no network round trip. It seeds a small corpus, indexes it for BM25 and vector search, and answers with the same code the native build runs.
loading the module…
The database is one file, in the same format the CLI reads. Saving hands
those bytes to the origin-private file system; downloading gives you a
file you can open with inlaysql serve --mcp.
Every number regenerates from a script in the repository. A table that contains only wins is advertising, so the losses are here too — and the rest of them are in the list below.
| Workload | InlaySQL | Compared with | Result |
|---|---|---|---|
| Point read by primary key | 2,577,421 ops/s | SQLite, journal (durable) / SQLite, WAL | ~7.6× faster durable; 2.10× vs WAL |
| Concurrent writes, 8 writers | 1,555 commits/s | SQLite, journal | ~17× faster |
| Concurrent writes, 32 writers | 3,529 commits/s | SQLite, journal | ~40× faster |
| Vector search, recall@10 1.000 | 58.04 µs | sqlite-vec |
~11× faster |
| Hybrid search (BM25 + vector) | 167.0 µs | DuckDB, pgvector | ~70-90× faster |
| Secondary-index inner join, 20k × 160k rows | 3.38 ms | SQLite | ~8× faster |
PK inner join, LIMIT 10 |
3.29 µs p50 | SQLite, journal (3.46 µs p50) | faster in all three runs on p50; a wash on throughput, inside an 18% spread, so not claimed as a win there |
| Indexed range scan, 50 rows | 7.13 µs | SQLite, journal (6.63 µs) | ~1.1× slower |
| Batch insert, 100 rows per statement, containerised like the servers | 88,456 rows/s | MySQL 8.4 (52,935 rows/s), PostgreSQL 17 (100,305 rows/s) | 1.64× MySQL, 0.88× PostgreSQL (tie inside a 21% band) |
| Server writes over MySQL wire, 8 connections | 1,456.5 ops/s | MySQL 8.4 (4,837.8 ops/s) | ~2-3× slower |
One developer machine, one sitting. These multiples are rounded to the
precision the measured run-to-run spread supports, so a bare "3.3×"
would claim more than the measurement has. Reproduce it; do not trust it —
./bench/run.sh and ./bench/compare.sh are in the
repository, and
BENCHMARK.md
records the machine, the durability settings, the provenance of every
table and the asymmetries that remain —
SCOREBOARD.md
is the win/loss matrix with its fairness audit.
Scheduled work rather than oversights — one line each, with the detail behind it. A page that lists only wins is advertising.
LIMIT 10 join; batch insert is 0.88×
PostgreSQL 17 like for like — a tie, inside a 21% A/A band; server
writes at eight connections are
~0.30× MySQL 8.4, because the single-writer gate is the wall. Every
cell, with its spread and a fairness audit, is in
BENCHMARK.md
and
SCOREBOARD.md.
# Use it like SQLite: download the library, one small loader, SQL. # Prebuilt for macOS (Apple silicon) and Linux — from the releases page: # https://github.com/inlaySQL/inlaysql/releases # PHP (FFI is built in; Python and Ruby are the same shape) $db = new InlaySQL('app.inlay'); $db->run('INSERT INTO users (name) VALUES (?)', ['Ada']); $rows = $db->run('SELECT id, name FROM users'); // → {"columns":["id","name"],"rows":[[1,"Ada"]]} # From source — the CLI, and hybrid retrieval end to end git clone https://github.com/inlaySQL/inlaysql && cd inlaysql cargo run --example hybrid_search # Or serve a database over the MySQL wire protocol, # then point your ORM at 127.0.0.1:3306 cargo run -p inlaysql-mcp --bin inlaysql -- serve --mysql app.inlay
Per-language loaders (PHP, Python, Ruby, C#, Java) are in the client-languages guide. Reading order for the repository: README for the pitch and the architecture, TESTING.md for what is covered and what is not, and docs/PLAN.md for what is being built next and why.