An embedded SQL database written in Rust, inspired by Turso. Page-based storage with a B-tree primary index, write-ahead log for crash safety, native async API, parallel query execution, vector search, change data capture, and an interactive REPL.
cargo run --releaseVelociDB v0.1.0
Database: veloci.db
Type '.help' for help, '.exit' to quit. Statements end with ';'.
velocidb> CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);
OK
velocidb> INSERT INTO users VALUES (1, 'Alice', 30);
OK
velocidb> SELECT * FROM users WHERE age > 25 ORDER BY name LIMIT 10;
id | name | age
-----------+-----------+-----------
1 | Alice | 30
1 row(s) returned
velocidb> .schema users
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);
REPL niceties: arrow-key history, persistent history file
(~/.velocidb_history, or $VELOCIDB_HISTORY), multi-line statements
terminated by ;, and .help, .tables, .schema [name], .cdc on|off,
.changes [seq], .exit.
use velocidb::Database;
fn main() -> anyhow::Result<()> {
let db = Database::open("my_database.db")?;
db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")?;
db.execute("INSERT INTO users VALUES (1, 'Alice')")?;
let results = db.query("SELECT * FROM users ORDER BY id")?;
println!("Found {} users", results.rows.len());
Ok(())
}use velocidb::Builder;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let db = Builder::new_local("my_database.db").build().await?;
let conn = db.connect()?;
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)").await?;
conn.execute("INSERT INTO users VALUES (1, 'Alice')").await?;
let results = conn.query("SELECT * FROM users ORDER BY id").await?;
println!("Found {} users", results.rows.len());
Ok(())
}Every call is offloaded to the tokio blocking pool, so async tasks never stall the reactor; concurrent read futures execute in parallel.
Exact (brute-force) K-nearest-neighbour search with Turso/libSQL-style syntax. Distance computation, filtering, and sorting are parallelized with rayon on larger tables.
CREATE TABLE docs (id INTEGER PRIMARY KEY, title TEXT, embedding F32_BLOB(3));
INSERT INTO docs VALUES (1, 'alpha', vector32('[1.0, 0.0, 0.0]'));
INSERT INTO docs VALUES (2, 'beta', vector32('[0.0, 1.0, 0.0]'));
-- KNN: nearest 5 documents by cosine distance
SELECT id, title, vector_distance_cos(embedding, vector32('[1, 0, 0]'))
FROM docs
ORDER BY vector_distance_cos(embedding, vector32('[1, 0, 0]'))
LIMIT 5;Supported metrics: vector_distance_cos, vector_distance_l2,
vector_distance_dot. The same search is available programmatically:
use velocidb::{Database, DistanceMetric};
let db = Database::open("my_database.db")?;
let neighbors = db.vector_search("docs", "embedding", &[1.0, 0.0, 0.0], 5, DistanceMetric::Cosine)?;
for (distance, row) in neighbors {
println!("{distance:.4}: {:?}", row.values);
}let db = Database::open("my_database.db")?;
db.enable_cdc();
db.execute("INSERT INTO users VALUES (2, 'Bob')")?;
db.execute("DELETE FROM users WHERE id = 1")?;
for change in db.changes_since(0) {
println!("#{} {} {} rowid={}", change.seq, change.op, change.table, change.rowid);
}Every committed INSERT / UPDATE / DELETE is captured with a monotonically
increasing sequence number and before/after row images — useful for
replication, cache invalidation, or audit trails. In the REPL: .cdc on,
.changes [seq].
Storage and durability
- 4 KB page-based storage on a single data file.
- Write-ahead log (
<db>-wal) with CRC32-checked records. Each write statement runs as one atomic group: the WAL is fsynced on commit, then the modified pages are applied to the data file, fsynced, and the WAL truncated. - Crash recovery on open: committed groups in the WAL are replayed; partial / torn / uncommitted records are discarded.
- DashMap-backed read cache with bounded capacity.
Indexing
- B-tree primary key index with full split + merge + redistribute paths for both leaf and internal nodes (proptest covers random insert/delete sequences).
SQL surface
CREATE TABLE(INTEGER / REAL / TEXT / BLOB /F32_BLOB(n)/VECTOR(n)columns;PRIMARY KEY,NOT NULL,UNIQUEconstraints)DROP TABLEALTER TABLE t RENAME TO new | RENAME COLUMN a TO b | ADD COLUMN c type | DROP COLUMN cINSERT INTO ... VALUES (...)with optional explicit column list; vector literals viavector32('[...]'),vector('[...]')or bare[...]SELECT [* | cols | COUNT(*) | vector_distance_*(col, vec)] FROM t [WHERE ...] [ORDER BY col | vector_distance_*(col, vec) [ASC|DESC]] [LIMIT n]UPDATE t SET col = val [, ...] [WHERE ...]DELETE FROM t [WHERE ...]WHEREsupports=,!=,<>,>,<,>=,<=,LIKE, withANDBEGIN/COMMIT/ROLLBACK(see limitations below)
Vector search (Turso-inspired)
F32_BLOB(n)/VECTOR(n)column type with dimension enforcement.- Distance metrics: cosine, euclidean (L2), dot product.
- Exact KNN via
ORDER BY vector_distance_*(...) LIMIT k(top-k selection, not a full sort) orDatabase::vector_search/AsyncConnection::vector_search.
Async and parallel (Turso-inspired)
- Native async API:
Builder→AsyncDatabase→AsyncConnection(async-iofeature, enabled by default). - WHERE filtering, ORDER BY sorting and vector distance computation run on the rayon thread pool once a query touches ≥ 1024 rows.
Change Data Capture (Turso-inspired)
- Opt-in change log of committed INSERT / UPDATE / DELETE with sequence
numbers and before/after row images; poll with
changes_since(seq).
Concurrency
- Writers serialized at the
Databaselevel so the pager only ever has one active WAL group. Readers run concurrently with each other. - Lock-manager-based per-table shared/exclusive locking for in-flight transactions.
- Single-writer. All write statements take a global writer mutex. Reads can be concurrent with each other but a single in-flight write blocks other writes for its duration.
- Multi-statement transaction rollback is incomplete.
BEGINandCOMMITrelease/acquire locks correctly, butROLLBACKdoes not undo storage mutations made by earlier statements in the transaction. (Each statement is its own WAL group; a future change can fold an explicit transaction into a single WAL group.) - No
JOIN,GROUP BY, sub-queries, no indexes other than the primary key. - Single primary key column. Composite primary keys are not supported.
- Vector search is exact. Every query scans all candidate rows (in parallel). Approximate indexing (HNSW/DiskANN-style) is future work, mirroring Turso's own roadmap.
- Embedded only. The async API runs in-process; there is no network server.
- CDC is in-memory. The change log is bounded (default 65,536 events) and not persisted across restarts.
The crate exports several modules that explore advanced storage and concurrency techniques. They are not used by the SQL engine today and are exported only so the experimentation is visible:
mvcc— Multi-version concurrency controlasync_io— Tokio /io_uringpage I/Olockfree— Lock-free page cache and queuessimd— Vectorized filter / aggregation kernelsbtree_optimized— Cache-conscious B-tree node layoutcrdt— CRDT synchronization primitivescloud_vfs— S3 / Azure / GCS-backed VFShybrid_storage— Row/columnar hybrid table layoutpmem— Persistent-memory / DAX VFS
git clone https://github.com/niklabh/velocidb.git
cd velocidb
cargo build --releaseMIT — see LICENSE.