Changelog

All notable changes to OxiDB, organized by version.

v0.43.0

2026-08-10 latest

Added — a fourth engine: recommendations (oxidb-rec)

  • “Customers who bought this also bought”, current the instant an order is written. No nightly batch, no Spark, no embeddings, no training — the capability is reachable by any application that can already write an order. Off by default (OXIDB_REC=1), per-database, wire-routed like SQL and TSDB (engine: "rec").
  • track / related / for_basket. Baskets ingest as pair-count increments independent of catalogue size (idempotent on basket id; oversized baskets skipped and counted); related is one sparse row lookup; for_basket scores the whole cart and never recommends what is already in it. Cold start returns empty — no evidence is not a recommendation.
  • Scoring is a query parameter, never a rebuild: llr (default — Dunning’s log-likelihood), cosine, jaccard, lift, count. The default was frozen on evidence: validated against 541k real retail orders, raw counts crown the shop’s global bestseller on a co-occurrence of two, and cosine medals one-off coincidences exactly where the theory predicts.
  • Time is built in: eight rolling buckets with lazy per-row shifts (no global sweep exists anywhere) and an exponential half-life query parameter — “rising together right now” is a query, not a pipeline. Durable via MANIFEST + snapshot + WAL, crash-consistent, auto-checkpointing.
  • COBRA stored procedures reach itdb.rec_related({...}), db.rec_track, and, crossing into the document engine, db.vector_search: one JSON boundary with the same validation as the wire.

Changed — full-text search stops being a memory liability

  • The text index moved to disk (.mtidx, both the collection and blob engines): a 1M-document FTS collection went from 1 GB resident to zero — 38 MB total, the documents themselves — and opening it no longer rescans the collection. The legacy _fts/index.json is migrated automatically on first open and removed.
  • A search’s memory no longer grows with the corpus. Query terms are processed rarest-first under a scoring cap (OXIDB_FTS_SCORE_CAP): 7.3 MB flat where a common term used to cost 25–51 MB per query, multiplied by concurrency.
  • Uploads can’t weaponize extraction: DOCX/XLSX readers stop pulling at a cap (OXIDB_FTS_MAX_EXTRACT_BYTES, default 4 MiB) so a decompression bomb is never inflated; highlighting scans a bounded window (OXIDB_FTS_MAX_HIGHLIGHT_BYTES, 256 KiB); indexing jobs no longer carry the uploaded bytes through the queue. Equal-scored results now tie-break deterministically — pagination over ties was quietly broken.

Changed — bulk DML costs what it touches

  • A bulk SQL DELETE/UPDATE no longer materializes the table — rows stream past the predicate, range predicates reach the index, and a match without RETURNING costs 8 bytes, not a row. DELETE ... LIMIT n (bindable as ?) makes a purge loop expressible against a table far larger than memory. A row-lock protocol hole around LIMIT — a concurrently-committed lower-id match could displace a locked row — was found and closed before release.
  • Unindexed document scans hold a window, not the collection: peak transient memory per scan dropped 12.6×, and a scan that stops at its first match is 5–7× faster.

Fixed — index builds: concurrency-safe, then non-blocking

  • Building a document index while writes were in flight corrupted it — concurrently inserted documents were permanently unreachable, updates answered under stale values, and a UNIQUE index accepted duplicates written during its own build. Fixed, then made non-blocking: create_index is now the CREATE INDEX CONCURRENTLY shape — writers stall ~25 ms at 1M documents instead of 916 ms. UNIQUE builds stay blocking on purpose. Upgrade note: an index built under concurrent writes on ≤ 0.42.12 may be missing documents; drop_index + re-create repairs it — re-running create_index does not.
  • The Cobra VM caught up with the 0.13 compiler: portable format v2 and the eleven fused opcodes — freshly compiled stored procedures used to be refused, or die at runtime on the first fused instruction.

Added — operations

  • OXIDB_READY_FILE — the server atomically writes each listener’s actual bound address once everything is up, and auto binds kernel-assigned ports (OXIDB_PG_PORT, MQTT, AMQP, S3, OxiMem): what postmaster.pid does for PostgreSQL. Spawning tools stop guessing ports and stop probing them.

v0.42.12

2026-08-08

Added — realtime subscriptions that filter, and events that carry the document

  • A change event now carries the document. Updates deliver the post-image, deletes the pre-image, and an upsert that inserts delivers the inserted document — previously an insert event born of an upsert sent no document at all, breaking its own contract. A subscriber no longer has to issue a read per event to learn what changed. Post-images ride the same reference the document cache already holds, so with no subscribers there is no cost. Found while building this: every update inside a transaction was reported as an insert, because the commit path discriminated on a field that is always empty in the default storage mode.
  • WebSocket subscription filters speak the full query language. The old matcher compared top-level fields for equality only, so every operator filter silently matched nothing. Filters are now parsed once at subscribe time with the engine’s own parser and evaluated by the engine’s own matcher: $gt, $in, $or and — for a live map viewport — $geoWithin and $near all work. An unparseable filter is refused at subscribe time rather than stored as one that never fires.
  • Backpressure coalesces instead of dropping. A subscriber that falls behind used to lose events outright. The newest event per document is now deferred in a per-subscriber queue and flushed when the channel drains, so a slow client converges on every document’s latest state rather than missing it. The dropped-event counter now counts only genuinely lost events. Replay buffer size is configurable with OXIDB_CHANGE_REPLAY_EVENTS (default 4096).
  • Upsert over REST and WebSocket. PATCH on a collection and the WebSocket update both accept "upsert": true. An upsert that inserts is a create, so the CREATE rule is adjudicated against the exact document the engine would insert — the same synthesis the upsert path itself runs, so the preview cannot drift from the write. WebSocket find now honours sort/skip/limit (it returned the whole collection before, whatever was asked), and creating an index over REST with an unknown type is refused with 400 instead of quietly building a field index and answering 200.

Added — geospatial: polygons, geofences, and $geoNear

  • Polygons. $geoWithin with a GeoJSON Polygon (exterior ring plus holes) and $geoIntersects. Geofences — “is this vehicle inside the delivery zone” — are finally expressible. A polygon spanning more than 180° of longitude is refused as ambiguous rather than answered on a guess.
  • $geoNear aggregation stage writes the distance in meters into a field of your choosing and orders nearest-first, so clients stop re-deriving haversine distances themselves. It parses into a synthesized leading $match, which means the geo index, candidate intersection and predicate pushdown serve it for free.
  • Nearest-k without inventing a radius. $near with a limit and no $maxDistance is served by an expanding-ring search over the geo index: grow the circle until enough full-query matches lie inside it, at which point everything not nominated is provably farther. “The 10 nearest” no longer requires guessing a distance, and no longer scans the collection. explain reports it as GEO_KNN.
  • A geo query can now use your other indexes too. Geo candidates intersect with field and composite index results; previously any usable field index switched the geo index off, so a query like “active vehicles near me” scanned every active row. This runs through the single entry point every find/count/update/delete/transaction/explain path already shares, so plans cannot drift apart.
  • The geo index is disk-backed (.geoidx), merged at the same persist tick as the other on-disk indexes. A clean start opens instantly with no document scan; a crash rebuilds; a corrupt file is refused and rebuilt rather than trusted. As with the other index bases it is a hint — geo predicates always verify against the live document, so a stale entry self-corrects instead of returning a wrong answer.
  • Security rules can express location policy. The rules language gains ordering comparisons (<, <=, >, >= — numbers numerically, strings lexicographically, mixed types never comparable so the rule fails closed) and in against a document array field or a literal list. “Only my followers can see my location” is now writable.

