c29c09d6e8cae0108d373d2d5b69b7a3e4f4b9f0
The Node driver sends {field: /re/} as a BSON regex element (type 0x0B),
which the matcher previously only handled via the $regex operator form;
and dot paths with numeric segments (tags.0) were ignored because array
descent only recursed into embedded docs. Both are part of standard
MongoDB query semantics and were caught by the driver e2e suite.
server: unbounded Io async limit so the accept loop never wedges, and
treat header-read failures (client RST on pool teardown) as clean
disconnects. With the default cpu_count-1 limit, groupAsync's eager
fallback ran connection handlers inline on the accept-loop fiber once
that many connections were alive, stalling accept() and timing out
handshakes for further clients.
Add tests/e2e/: official driver CRUD, concurrency, and kill -9 recovery
suites (29 + 2 + 3 checks), plus unit tests for the query fixes.
mongo-light
A lightweight, embedded MongoDB-compatible document database written in
Zig 0.16. Like SQLite, it stores everything in a single file; unlike SQLite,
it speaks the MongoDB wire protocol, so real clients — mongosh, the Node.js
driver, PyMongo — connect over TCP and just work.
Quick start
zig build # build the server
zig build test # run the unit test suite
zig-out/bin/mongo-light --port 27017 --db data.log
# in another terminal:
mongosh --port 27017
> db.users.insertOne({name: "alice", age: 30})
> db.users.find({age: {$gt: 25}}).toArray()
> db.users.updateOne({name: "alice"}, {$set: {vip: true}})
> db.users.deleteOne({name: "bob"})
Features
- Wire protocol: OP_MSG (2013) plus legacy OP_QUERY/OP_REPLY (2004/2001)
for the driver handshake; hello/isMaster with
maxWireVersion: 8, so modern drivers (Node, Python, mongosh) connect without workarounds. - BSON: full parse/serialize round-trip for all common types (including binary, regex, timestamps, ObjectId), canonical MongoDB comparison order for sorting and range queries.
- CRUD:
insert,find(filter, sort, skip/limit, projection),update(multi/upsert),delete,findAndModify,count,aggregate($match,$sort,$skip,$limit,$project,$count,$groupwith$sum), pluscreate/drop/listCollections/listDatabases/dropDatabase. - Query operators:
$eq$ne$gt$gte$lt$lte$in$nin$exists$regex(hand-rolled engine: anchors,.,* + ?, character classes, groups, alternation,i/soptions)$not$and$or$nor$size$all$elemMatch, with dot paths and array multikey semantics. - Update operators:
$set$unset$inc$push($each)$pull$rename, with dot-path creation (including array indices). - Storage: append-only record log (CRC32-checked,
fsyncper write, torn-tail tolerant) with in-memory indexes rebuilt on open and automatic compaction (rewrite + atomic rename when the log grows past 16 MB). Killed mid-write (kill -9), the database recovers all committed writes; the log and compaction both work with relative or absolute--dbpaths. Records up to the announced 16 MBmaxBsonObjectSizereplay correctly. - Concurrency: a writer-preferring read/write lock splits command
execution — reads (
find,count,aggregate,list*) run concurrently across connections, writes (CRUD, DDL) are exclusive and totally ordered, and handshake/no-op commands run lock-free. The log append +fsyncstill happen under the write lock, so the crash guarantees are unchanged. Fine for light workloads.
Layout
src/
bson.zig BSON parse/serialize, ObjectId, canonical comparison order
wire.zig OP_MSG/OP_QUERY framing, message + reply builders
commands.zig command dispatch (hello, CRUD, aggregate, admin)
server.zig TCP accept loop, per-connection handlers
db.zig in-memory engine: db → collection → _id → document maps
storage.zig append-only log: records, replay, CRC validation
query.zig filter matcher, regex engine, sort, projection
update.zig update operators with dot-path navigation
main.zig CLI: --port, --bind, --db
Not (yet) implemented
- Authentication (SCRAM) — run without credentials
- Real cursors (all results are returned in one batch, cursor id 0)
- Indexes (O(n) scans)
- Transactions, change streams, replicasets
- Compression (OP_COMPRESSED)
Code style
Zig 0.16 idioms (std.Io threaded through everything, unmanaged
containers); user-declared functions use snake_case per this repo's house
style.
Languages
Zig
82.8%
JavaScript
15.5%
Shell
1.7%