Skip to content

shard-db 2026.07.3

Coverity hardening (46 true-positive fixes across two triage rounds), aggregate HAVING clause OR/nested-tree support on both the JSON and NQL paths, an NQL --join flag, and a btree-cache concurrency improvement on the cold-miss path. No on-disk format changes — wire-compatible with 2026.07.2, no ./migrate required.

Highlights

  • Coverity hardening — 82 findings from a fresh scan triaged; 46 true positives fixed across 9 PRs (stack overflows, disk-corruption fail-safes, null derefs, resource leaks, data races, dead code, build warnings). Fail-safe behavior only — no functional or format changes.
  • Aggregate HAVING OR/nested-tree supporthaving_json (JSON protocol) previously flattened to AND-only, silently dropping OR/nested conditions that NQL already supported. Both paths now share one tree-aware matcher. Also fixes a bug where a plain count() aggregate with a having clause and no group_by bypassed having filtering entirely.
  • NQL --join flag — join support on find via NQL, built directly as JoinSpec C structs (no JSON intermediate). Fixes two double-free bugs and a missing as default uncovered during implementation.
  • Performance — btree-cache miss path no longer serializes cold-start syscalls (open/mmap/munmap/close) behind one global mutex; widens parallel fan-out for cold indexed queries across many shards.
  • npm binding fix + rebuild — the Node.js native addon was missing the seven query_*.c translation units created by 2026.07.2's query.c split, silently building an incomplete binding. Fixed in binding.gyp (npm 1.0.9). The binding also compiles every source file this release touches (query.c, query_aggregate.c, btree.c, nql.c, bitmap.c, index.c, io_direct.c, slotcask.c, config.c, server.c), so npm is bumped again to 1.0.10 to ship this release's Coverity fixes, HAVING fix, join support, and btree-cache perf improvement to Node.js consumers.

Coverity hardening

A fresh Coverity scan (82 findings, all New/Unassigned/Unclassified) was triaged by module cluster; verdicts and full CID mapping live in docs/coverity-triage-2026-07.md. 46 true positives were fixed across 9 PRs; the remainder were classified false-positive (established idioms Coverity's flow analysis loses track of, e.g. atomics, double-checked locking) or accepted risk (documented, low-severity, not worth the complexity to fix — e.g. a startup-only TOCTOU guarded by the single-instance flock).

PR Category CIDs fixed
#219 Stack-buffer overflows — encode_criterion_value, group_by CSV conversion, join buffer offset accumulation 4
#220 Disk corruption — malformed/bitrot on-disk data (btree page chains, klen/vlen fields, varchar length prefixes, bitmap dictionary walk, uninitialized-struct-on-goto-fail) now fails safe instead of OOB read/loop/corrupt 12
#221 Null-deref — unchecked slotcask_registry_get() in keyset_count_from_or / keyset_emit_find 3
#222 Build warnings (signed/unsigned comparison, dead helper) — no CIDs, flagged during #221 review 0
#223 Resource leaks — NqlCommand on NQL parse failure, closedir on warmup OOM, cmd_get_multi OOM path, bulk-insert wire_keys on timeout 4
#224 Data race — recover_one_stream wrote active_file_id/reserve_off without rotation_lock 2
#225 Dead code — 5 unreachable v1 function bodies + a duplicated post-return block + 2 dead stores, left over from the v1→v2 slotcask migration 8
#226 Misc hardening — unlogged accept() errno, OOB clamp in decode_field_to_buf, 2 unchecked ftruncate(), unbounded memcpy in cmd_add_indexes 5
#229 Round-2 rescan — stack overflow in selective_reindex_dirty, plus 7 more null-deref/leak/hardening findings surfaced after round-1 fixes 8

New regression tests were added alongside the disk-corruption and stack-overflow fixes (corrupting real on-disk bytes against a live daemon and asserting survival).

Aggregate HAVING clause: OR and nested trees

having_json on the JSON protocol previously flattened having clauses to an AND-only array, silently dropping {"or":[...]} or nested AND/OR conditions that the NQL path already supported via CriteriaNode trees. Both entry points now build a having_tree and route through a single tree-aware matcher (agg_having_match_tree).

This also fixes two pre-existing bugs:

  • The no_having gate used by 8+ aggregate fast-path shortcuts didn't account for tree-based having clauses.
  • The no-group-by count-only metadata shortcut never checked no_having at all — a plain count() aggregate with a having clause and no group_by silently returned an unfiltered count.

Malformed having JSON now returns {"error":"bad having: ..."} instead of silently degrading to no filtering.

./shard-db query '{"mode":"aggregate","dir":"default","object":"orders","aggregates":[{"op":"count","as":"n"}],"group_by":["status"],"having":{"or":[{"field":"n","op":"gt","value":10},{"field":"n","op":"lt","value":2}]}}'

NQL: --join flag

find queries in NQL now accept --join, building JoinSpec structs directly from parsed tokens instead of routing through a JSON intermediate — matching how cmd_find_do already handles JSON joins.

find orders --join customers local=customer_id as cust fields name,email
find orders --join customers local=customer_id left

as is optional (defaults to the remote object name), matching JSON join behavior. Uncovered and fixed during implementation:

  • Double-free in cmd_find_tree (cmd_find_do already frees joins internally)
  • Double-free in dispatch_nql_query (cmd_find_do frees, then nql_free_command tried to free the same joins again)

NQL's docs previously understated its own coverage — --cursor and --having were already fully supported (same as JSON) but undocumented or listed under "Limitations." The NQL doc's "Limitations" section is renamed "Scope" and corrected: NQL now covers find, count, aggregate, and joins (via --join on find); only bulk operations, CAS, schema mutations, and file storage remain JSON-only.

Performance: btree-cache miss path

A cold-cache indexed query fans out across up to 128 index shards in parallel. Every worker thread previously funneled through one global mutex to do real syscalls (open/mmap/munmap/close) one at a time — serializing exactly the case that should be parallel (cold cache + high shard fan-out).

bt_cache_drop_slot is split into bt_cache_evict_slot (under-lock detach, no syscalls) and bt_dispose_mapping (post-unlock teardown); both eviction paths now dispose the victim's fd/mmap after releasing bt_cache_lock instead of while holding it. Readers do open+mmap outside the lock, re-probe on reacquire, and serve their mapping uncached if another thread won the install race in the meantime. Writer-side fresh-file creation is unchanged.

Verified race-free under ThreadSanitizer on the affected index/btree/cursor test subset.

npm binding fix + rebuild

2026.07.2's query.c split (into query_aggregate.c, query_join.c, query_plan.c, query_find.c, query_bulk.c, query_maint.c, query_schema.c) was not reflected in the npm native addon's binding.gyp, which still only listed the original query.c. This silently built an incomplete binding missing most query functionality. Fixed by adding the seven new translation units to npm/binding.gyp (npm 1.0.9).

That fix alone doesn't ship this release's actual bug fixes, though — binding.gyp compiles the same C sources this release changes (query.c, query_aggregate.c for the HAVING fix, btree.c for the cache perf fix, nql.c for --join, plus bitmap.c, index.c, io_direct.c, slotcask.c, config.c, server.c for the Coverity hardening). npm package shard-db bumped 1.0.9 → 1.0.10 so Node.js consumers get a rebuilt binding with all of this release's fixes.

Upgrade notes

No storage format changes in this release. No ./migrate step required.

./shard-db stop
# copy new build/bin/ artifacts
./shard-db start

Wire-compatible with 2026.07.2 — no client changes needed.