Added — geo across the other surfaces

  • Redis-compatible GEO commands: GEOADD, GEOPOS, GEODIST, GEOHASH, GEOSEARCH (by member or coordinates, by radius or box, with COUNT and the WITH* modifiers). Redis’s own design is reproduced rather than approximated — a GEO key is a sorted set scored by the interleaved geohash — so persistence, WATCH and ZREM work on GEO keys with no extra machinery.
  • MQTT → collection bridge (OXIDB_MQTT_INGEST): declarative topic-filter:collection routes with MQTT wildcards, so a published message becomes a queryable document before the QoS acknowledgement — write-before-ack, like the broker itself. A malformed route specification refuses startup rather than dropping messages silently.
  • Time-series tracks: a distance operation returns path length in meters per group or bucket (consecutive-fix haversine, computed per tag set so grouped vehicles never chain into one another), and track returns the fix list, Douglas-Peucker simplified to a tolerance in meters so a day-long track need not ship 20,000 points.

Changed — idle connections are no longer dropped by default

  • OXIDB_IDLE_TIMEOUT now defaults to 0 (never). The old 30-second default disconnected long-lived connections — connection pools, subscriptions, keep-alive wire clients — unless a deployment remembered to turn it off. Idle disconnect is now opt-in: set a positive number of seconds to enable it. The abandoned-transaction backstop is unaffected: OXIDB_TX_MAX_IDLE_SECS (default 300) still rolls back a transaction whose owner goes quiet, whether or not the connection stays open. The hardening checklist still recommends a non-zero idle timeout for an exposed deployment.

Fixed — the embedded engine never started its maintenance thread

  • Only the server ever started periodic maintenance, so an embedded database (FFI, mobile, any in-process use) ran with no WAL checkpointing, no on-disk index persistence, and a full rescan on any reopen after a non-clean exit. Found on a real device: a 2-million-document database took about 60 seconds to reopen, dragging an unfolded write-ahead log behind it. Every embedded open now starts the maintenance thread — the same database reopens in about 2 seconds and the log folds at its threshold.

Added — OxiDB embedded for Flutter and Dart

  • A new package puts the whole engine in a mobile app, in-process, with no server: CRUD with the full query language, upsert, every index type (field, unique, composite, text, geo, TTL), aggregation including $geoNear, BM25 full-text search, ACID transactions, blob buckets, the SQL engine, and AES-256-GCM at rest with a 32-byte key taken from the platform keystore — never from a file in the sandbox. A Preferences view gives the familiar key-value shape on top of it.
  • Off the UI thread, by construction. Opening with OxiDb.background() runs the engine on a worker isolate and returns an API that mirrors the synchronous one call-for-call, but in Futures. This matters because the bindings are synchronous: a call that contends with a background checkpoint was measured at 450 ms mid-fold, long enough for Android to kill the frame. The UI isolate now only ever awaits.
  • A lite build for mobile drops the SQL engine and the PDF/DOCX/XLSX text extractors: 4.8 MB on disk, about 2 MB compressed per ABI, down from 9.8 MB. On a lite build the SQL commands answer an error that names the build rather than failing obscurely. Every other consumer — server, WebAssembly, pool — is byte-for-byte unaffected.
  • Two things that were already broken are fixed: the Android Java client had never compiled (unhandled checked exceptions in every method) and read response fields that do not exist, and the Swift package’s header symlink had dangled since the repository reorganisation. Both are rebuilt and verified against a compiler.

v0.42.8

2026-08-05

Security — two holes in the read path (upgrade if you use security rules)

  • A rule’s numeric comparison could publish the rows it meant to hide. Rule literals were parsed as floating point while documents store whole numbers as integers, and the two were compared by representation rather than by value — so every numeric comparison in a rule was wrong, in both directions. read: "doc.hidden != 1" matched the hidden rows too and returned them; read: "doc.hidden == 0", the same intent written the other way, matched nothing and hid everything. Numbers now compare by value. If you have a rule comparing a number, re-read it after upgrading — it starts behaving as written, which may be a change either way.
  • GET /api/{'{collection}'}/count ignored the read rule entirely. A collection with read: false refused find with 403 and answered count anyway. Because the endpoint takes an arbitrary filter, this was not merely a row-count leak but a disclosure oracle: a caller could ask “how many rows have this email” as an existence check, or binary-search a numeric field with $gte, without ever being permitted to read a document. It is now refused like find, and a row-level rule filters the count to the rows the caller may actually see. Same class as the aggregation gap closed in 0.39.21; this path was missed in that pass.

Added — hosted MCP endpoint

  • An AI assistant can now reach a project over HTTP, with nothing installed locally: POST /mcp/<project> carrying the project’s own key. It is served by forwarding that key to the REST surface, so the project’s rules, roles and rate limits apply exactly as they do to any other request — the MCP layer decides nothing. Each request is independent, so one process can serve many projects with no shared state between them. See the MCP docs.
  • oxidb-mcp --version and --help now answer instead of waiting for a host to talk to them.

v0.42.7

2026-08-05

Added — MCP server (oxidb-mcp)

  • An AI assistant can now query OxiDB directly. oxidb-mcp is a new binary speaking the Model Context Protocol — the standard Claude Code, Claude Desktop, Cursor and the other agentic editors use to reach external tools. Point a host at it and the model can orient itself in a database and answer questions about the data without anything being copy-pasted into a prompt.
  • All three engines, plus full-text search. Orientation (list_databases, list_collections with counts, list_tables, describe_table, list_indexes for documents or SQL), document queries (find, count, aggregate), sql_query, tsdb_query and text_search. explain is a tool of its own, so the model can read the plan behind a slow query and fix it rather than guess.
  • Read-only by default, and the gate that matters is the server’s. The write tools are not registered at all without OXIDB_MCP_WRITES=1 — a model cannot call a tool it was never offered — and the documented setup gives the assistant a Read-role account, so writes are refused by RBAC no matter what the tool layer asks for. That matters because anything read out of a database enters the model’s context: a hostile document that talks a model into writing has nothing to write with.
  • Results are budgeted for a context window, and truncation is stated. A read returns 50 rows by default and 500 at most; when a result is trimmed it says so and reports the true total from an index-only count. A silent cap would read as “that was everything”.
  • Nothing in the server changed. It is a standalone client binary over the native OxiWire protocol — two dependencies, no engine code — so it works against any existing deployment. Setup and the security model: MCP server docs.

v0.42.6

2026-08-02

Added — geospatial queries (document engine)

  • $geoWithin ($centerSphere, $box) and $near/$nearSphere with $maxDistance/$minDistance in meters, with implicit nearest-first ordering. Points are stored as GeoJSON Point, [lon, lat], or {lat, lon}; distances are haversine on a spherical earth. Shapes that cannot be answered correctly (planar $center, polygons) are refused by name rather than answered approximately.
  • Geohash index (create_geo_index): a query shape becomes a small cell cover, and every candidate is verified against the live document — the index can be generous but never wrong. Replicated in cluster mode, reported by list_indexes, and it works in the WASM build too: the geo globe demo runs 10,000 cities with $near/$geoWithin entirely in the browser.

