All notable changes to OxiDB, organized by version.
oxidb-rec)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.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.db.rec_related({...}), db.rec_track, and, crossing into the document engine, db.vector_search: one JSON boundary with the same validation as the wire..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.OXIDB_FTS_SCORE_CAP): 7.3 MB flat where a common term used to cost 25–51 MB per query, multiplied by concurrency.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.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.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.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.$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.OXIDB_CHANGE_REPLAY_EVENTS (default 4096).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.$geoNear$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.$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..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.<, <=, >, >= — 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.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.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.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.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.$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.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.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.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.oxidb-mcp)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.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.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.$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.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.$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.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.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.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.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.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.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/.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.docs/query-benchmark.md.OXIDB_DOC=0 runs the server without the document engine entirely — 9.8 MB idle RSS for a SQL/TSDB-only deployment.CONSTRAINT pk PRIMARY KEY (a, b)), enforced on every write path including transactions.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.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.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.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+).{"cmd":"auth","token":…,"db":"<ref>"} verifies against that project's ES256 key and pins the connection to that database.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./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.ALTER TABLE … ALTER COLUMN … TYPEALTER 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.<host>/<project>/rest/v1/…) so no wildcard cert is needed, and a static dashboard. Developer sign-in is Google-only.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”.OXIDB_DOC_CACHE_SIZE / OXIDB_DOC_BYTES_CACHE_SIZE.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.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)..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.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.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).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.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.OXIDB_WAL_CHECKPOINT_BYTES (default 64 MiB, 0 restores the old behaviour); crash-tested against SIGKILL mid-checkpoint.OxiDb.Client.Tcp (.NET) — TsdbWriteAsync, TsdbWriteLineProtocolAsync and TsdbQueryAsync with typed points, aggregations (TsdbAgg.Mean … Percentile(p)) and epoch-ms helpers, replacing hand-rolled raw commands.aws-cli, boto3, MinIO SDKs and the AWS .NET SDK now all verify uploads cleanly, with no workaround flags.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.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.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.any_compound 0.79x→1.21x, string_multi 0.67x→2.07x.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.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.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.Value, and an OxiWire binary wire format.WITH / 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.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.std::fs in the /proc stats readers.sequences.json so a NEXT VALUE FOR can never desync a generation's catalog from its snapshots.+ (one level) and # (subtree) filters, backed by the OxiMem pattern-subscriber layer.OXIDB_MQTT_USER/OXIDB_MQTT_PASSWORD require matching CONNECT credentials.aws s3 ls pages correctly over large buckets.?lifecycle Days rules per bucket with a background sweeper.cargo test: signed roundtrip, corrupted-signature 403, multipart assembly, batch delete.redis.call, cjson, redis.sha1hex; atomic, busy-script time limit, SCRIPT KILL.expired events.ZREVRANGE key 0 0 returned the lowest member; ranks now index the descending view.find_for_update pessimistic document locks: contenders queue instead of conflict-storming.$ohlcv (tick→candle), range/time window frames, $densify, $fill.GET /metrics on the REST listener; zero dependencies.OXIDB_SLOW_QUERY_MS capture.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.
/v1/ URL prefix — GET /v1/hello returns server info; /v1/api/... is the 1.0 stable surface entry point. Legacy bare /api/... still routes during the deprecation window.
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).
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.
find queries that are exactly covered by a composite index's fields now route through find_prefix directly, skipping post-filter and Value materialisation.
$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.
OXIDB_DOC_CACHE_SIZE overrides the 100K default. Production hardware with more RAM can hold the full working set.
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.
OxiDb.Client.Tcp, OxiDb.Client.Embedded, OxiDb.EntityFrameworkCore, and NEW: OxiDb.Linq (LINQ provider, previously source-only).
OxiDbException 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.
FindAsync<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.
services.AddOxiDbTcp(opts => opts.Host(…)) registers IOxiDbClient as a singleton.
Query.Eq, Query.Gte, Query.In, Query.And, Query.Or, Query.Range … for runtime-constructed queries that don't fit LINQ.
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).
RotationPolicy 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.
_tx_commit_log, OXWA for .wal, OXBT for .btree; explicit format_version in blob .meta JSON. Establishes the 1.0 on-disk-format contract.
find / 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.
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_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.
raft_state.json on first boot.
OxiDbStore 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.
OxiDbStore::open(db, &data_dir) constructor — loads existing Raft state on startup; OxiDbStore::new(db) retained as in-memory variant for tests.
save_vote, save_committed, append_to_log, delete_conflict_logs_since, purge_logs_upto, apply_to_state_machine, install_snapshot.
$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.
CreateProcedure, CallProcedure, ListProcedures...), CreateTTLIndex, retention policies, alerting methods, ExtractText, Backup/Restore, SetDialect.
$set / $inc / $unset — variants.0.stock no longer corrupts arrays.
matches_doc and matches_value into a shared eval_field_op helper.
oxidb-wasm crate compiles OxiDB to wasm32 and runs entirely in the browser.
init, insert, find, update, delete, count, sql, aggregate.
src/locks.rs) -- Uses parking_lot on native targets, spin locks on wasm32.
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.
0xDB. Replaces MsgPack for all request/response paths.
Encoder + decoder in Rust and Go
OxiDb.Client.Tcp, OxiDb.Client.Embedded, OxiDb.EntityFrameworkCore.
d7d5a05
github.com/vmihailenco/msgpack/v5 -- Removed from Go module dependencies.
Arc-refcounted.
0997d8e
pread for concurrent reads without locking the write path.
63093a3
_id and the indexed field from raw JSONB during index build.
beb3f49
$group with $sum: 1 on indexed fields returns set size without touching documents.
b339e5a
Value clones, use DocIdSet instead of BTreeSet for single-entry indexes.
4897f86
cluster feature flag).oxidb-client-ffi (cdylib) and oxidb-embedded-ffi (staticlib + cdylib) for language bindings.