Added — graph queries (document engine)

  • $graphLookup aggregation stage: breadth-first traversal issuing one $in query per frontier, so an index on connectToField serves the whole traversal. Cycle-safe, restrictSearchWithMatch prunes during traversal, and the 100k-document ceiling is a loud error — never a silent partial answer.
  • $shortestPath: Dijkstra over an edge collection, adjacency fetched lazily in batched $in lookups so endpoint indexes serve the search. Negative weights are refused by name, maxCost answers an honest “no route”, and the 500k settled-node ceiling errs loudly. The globe demo routes İstanbul→Belgium (2,600 km, 136 road segments) through this stage in the browser.

Added — transaction idle timeout (both engines)

  • OXIDB_TX_MAX_IDLE_SECS (default 300, 0 = never): a client that vanishes mid-transaction while its connection stays open no longer parks buffered state — or FOR UPDATE locks — on the server forever. The document engine rolls the transaction back and answers TransactionExpired (distinct from “not found”, so the returning client learns what actually happened); the SQL engine expires parked session transactions the same way, reported over the PostgreSQL wire as SQLSTATE 25P03 — PostgreSQL’s own idle_in_transaction_session_timeout. Steady activity resets the clock; disconnect rollback is unchanged.

Added — Go client

  • Watch change streams: per-change callbacks with resume tokens, and dropped-event overflow reported in-band rather than hidden. Plus CreateGeoIndex for the new geospatial index.

Fixed

  • A load test surfaced the server going silent after a few hundred users. Two paths held hot locks across slow work: storage scans ran caller callbacks under the data lock (deadlocking against a queued compaction the moment a callback read back into storage), and the background sync/TTL passes held the collection registry lock across seconds of file I/O — one queued writer then parked every request in the process. Both now snapshot their handles and release the lock before the slow part; a regression test pins the scan-vs-compaction case.
  • SQL: a session that failed to resume its transaction kept the dead id, so every later statement — including the ROLLBACK a client sends to recover — repeated the same error forever. The session now starts clean.
  • find_for_update no longer leaks just-acquired document locks when the transaction lookup fails.

v0.42.0

2026-07-31

Added — PostgreSQL wire protocol

  • OxiDB now speaks the PostgreSQL v3 protocol (OXIDB_PG_PORT, requires OXIDB_SQL=1). Verified with real drivers, not against the spec: psql 18, psycopg 3.3, Npgsql 8 in its default mode, pgjdbc 42.7 (including DatabaseMetaData introspection), and DBeaver 25.3 connects and browses with its native PostgreSQL driver. TLS works (sslmode=require); authentication is the same SCRAM-SHA-256 as the native port, so the same accounts work on both.
  • EF Core runs over the unmodified Npgsql provider: EnsureCreated, generated keys via RETURNING, joins, transactions, LATERAL — end to end. Getting there added binary timestamp parameters and results, AT TIME ZONE 'UTC', PostgreSQL 14's 3-argument date_trunc, and calendar INTERVAL arithmetic desugared onto the calendar-correct add_months.
  • System-catalog queries are answered with PostgreSQL's real column sets, filled from OxiDB's own schema; what cannot be answered truthfully is refused by name rather than answered empty — an empty result would be believed. Query results are buffered per message rather than written per row, which took a 1,000-row read from 526 to 1,796 ops/s.

Added — embedded EF Core (.NET)

  • UseOxiDb("Path=./mydata") runs the whole EF Core stack in-process — no server, SQLite-style: the database is a directory next to your application. The same DbContext points at a server by changing one connection string. One engine per directory is shared process-wide while each connection keeps its own interactive transaction, so concurrent transactions from multiple contexts work exactly as they do over TCP. Minimal example at examples/dotnet/EmbeddedEfCore/.
  • Closing an embedded database now checkpoints it: a cleanly exited application leaves a snapshot-only data directory (the WAL folded down to its bare header) — what a backup or sync tool wants to see. A crash still loses nothing; the log tail replays at the next open.

Changed — SQL engine: disk-first by default, measured against PostgreSQL

  • The SQL engine's disk-first mode is now the default (OXIDB_SQL_DISK_FIRST=0 restores all-resident rows): a warm 1.2M-row, index-heavy database costs ~39 MB of process memory instead of hundreds, and rows, primary keys, and every index live in mapped files bounded by the checkpoint interval — not by row count. The sparse row index costs 0.69 bytes per row, so 100M rows need ~69 MB resident where the previous layout needed 3.1 GB.
  • Measured against stock PostgreSQL 18 on identical data, 5 of 8 query workloads are at parity (0.93–1.10x: point lookups, composite-key lookups, secondary-index equality, range + ORDER BY + LIMIT, and low-selectivity index scans via a bitmap-heap-scan-equivalent cursor walk); full scans and joins run at 0.72–0.78x. The benchmark, method, and the honest remainder are in docs/query-benchmark.md.
  • Group commit: concurrent SQL writers now share fsyncs — a flat ~266 writes/s at every concurrency became ~1.2k/s at 16 connections.
  • Opening a database with a large unfolded log peaks far lower (a checkpoint now hard-links unchanged tables instead of rewriting them), and OXIDB_DOC=0 runs the server without the document engine entirely — 9.8 MB idle RSS for a SQL/TSDB-only deployment.

Added — SQL surface

  • Composite PRIMARY KEY (CONSTRAINT pk PRIMARY KEY (a, b)), enforced on every write path including transactions.
  • Integer width enforcement: SMALLINT/INT/TINYINT are range-checked constraints (PostgreSQL error code 22003) rather than silently widened; storage stays i64, and existing catalogs keep their old semantics.
  • CREATE UNIQUE INDEX is enforced — it validates existing rows, then rides the same uniqueness machinery as declared UNIQUE columns, on the live path, WAL replay, and after checkpoints alike. Shapes that cannot be enforced (multi-column, inside a transaction) are refused by name; before this, EF's IsUnique() quietly produced a plain index and duplicates sailed through a constraint the application believed in.

Fixed

  • A concurrent writer could silently overwrite another's row. A transaction reserved row ids by peeking a counter, so a simultaneous writer could be handed the same id and the later commit replaced the earlier row with no error anywhere. Ids and AUTO_INCREMENT values are now reserved from the engine as the transaction buffers each write, and the commit re-validates every key against committed state under the commit lock.
  • An indexed TIMESTAMP column probed with an integer parameter answered 0 rows — exactly what EF Core sends for every DateTime parameter. The index found the entries and the candidate verification then rejected all of them, because index-key equality disagreed with index-key ordering about cross-type numerics. Equality now agrees with ordering.
  • HAVING on a group key mis-read the projection on the streamed aggregation path (comparing the count where the key should be), and HAVING count(*) without an ORDER BY was rejected outright. Both answer correctly now.

v0.40.0

2026-07-28

Changed — OxiDB is source-available

  • Read the source, modify it, and run it in production for your own applications and business — free, at any scale, with no registration. A commercial licence covers two things and only two: offering OxiDB to third parties as a service, and distributing it, alone or embedded in a product. See the licence page.
  • This opens up the v0.33–v0.39 line rather than closing it further: those versions required a licence for any use at all, including running it yourself. Every prior release keeps the licence it was published with, and the MIT client libraries are unchanged — redistribution included.

Fixed — a transaction spanning two collections could recover half-applied

  • The reason this release matters more than its licence change. In disk-first mode — the default since 0.38 — a record reached the data file when a transaction applied it, before its commit mark was durable, and the index is rebuilt on open by scanning for active records. A crash between apply and commit therefore resurrected one collection's half of a transaction while the other half, correctly, was discarded by WAL replay. Deletes had the mirror of it: an uncommitted delete could remove a document permanently.
  • Found by running the crash half of the suite (cargo test -- --ignored), which the normal run skips: three tests failed on one bug — the Jepsen-style bank that kills the process mid-commit, the multi-collection atomicity drill, and exactly-once retry.
  • Records written by an uncommitted transaction are now written pending and are invisible to a rebuild; the record each one displaces stays live. Both are settled at the commit point, so a crash between the mark and the settle costs nothing — replay restores the write, gated on the same commit log. Compaction and checkpointing stand off while any transaction has work outstanding. The in-RAM mode had the same hole by a different route and is covered by the same guard.

Fixed

  • The .NET TCP client could not survive a server restart. It connected once and stayed connected, so a deploy left every request failing until the client process restarted. It now redials and re-authenticates — retrying only where it is safe, and never inside a transaction, where the connection is the transaction.
  • Reading with the wrong encryption key panicked instead of returning an error, reachable from a REST request: non-document bytes went straight to a parser that trusts a length in their header.

v0.39.15

2026-07-24

Fixed — WebSocket handshake

  • The server computed Sec-WebSocket-Accept with the wrong RFC 6455 GUID, so every client that validates the accept hash — browsers, ws, undici — refused the connection, and only clients that skipped the check could connect. The GUID is now the RFC value, so native WebSocket works everywhere; the JavaScript client's hand-rolled Node WebSocket workaround is gone in favour of the platform one (oxidb npm 0.26.0, Node 22+).

Added — realtime subscriptions

  • Live change streams over WebSocket, scoped to a tenant project. {"cmd":"auth","token":…,"db":"<ref>"} verifies against that project's ES256 key and pins the connection to that database.
  • Security rules are enforced on the WebSocket surface too, at parity with REST: find/count filter per row, writes check per document, and a subscribe delivers an RLS-filtered event stream — an event whose document the caller may not see is dropped rather than leaking its id. Engine fix: insert_many emitted change events with no document body (the path every REST insert takes), so subscribers now receive the inserted document.

Added — per-project file storage and backup

  • /api/storage — list buckets and objects, upload, download (original content type + ETag), delete, HEAD metadata. Isolation is per tenant database, a per-project storage quota is enforced at upload time, anonymous keys are read-only, and a non-empty bucket refuses to delete.
  • POST /api/backup?db=<ref> (admin) streams a tar.gz of that database as an attachment. Stateless — nothing is retained server-side to expire or leak.

Added — SQL: ALTER TABLE … ALTER COLUMN … TYPE

  • PostgreSQL ALTER COLUMN c [SET DATA] TYPE t and MySQL MODIFY COLUMN. Every row is dry-run cast first — an uncastable value or an over-length VARCHAR(n) aborts before anything reaches the WAL — then the column is rewritten in place, indexes rebuilt, and a checkpoint taken. Columns bound by PRIMARY KEY / AUTO_INCREMENT / UNIQUE / FOREIGN KEY are refused, since a cast can collide previously distinct keys.

Added — OxiBase

  • Email-based end-user auth — address verification, password reset and administrative user management, delivered over real SMTP. Without SMTP configured, the previous behaviour is unchanged.
  • Per-project request logs (the data plane tags each request with its target database; the control plane exposes a paged, filterable log endpoint) and TypeScript type generation — exact for SQL tables, inferred by sampling for document collections.
  • Dashboard: Files, Logs and Users tabs; editable SQL tables (rows and columns, including retype and drop); document row editing; a CodeMirror SQL editor with OxiDB dialect highlighting; one-click backup.

v0.39.10

2026-07-23

Added — OxiBase, a multi-tenant backend on top of OxiDB

  • A multi-tenant control plane. Provision isolated tenant projects, each with its own database and ES256/JWKS API keys (anon + service_role). Per-project end-user auth (sign-up / sign-in with rotating refresh tokens), path-based tenant addressing (<host>/<project>/rest/v1/…) so no wildcard cert is needed, and a static dashboard. Developer sign-in is Google-only.
  • Row-level security. A read rule that references doc.<field> is enforced per returned row — an unfiltered select returns only the caller's own rows. Security-rule expressions are validated before they are saved, so a typo can no longer become a silent fail-closed “deny all”.
  • Per-project resource quotas. Collection, SQL-table and total-document caps, owned by the control plane and enforced in the data plane at creation/insert time; shown and editable in the dashboard.

Added — observability

  • Request logging to OxiDB itself. The server can ship a structured message per request to OxiDB's own GELF ingest port, or to a lighter MessagePack log port (compact binary, no per-field auto-indexing) — so a load test's every operation lands in a queryable collection.
  • WASM OPFS persistence. The in-browser build can snapshot its database to the Origin Private File System and restore it on reload, so data survives page refreshes.

Changed — memory: one shared document cache

  • Resident memory no longer scales with the number of collections. The deserialized-value and encoded-bytes caches were per-collection, each with its own budget, so a many-collection / multi-tenant workload multiplied RAM. They are now a single process-global cache under one budget. On a 20-tenant, 100-collection, 500k-document load test this cut resident memory from ~1.3 GiB to ~307 MiB at the same throughput. Tune with OXIDB_DOC_CACHE_SIZE / OXIDB_DOC_BYTES_CACHE_SIZE.

v0.38.0

2026-07-19

Changed — disk-first document storage is the default

  • Resident memory no longer scales with your data. Document bodies now live in an mmap'd, zstd-compressed file with only a ~24 B/doc offset index in RAM; the OS page cache holds the hot part and gives it back under pressure. Measured on a live 48-hour IoT workload: 489 MB resident → ~35 MB, with identical indexed-read throughput and a ~17% write cost. OXIDB_DISK_FIRST=0 restores the always-resident mode; existing collections keep the format they were created with, so upgrading never reinterprets data. The entire test fleet now runs against disk-first, including the SIGKILL crash suites, and encryption at rest was re-verified against every file the engine writes.

Added — SQL

  • SELECT ... FOR UPDATE takes real row locks. It used to parse and silently not lock. Matched rows are now pessimistically locked until commit/rollback; concurrent UPDATE/DELETE/FOR UPDATE on them block (up to OXIDB_SQL_LOCK_TIMEOUT_MS, default 5000 — also how a deadlock resolves). Plain UPDATE/DELETE lock their rows too, closing the engine's lost-update window: two concurrent read-modify-write transactions on one row now serialize. Shapes that cannot lock base rows (joins, aggregates, DISTINCT, set ops, views, derived tables, FOR SHARE) are refused with a clear error, never accepted without the lock — and FOR UPDATE classifies as a write, so oxipool never routes it to a replica. Details.
  • {"cmd": "disk_usage"} — per-engine on-disk footprint of the data directory in one call (documents incl. the mmap'd share, SQL, time-series, blobs, OxiMem, MQTT/AMQP substrates, full-text, PITR archive, system).

Fixed — a WAL durability bug

  • Sealed WAL segments after the first were invisible. The segment scanner's fast path assumed .0 always exists, but the first online checkpoint deleted it — after which later segments were never retired (unbounded disk growth) and, far worse, never replayed at recovery: a crash between a seal and its persist lost the acknowledged writes in that segment. .0 is now a permanent empty sentinel, every checkpoint retires all covered segments (data dirs the old bug left behind self-heal), and three regression tests pin it — the crash-replay test was red before the fix.

v0.37.0

2026-07-17

Added — AMQP: the RabbitMQ protocol

  • AMQP 0-9-1 listener (OXIDB_AMQP_PORT, off by default) — RabbitMQ client code works unmodified, verified end-to-end with pika (Python), RabbitMQ.Client (.NET) and amqp091-go (Go). Work queues with competing consumers (the semantic MQTT cannot express), default + direct/fanout/topic exchanges, Basic.Qos prefetch, publisher confirms, mandatory Basic.Return, nack/reject. Anything outside the subset is refused with a clear channel error, never silently accepted.
  • Durability follows the protocol: a durable queue holding delivery_mode=2 messages is written through the document engine's WAL — the confirm is only sent after the fsync, and messages survive a SIGKILL (crash-tested; acknowledged messages stay consumed).
  • MQTT ↔ AMQP bridge via the pre-declared amq.topic exchange, the same mapping RabbitMQ's MQTT plugin uses (/ ↔ ., QoS ≥1 ↔ persistent): a sensor publishes MQTT, a worker pool consumes AMQP, one binary.
  • Faster than RabbitMQ on 5 of 6 benchmark scenarios (same Go client both sides): pipelined confirms 1.50×, confirm latency 1.74×, end-to-end throughput 1.29×, end-to-end latency 1.52×, 8-connection durable 1.11×. Behind it: per-burst fsync batching (one insert_many per pipeline burst; 264 → 53k msg/s), a cross-connection group committer (concurrent bursts share fsync rounds), and a cross-thread wake pipe in each connection's poll(2) set (delivery latency 51 ms → 0.02 ms). The one loss — single-connection durable — is the price of a real F_FULLFSYNC behind every confirm, which RabbitMQ's lazy interval flush does not pay.

Added — engine & clients

  • Online WAL checkpointing for the document engine — the write-ahead log is sealed and folded into the snapshot while writers run, so it no longer grows unboundedly between restarts. Size-triggered via OXIDB_WAL_CHECKPOINT_BYTES (default 64 MiB, 0 restores the old behaviour); crash-tested against SIGKILL mid-checkpoint.
  • Typed time-series surface in OxiDb.Client.Tcp (.NET) — TsdbWriteAsync, TsdbWriteLineProtocolAsync and TsdbQueryAsync with typed points, aggregations (TsdbAgg.MeanPercentile(p)) and epoch-ms helpers, replacing hand-rolled raw commands.

Fixed

  • S3 ETags are now real MD5s — the previous ETag (truncated SHA-256) broke the AWS SDK for .NET, which re-computes the MD5 of what it uploaded and refuses a disagreeing ETag. aws-cli, boto3, MinIO SDKs and the AWS .NET SDK now all verify uploads cleanly, with no workaround flags.

v0.36.0

2026-07-17

Fixed — WebAssembly is back

  • The wasm32 build is repaired — v0.35.0 shipped binary-only because it had been broken for a while. 2.2 MB raw, 0.76 MB gzipped, verified in a browser. TransactionId (a u64) lived in the native-only tx_log module, so five portable modules each duplicated it behind a cfg and the sixth broke the build; it now has one portable home. Also fixed: explain's Instant (wasm32 has no monotonic clock) and a shutdown() that assumed background threads.

Fixed — clustering

  • The bootstrap node published an empty Raft address. raft_init registered the initial member with no address while every learner got a real one. Invisible for as long as that node leads — nobody dials the leader — but once it loses leadership no new leader can ever reach it, and it silently freezes at its old log while the cluster commits without it. Bootstrapping with no address is now refused outright.
  • oxipool sent every SQL read to the master. The read/write classifier looked for the statement in a field the SQL wire shape does not have, so everything read as a write and replicas never served a SQL query.

Performance — compound predicates & string affixes

  • AND / OR now short-circuit. Both sides were always evaluated — x > 995 AND TRUE cost 52% more than x > 995 alone. Every compound WHERE in the engine gains.
  • STARTS_WITH / ENDS_WITH — exact, literal, case-sensitive affix tests that compare borrowed bytes in place. Ordinal StartsWith/EndsWith previously rendered as per-row SUBSTRING+LENGTH, because LIKE is case-insensitive and a needle containing % would become a wildcard. Faster than LIKE without giving up the semantics that ruled it out.
  • Against PostgreSQL over EF Core both shapes flipped from losses to wins: any_compound 0.79x→1.21x, string_multi 0.67x→2.07x.

Added

  • oxidb-server --version / --help — probing the binary with --version used to start a server on the default port. --help is also the only in-binary documentation of the env-var configuration.
  • Engine-aware backup & restore for the SQL and time-series engines, both low-lock: the engine lock is held only to pin a generation, and the archive compresses with it released, so queries and writes continue throughout.

Tested

  • p99 soak harness (plus a stdlib-only driver for hosts with no Rust toolchain). On Linux alongside live instances: 1.6M ops at 5,387 ops/s, read p99 3.9 ms, zero errors, no latency drift, RSS flat.
  • Partition tests for the sharded router — a missing shard must fail loudly, never return a plausible undercount — and for asymmetric network failures, where one direction dies and the two ends disagree about whether the peer is alive.

v0.35.0

2026-07-16

Added — instant, online schema changes

  • ALTER TABLE ADD COLUMN / DROP COLUMN are O(1) — metadata-only, no row rewrite, no checkpoint. Add or drop a column on a 500M-row live table with zero downtime. ADD pads old rows with the default on read; DROP tombstones the column in place and projects it out.
  • Checkpoint compaction reclaims a dropped column's space, folded into a checkpoint that rewrites every row anyway.

Changed — crash-atomic durability

  • MANIFEST + generation checkpoints — each checkpoint writes a whole new gen.<N>/ and promotes it with a single atomic MANIFEST rename. A crash before it leaves the previous generation whole; catalog and snapshot arities can never disagree after a crash. Recovery replays only WAL records past a watermark.

Added — EF Core provider & SQL performance

  • Official EF Core specification tests: 3832/3832 green across all 12 Northwind suites, with full migrations and design-time scaffolding.
  • OxiDB beats PostgreSQL on the EF Core benchmark — contiguous scan cache, correlated-subquery decorrelation, streamed scans with push-down, single-pass GROUP BY, index-nested-loop joins, a 48→24-byte Value, and an OxiWire binary wire format.
  • Analytics surfaceWITH / WITH RECURSIVE CTEs, set operations (UNION/EXCEPT/INTERSECT), LATERAL joins, DISTINCT ON, mode() WITHIN GROUP, CREATE SEQUENCE / NEXT VALUE FOR, multi-level correlation, case-insensitive LIKE + COLLATE.

Added — TSDB time-series engine

  • A standalone oxidb-tsdb engine (mounted like SQL, engine: "tsdb") — Gorilla-compressed columnar streams (~0.3 bytes/point), typed fields, InfluxDB line-protocol ingest, rate()/percentile, continuous-aggregate rollups, and MANIFEST-atomic persistence. Go client included.
  • OxiDB Studio desktop app — visual Query Designer, schema tree, editable result grids (macOS build on the downloads page).

Fixed

  • Linux (musl) build: qualified std::fs in the /proc stats readers.
  • Sequences persist in a separate sequences.json so a NEXT VALUE FOR can never desync a generation's catalog from its snapshots.

v0.34.7

2026-07-09

Added — full MQTT 3.1.1 broker

  • Topic wildcards+ (one level) and # (subtree) filters, backed by the OxiMem pattern-subscriber layer.
  • Retained messages — last-known-value delivery to new subscribers; empty retained payload clears.
  • QoS — QoS 1 delivery with packet ids; inbound QoS 2 completes the PUBREC/PUBREL/PUBCOMP handshake.
  • Last Will & Testament — published on abnormal disconnect or keepalive expiry (1.5× enforced).
  • AuthOXIDB_MQTT_USER/OXIDB_MQTT_PASSWORD require matching CONNECT credentials.
  • Wire-test suite speaking raw MQTT bytes (6 tests).

v0.34.6

2026-07-09

Added — S3 API

  • ListObjectsV2 continuation tokensaws s3 ls pages correctly over large buckets.
  • Lifecycle expiration?lifecycle Days rules per bucket with a background sweeper.
  • SigV4 wire-test suite under cargo test: signed roundtrip, corrupted-signature 403, multipart assembly, batch delete.

v0.34.2 – v0.34.5

2026-07-09

Added — OxiMem becomes a full Redis-class store

  • Transactions — MULTI/EXEC/DISCARD/WATCH/UNWATCH with O(1) version-counter WATCH and Redis EXECABORT semantics.
  • Server-side scripting — EVAL/EVALSHA/SCRIPT (Lua 5.4) with KEYS/ARGV, redis.call, cjson, redis.sha1hex; atomic, busy-script time limit, SCRIPT KILL.
  • Blocking ops — BLPOP/BRPOP/BZPOPMIN/BLMOVE/BRPOPLPUSH, condvar-woken.
  • Persistence — rebuild-on-boot from the SQL mirror (all five types, TTL-correct) and fast-mode snapshots.
  • Pub/sub — PSUBSCRIBE glob patterns, keyspace notifications, expired events.
  • 30+ new commands (set ops, GETDEL/COPY/GETEX, ZREMRANGEBY*, ZUNION/ZINTERSTORE, LMPOP/ZMPOP, bit ops, sub-scans with real cursors), Prometheus command counters + latency histogram.

Fixed

  • ZREVRANGE rank orderZREVRANGE key 0 0 returned the lowest member; ranks now index the descending view.

v0.34.0

2026-07-08

Added

  • Group commit — concurrent transactions share fsyncs; hot-account workloads went from ~130 to 300+ tx/s on a laptop, 1.5–2.2k tx/s on a 4-core Linux VPS at full durability.
  • SELECT FOR UPDATEfind_for_update pessimistic document locks: contenders queue instead of conflict-storming.
  • Time-series aggregation$ohlcv (tick→candle), range/time window frames, $densify, $fill.
  • PrometheusGET /metrics on the REST listener; zero dependencies.
  • explain & slow-query profiler — real planner output plus OXIDB_SLOW_QUERY_MS capture.
  • Isolation characterization — the OCC model documented and pinned by tests; Jepsen-style crash suite, fsync-failure injection, Elle-style serializability checker, Raft partition tests.

Fixed

  • Torn transaction-log writes — commit log now replaced atomically (found by the Jepsen-style suite).
  • Same-document write composition — two updates to one document in a transaction no longer clobber each other.
  • fsync-failure durability hole — a rejected commit can no longer leak into a checkpoint.

v0.29 – v0.33

2026-06 – 2026-07

Highlights

  • SQL engine (ADR-0010) — standalone relational engine beside the document engine: DDL, DML, joins, GROUP BY/HAVING, secondary indexes, parameterized queries, per-engine transactions; beats PostgreSQL 15 on the reference workload.
  • Stored procedures — CREATE/DROP PROCEDURE, CALL, named params, atomic execution.
  • EF Core & ADO.NET (ADR-0013) — OxiDb.Data (Dapper-ready) and an EF Core 9 provider; interactive transactions with savepoints.
  • Multi-database (ADR-0012) — isolated databases with per-database SQL engines, RBAC, TTL/alert threads.
  • Licensing — v0.33.0+ is proprietary (commercial licensing); TCP client libraries remain MIT.

v0.28.18

2026-05-25

Added

  • OxiWire HELLO handshake — new cmd: "hello" returns server version, supported wire versions, stable-surface feature set, experimental feature set, and auth methods. Pre-auth, idempotent, backward-compatible (clients without HELLO default to wire v1). See oxidb-server/src/hello.rs and ADR-0003 Phase 2.
  • REST /v1/ URL prefixGET /v1/hello returns server info; /v1/api/... is the 1.0 stable surface entry point. Legacy bare /api/... still routes during the deprecation window.
  • WebSocket subprotocol versioning — server advertises oxidb.v1 via Sec-WebSocket-Protocol. Clients without the header still connect.
  • oxidb migrate CLI — new subcommand on oxidb-cli: migrate inspect --data <PATH> walks a data directory and reports each file's on-disk format version (OXWA / OXTX / OXBT / OXIX / blob format_version). migrate run validates versions and is the scaffold for future v2 migrations (ADR-0003 Phase 4).

Performance

  • Bytes-first find path for OxiWire responses — new jsonb_oxiwire module converts JSONB to OxiWire bytes via a custom serde Visitor, skipping the serde_json::Value tree intermediate (~20 µs/doc saving on cache miss). A new doc_bytes_cache (env-tunable via OXIDB_DOC_BYTES_CACHE_SIZE, default 1M) keeps pre-encoded bytes around.
  • Composite-index fast pathfind queries that are exactly covered by a composite index's fields now route through find_prefix directly, skipping post-filter and Value materialisation.
  • Partial-JSONB filter — new helper evaluates top-level $eq/$ne/$gt/$gte/$lt/$lte/$in conditions plus $and / $or / dot-paths directly against JSONB bytes using codec::extract_field. Wired into the aggregation pipeline's $match step AND the find full-scan rayon path; reserves the full JSONB→Value decode for queries with predicates the partial matcher can't evaluate.
  • Doc cache capacity is env-tunableOXIDB_DOC_CACHE_SIZE overrides the 100K default. Production hardware with more RAM can hold the full working set.

Benchmark

  • OxiDB sweeps MongoDB at 1M docs. The full tests/comparison-mongodb bench at 1M-document scale (in-network Docker harness, no port-forward artifact) goes OxiDB 24 – MongoDB 0 across 24 measured workloads. Largest wins: count-all 2189×, Top-5 cities aggregation 1262×, composite-indexed compound 4.1×. Smallest wins: bulk insert 1.1×, range-10K-rows-each 1.2×. Resource footprint at peak: OxiDB 1.71 GiB RSS / 741 MB disk vs MongoDB 1.00 GiB / 626 MB.

.NET clients (developer-friendly rework)

  • 4 NuGet packages published at v0.28.18OxiDb.Client.Tcp, OxiDb.Client.Embedded, OxiDb.EntityFrameworkCore, and NEW: OxiDb.Linq (LINQ provider, previously source-only).
  • Exception hierarchyOxiDbException base + OxiDbDuplicateKeyException, OxiDbTransactionConflictException, OxiDbAuthenticationException, OxiDbNotFoundException, OxiDbImmutableException (WORM), OxiDbConnectionException, OxiDbProtocolException. Server error strings routed to the right subclass via FromServerMessage. Legacy OxiDbTcpException retained as [Obsolete] alias.
  • HelloAsync + HelloResponse record — wire-protocol handshake returning server version, supported wire versions, stable + experimental feature sets, auth methods.
  • Typed CRUD overloadsFindAsync<T>, FindOneAsync<T>, InsertReturningIdAsync (returns long), InsertManyReturningIdsAsync (returns long[]). Eliminate the JsonElement→parse dance.
  • StreamAsync<T>IAsyncEnumerable<T> over paginated LIMIT/SKIP batches for million-row result sets.
  • DI integrationservices.AddOxiDbTcp(opts => opts.Host(…)) registers IOxiDbClient as a singleton.
  • Type-safe query builderQuery.Eq, Query.Gte, Query.In, Query.And, Query.Or, Query.Range … for runtime-constructed queries that don't fit LINQ.

1.0 prep docs

  • docs/SEMVER.md, docs/STABILITY.md, docs/DEPRECATION.md, docs/SECURITY.md — Phase 5 of ADR-0003. Translate the ADR-0004 release-policy decisions into operational docs (24-month LTS, additive-only minor releases, GitHub Security Advisories channel, etc.).
  • docs/PHASE3-SDK-FREEZE.md + Python client api/v1.json snapshot + CI gate script (template for the other 9 Tier-A clients).
  • docs/format/compat-matrix.md — Phase 2 cross-version compat matrix (OxiWire / REST / WebSocket).

v0.28.12

2026-05-24

Added

  • Audit log rotationRotationPolicy in oxidb-server/src/audit.rs supports size-based (OXIDB_AUDIT_MAX_BYTES), age-based (OXIDB_AUDIT_MAX_AGE_SECS), and calendar-aligned UTC rotation (OXIDB_AUDIT_CALENDAR=hourly|daily), with optional gzip compression of rotated files (OXIDB_AUDIT_COMPRESS=true). Wired into both standalone and cluster modes.
  • CERN-grade testing program — 9 cargo-fuzz targets (RESP, pg_wire, OxiWire, MsgPack, differential vs Redis & Postgres), OSS-Fuzz integration scaffolding, coverage reporting, ACID isolation-anomaly suite, HEP-shaped scale workload, encrypted-backup DR drill, upgrade-chain fixture corpus, and 39 authn/authz/SCRAM/canonicalisation/audit attack patterns — all rejected by the server.
  • Format version headers — OXTX for _tx_commit_log, OXWA for .wal, OXBT for .btree; explicit format_version in blob .meta JSON. Establishes the 1.0 on-disk-format contract.

Fixed

  • Unauthenticated DoS bugs found by fuzzing — RESP multi-byte UTF-8 line splitter panic, RESP CR-truncation + allocator-bomb, pg_wire message length unbounded allocation (now capped at 16 MiB), pg_wire i16-overflow + empty-body panic, OxiWire array/map pre-allocation now bounded by remaining bytes. Server versions < 0.28.3 vulnerable.

Changed

  • Julia client surfacefind / aggregate now return a Tables.jl-compatible row collection (DataFrames, CSV, MLJ, GLM accept it directly). SQL exports removed from Julia clients — OxiDB is a document database; Tables.jl covers the data-frame integration story.

v0.25.3

2026-04-25

Changed

  • Raft persistence: O(1) per mutation — rewrote oxidb-server/src/raft/log_store.rs to split state into a small raft_meta.json (vote / committed / sm_data) and an append-only raft_log.jsonl (one entry per line).
  • Append-only log writesappend_to_log is now a single line append per entry instead of rewriting the entire log; delete_conflict_logs_since and purge_logs_upto rewrite only on those rare events.
  • Transparent migration from the v0.25.2 single-file raft_state.json on first boot.
  • Unblocked 1M-record load tests under failover — 22.4 s end-to-end, 44,701 rec/s avg, zero records lost. The previous single-file snapshot stalled the cluster at ~52% complete due to 14 MB-per-mutation rewrites.

v0.25.2

2026-04-25

Added

  • Persistent Raft state for cluster modeOxiDbStore in oxidb-server/src/raft/log_store.rs was previously in-memory only; nodes that restarted came back as Learner term=0 and lost cluster membership, breaking failover scenarios.
  • New OxiDbStore::open(db, &data_dir) constructor — loads existing Raft state on startup; OxiDbStore::new(db) retained as in-memory variant for tests.
  • Atomic write-through on every mutation — save_vote, save_committed, append_to_log, delete_conflict_logs_since, purge_logs_upto, apply_to_state_machine, install_snapshot.
  • ShardReplicaRealWorldTest harness — 14-service docker-compose: 9 oxidb-server nodes (3 Raft groups), 3 per-shard oxipool master/replica routers, 1 top-tier shard-routing oxipool, Go API tier, one-shot cluster-init bootstrapper.
  • End-to-end test suites — Go smoke harness (5 assertions), Python integration tests (8 cases: CRUD + sharding + aggregation), Python failover scenarios (5: network partition, follower down, recovery catch-up, two followers down, leader down), parameterized load test with mid-stream failover (validated against 10K, 100K, 1M record loads).

v0.25.1

2026-04-18

Added

  • Eight new query operators$not, $nor, $all, $size, $type, $mod, $expr, $elemMatch.
  • $not field operator — negate any field condition; missing fields evaluate to true (MongoDB-compatible).
  • $nor top-level operator — match documents where none of the listed conditions are true.
  • $all array operator — array must contain all specified values.
  • $size operator — match arrays with an exact length.
  • $type operator — match by JSON type (string, number, bool, array, object, null, int).
  • $mod operator — modulo arithmetic on numeric fields ([divisor, remainder]).
  • $expr top-level operator — cross-field comparisons, e.g. {"$expr": {"$gt": ["$sold", "$stock"]}}.
  • $elemMatch operator — match array elements against sub-queries with AND semantics.
  • Go client additions — stored procedures (CreateProcedure, CallProcedure, ListProcedures...), CreateTTLIndex, retention policies, alerting methods, ExtractText, Backup/Restore, SetDialect.

Fixed

  • Array dot-notation in $set / $inc / $unsetvariants.0.stock no longer corrupts arrays.

Changed

  • Refactored matches_doc and matches_value into a shared eval_field_op helper.

v0.24.0

2026-04-10

Added

  • WebAssembly support -- New oxidb-wasm crate compiles OxiDB to wasm32 and runs entirely in the browser.
  • In-memory browser mode -- No server needed. JSON queries, SQL, and aggregation all work client-side in the browser.
  • wasm-bindgen API -- Full JavaScript API surface: init, insert, find, update, delete, count, sql, aggregate.
  • ~1.5 MB gzipped WASM binary -- Compact binary size suitable for production web applications.
  • TypeScript types included -- Full type definitions shipped with the WASM package for editor autocompletion and type safety.
  • Cross-platform lock shim (src/locks.rs) -- Uses parking_lot on native targets, spin locks on wasm32.

Changed

  • Native-only dependencies made target-specific -- rayon, memmap2, zstd, and other native-only crates moved to target-specific dependencies to enable WASM compilation.
  • #[cfg(not(target_arch = "wasm32"))] guards throughout core engine -- Platform-incompatible code paths conditionally compiled out for the WASM target.

v0.18.0

2026-03-05

Added

  • OxiWire binary protocol -- Custom wire format with 1-byte type tags, 4-byte LE lengths, 8-byte LE numbers. Magic byte 0xDB. Replaces MsgPack for all request/response paths. Encoder + decoder in Rust and Go
  • .NET EF Core provider -- Full Entity Framework Core support with LINQ queries, transactions, and both TCP and embedded modes. d7d5a05
  • .NET NuGet packages -- OxiDb.Client.Tcp, OxiDb.Client.Embedded, OxiDb.EntityFrameworkCore. d7d5a05
  • Composite index tests -- 9 subtests covering exact match, prefix match, count, sort, update, delete, aggregate, drop, and triple-field composite indexes in Go.
  • Parallel OxiWire serialization -- Result sets >= 5,000 docs are serialized across up to 8 CPU cores. Chunk-based, zero per-doc allocation.
  • OxiDB vs MongoDB benchmark suite -- 22 tests across 7 categories. Score: OxiDB 19 -- MongoDB 1.
  • OxiDB vs PostgreSQL benchmark suite -- 20 tests comparing document workloads. Score: OxiDB 10 -- PostgreSQL 10. d7d5a05
  • OxiDB vs SQLite benchmark -- 100K document embedded benchmark. ce8db5f

Changed

  • Aggregation indexed-path threshold -- Changed from 10% to 50% selectivity. Indexed aggregation path now preferred when candidate set is less than 50% of collection size.
  • Go client rewritten for OxiWire -- All requests/responses use OxiWire binary format. MsgPack dependency removed entirely.
  • Pipeline handler updated -- Sub-responses decoded from OxiWire and re-encoded for composite pipeline responses.

Removed

  • MsgPack support -- Removed from server (Rust), Go client, and all benchmark tests. OxiWire is the sole binary protocol.
  • github.com/vmihailenco/msgpack/v5 -- Removed from Go module dependencies.

v0.17.0

2026-02-23

Added

  • LRU document cache -- Per-collection in-memory cache with configurable capacity. JSON deserialized once, then Arc-refcounted. 0997d8e
  • Streaming scan for non-indexed finds -- Avoids loading all documents into memory for large unindexed queries. 39cd803
  • Lock-free pread -- Separate read-only file handle uses pread for concurrent reads without locking the write path. 63093a3
  • Sorted-offset batch reads -- Indexed finds sort offsets before reading to minimize disk seeks. 63093a3
  • Zero-decode aggregation -- Extract only needed fields from raw JSONB, skip full document deserialization. 4a6f696
  • Batch pread for indexed $match aggregations -- Combine pread with zero-decode for indexed aggregation paths. 4b610ea
  • Zero-decode index creation -- Extract only _id and the indexed field from raw JSONB during index build. beb3f49
  • DocIdSet optimization -- Inline storage for single-document index entries saves ~80 bytes per entry. 4897f86
  • Zero-decode filter for unindexed scans -- JSONB keypath extraction avoids full JSON parse on scan. 7b9c639
  • Parallel segmented scan -- Large unindexed queries split across CPU cores for parallel processing. b339e5a
  • Index-only count for aggregations -- $group with $sum: 1 on indexed fields returns set size without touching documents. b339e5a

Changed

  • Memory consumption reduced -- Skip bulk cache during insert, drop unused Value clones, use DocIdSet instead of BTreeSet for single-entry indexes. 4897f86
  • Streaming I/O throughout -- Replaced collect-then-process patterns with streaming iterators for finds, aggregations, and index creation.

v0.16.0

2026-02-23

Added

  • Core document database engine -- Append-only storage, WAL with CRC32 checksums, per-collection locking.
  • JSON query language -- $eq, $ne, $gt, $gte, $lt, $lte, $in, $exists, $regex, $and, $or. Dot notation for nested fields.
  • Update operators -- $set, $unset, $inc, $mul, $min, $max, $rename, $currentDate, $push, $pull, $addToSet, $pop.
  • Aggregation pipeline -- $match, $group, $sort, $project, $limit, $skip, $unwind, $addFields, $lookup, $count. Accumulators: $sum, $avg, $min, $max, $count, $first, $last, $push.
  • Single-field, unique, and composite indexes -- BTreeMap-backed with index-only count and index-backed sort.
  • Full-text search -- TF-IDF ranking with HTML, XML, JSON, PDF, DOCX, XLSX, and OCR support.
  • Vector search -- HNSW index with cosine, euclidean, and dot product distance metrics.
  • ACID transactions -- OCC with 3-phase commit, per-document versioning, deadlock-free sorted locking.
  • SQL support -- SELECT, INSERT, UPDATE, DELETE, CREATE/DROP INDEX, CREATE/DROP TABLE, JOINs, GROUP BY, aggregate functions.
  • Blob storage -- S3-style bucket/object API with metadata, ETags, content types.
  • Encryption at rest -- AES-256-GCM with random 12-byte nonce per document.
  • Zstd compression -- Level 3, transparent per-document, thread-local context reuse.
  • Change streams -- Watch collections for insert/update/delete events. Resumable with 4096-event replay buffer.
  • Stored procedures -- Named multi-step operations with parameter substitution.
  • Scheduled tasks -- Background job scheduling with enable/disable control.
  • Multi-database support -- Isolated databases within a single server instance.
  • Backup & restore -- Compressed full backups with all data, indexes, and metadata.
  • TCP server -- Length-prefixed JSON over TCP (max 16 MiB). Tokio-based async runtime.
  • SCRAM-SHA-256 authentication -- Salted challenge-response, no plaintext passwords on wire.
  • RBAC -- Admin, ReadWrite, Read roles with per-command authorization.
  • TLS/SSL -- Certificate-based encryption for all traffic.
  • Audit logging -- GELF format for centralized logging.
  • Raft clustering -- Multi-node replication via openraft (optional cluster feature flag).
  • Client libraries -- Python, Go, Julia, .NET (TCP + Embedded), Swift (C FFI).
  • C FFI -- oxidb-client-ffi (cdylib) and oxidb-embedded-ffi (staticlib + cdylib) for language bindings.
Report Issue