Last Updated: Phase 8.1 COMPLETE - WebAssembly (browser + WASI), 795 tests ✅
Total Tests: 795 ✅ (788 passing, 7 ignored)
- ✅ 530 unit tests (lib, +6 from browser/mod.rs wasm-bindgen-test suite)
- ✅ 24 bi-temporal tests (integration)
- ✅ 10 complex query tests (integration)
- ✅ 8 recursive rules tests (integration)
- ✅ 10 concurrency tests (integration)
- ✅ 7 WAL / crash recovery tests (integration)
- ✅ 16 index tests (integration, Phase 6.1)
- ✅ 4 performance / packed page tests (integration, Phase 6.2)
- ✅ 8 retraction tests (integration, Phase 6.4a)
- ✅ 6 edge case tests (integration, Phase 6.4a)
- ✅ 10 B+tree v6 tests (integration, Phase 6.5)
- ✅ 14 negation (
not) tests (integration, Phase 7.1a) - ✅ 7 not-join tests (integration, Phase 7.1b)
- ✅ 28 aggregation tests (integration, Phase 7.2a)
- ✅ 8 predicate expression tests (integration, Phase 7.2b)
- ✅ 9 disjunction tests (integration, Phase 7.3)
- ✅ 7 production pattern tests (integration, Phase 7.5 — cross-feature scenarios)
- ✅ 22 error handling tests (integration, Phase 7.5 — error-path coverage; 7 ignored: or+neg-cycle bug)
- ✅ 9 temporal metadata tests (integration, Phase 7.6 —
:db/valid-from,:db/valid-to,:db/tx-count,:db/tx-id,:db/valid-at) - ✅ 12 window function tests (integration, Phase 7.7a — cumulative sum/count/min/avg, rank with ties, row-number, partition-by, desc ordering, mixed aggregate+window, edge cases, lag/lead parse rejection)
- ✅ 14 UDF tests (integration, Phase 7.7b — custom aggregates, custom predicates, UDF as window function, name collision guards, runtime errors, thread safety)
- ✅ 17 prepared statement tests (integration, Phase 7.8 — entity/value/as-of/valid-at slots, combined temporal+entity, AnyValidTime, error paths, plan reuse)
- ✅ 23 doc tests (15 prior + 8 new from Phase 7.9 rustdoc sweep on public API items)
Status: ✅ All 788 tests passing (7 ignored: confirmed or+neg-cycle stratification bug)
Phase 8.1a Features (browser WASM, complete):
- ✅
BrowserDbpublic API:open_in_memory,execute,checkpoint,export_graph,import_graph - ✅
BrowserBufferBackend— in-memoryStorageBackendover a flat page buffer, byte-identical to native.graphformat - ✅
IndexedDbBackend— page-granular IndexedDB storage viaweb-sys+wasm-bindgen - ✅
wasm-packbuild generatingpkg/with JS glue and TypeScript.d.ts - ✅
wasm-bindgen-testsuite: 6 browser integration tests (Chrome + Firefox in CI)
Phase 8.1b Features (WASI, complete):
- ✅
FileBackendverified under WASI capability-based filesystem (no changes needed) - ✅
wasm32-wasip1CI workflow: build, unit tests (Wasmtime runner), smoke tests (Wasmtime + Wasmer) - ✅ Thread-dependent tests gated with
#[cfg(not(target_os = "wasi"))]
Cross-platform compatibility (issue #150, complete):
- ✅
tests/cross_platform_compat_test.rs: 2 native tests (raw page byte round-trip + fixture readability) - ✅
tests/fixtures/compat.graph: committed v7 binary fixture with known facts - ✅
native_fixture_readable_by_browser_dbwasm-bindgen-test: imports native fixture, verifies both facts - ✅ 795 tests passing (unit + integration + doc + wasm); version bumped to v0.20.0
Phase 7.9 Features (current, complete):
- ✅
Minigraf::repl(&self) -> Repl<'_>factory method —Replnow borrows&Minigraffor lifetime safety - ✅ All internal types narrowed to
pub(crate):FactStorage,PersistentFactStorage,FileHeader,StorageBackend,DatalogExecutor,PatternMatcher,Fact,TxId,VALID_TIME_FOREVER,Wal, etc. - ✅ Full rustdoc on all public API items with
# Examplesdoctests; 8 new doctests added - ✅
[package.metadata.docs.rs]inCargo.toml— docs.rs builds withall-features = true - ✅
#![warn(missing_docs)]— enforces documentation coverage going forward - ✅ Bare
.unwrap()in library code replaced with.expect("lock poisoned")/.expect("WAL not initialized") - ✅
cargo clippy -- -D warningsclean - ✅ macOS and Windows added to CI test matrix (
rust.yml) - ✅ crates.io and docs.rs badges + Installation section in
README.md - ✅ 788 tests passing (unit + integration + doc); version bumped to v0.19.0
Phase 7.8 Features (complete):
- ✅
EdnValue::BindSlot(String),AsOf::Slot(String),ValidAt::Slot(String),Expr::Slot(String)AST variants intypes.rs - ✅
BindValueenum insrc/query/datalog/prepared.rs:Entity(Uuid),Val(Value),TxCount(u64),Timestamp(i64),AnyValidTime - ✅
PreparedQuerystruct — stores parsed AST + optimised plan +Archandles to fact store and registries; re-executes against live fact store state - ✅
prepare_query()(pub(crate)) — parse, validate, compute query plan once - ✅
PreparedQuery::execute(bindings)— deep-clone + AST walk substitution; type-checked per bind position; executor, optimizer, matcher unchanged - ✅ Panic guards (no slot-name interpolation) in
executor.rs(4ValidAt::Slotsites, 1Expr::Slotsite) andstorage.rs(AsOf::Slot) - ✅
Minigraf::prepare(query_str) -> Result<PreparedQuery>on public API (db.rs) - ✅
BindValueandPreparedQueryre-exported fromlib.rs - ✅
tests/prepared_statements_test.rs— 17 integration tests - ✅ 780 tests passing (unit + integration + doc)
Phase 7.7b Features (current, complete):
- ✅
UdfOpsandPredicateDesctypes insrc/query/datalog/functions.rs— register custom aggregates (init/step/finalise closures) and custom predicates (filter closure) - ✅
FunctionRegistry::register_aggregateandregister_predicatemethods; collision guards reject re-registration of built-in names or duplicate UDFs - ✅
FindSpec::UdfandWhereClause::UdfPredicatevariants intypes.rs; UDF aggregates usable in:findand:overwindow specs; UDF predicates usable in:where - ✅ Parser extended: UDF aggregate invocations in
:find/:over; UDF predicate invocations in:where; unknown function names deferred to runtime, not rejected at parse time - ✅ Executor routes UDF aggregates through
FunctionRegistryat query time; UDF predicates evaluated per binding row - ✅
Minigraf::register_aggregateandregister_predicateon the public API (db.rs) - ✅
tests/udf_test.rs— 14 integration tests - ✅ 753 tests passing (unit + integration + doc)
Phase 7.7a Features (current, complete):
- ✅
FunctionRegistryinsrc/query/datalog/functions.rs— string-keyed registry; built-in aggregates (sum,count,min,max,avg,count-distinct,sum-distinct) migrated into it;window_ops(init/step/finalise) on window-compatible entries;is_builtinflag - ✅
WindowFunc,Order,WindowSpec,FindSpec::Windowtypes intypes.rs;AggFuncenum removed;FindSpec::Aggregate.funcchanged toString - ✅
parse_window_exprinparser.rs—(func ?v :over (:partition-by ?p :order-by ?o :desc))syntax;lag/leadrejected; unknown function → parse error; non-window-compatible in:over→ parse error - ✅
apply_post_processing,compute_aggregation,apply_window_functions,project_find_specsinexecutor.rs— replacesapply_aggregation/apply_agg_func - ✅
FunctionRegistrywired throughdb.rs(Minigraf::InnergainsArc<RwLock<FunctionRegistry>>) - ✅
tests/window_functions_test.rs— 12 integration tests (cumulative sum, running count/min/avg, rank with ties, row-number, partition-by, desc ordering, mixed aggregate+window, single-row and empty-result edge cases, lag/lead parse rejection) - ✅ 746 tests passing (unit + integration + doc)
Phase 7.6 Features (current, complete):
- ✅
PseudoAttrenum andAttributeSpecwrapper type intypes.rs - ✅
parse_query_patterninparser.rs— detects:db/*keywords in attribute position; rejects in entity/value positions - ✅
PatternMatcher::from_slice_with_valid_atconstructor — passes query-levelvalid_atinto the matcher - ✅ Hard-error guard in executor: per-fact pseudo-attrs require
:any-valid-time - ✅
:db/valid-atbinds the effective query timestamp;:any-valid-timeaccepted as standalone keyword - ✅
tests/temporal_metadata_test.rs— 16 integration tests (time-interval range queries, time-point lookups, tx-time correlation,:db/valid-atsemantics, parse/runtime error guards) - ✅ 647 tests passing (438 unit + 209 integration)
Phase 7.5 Features (complete):
- ✅
cargo-llvm-covbranch coverage tooling documented inCONTRIBUTING.md - ✅ Baseline branch coverage recorded; executor.rs ~86.61%, evaluator.rs ~89.29% (up from ~75% / ~73%)
- ✅
tests/production_patterns_test.rs— 8 cross-feature integration tests - ✅
tests/error_handling_test.rs— 8 error-path integration tests (1 ignored: confirmed or+neg-cycle stratification bug) - ✅ Stream 3 unit tests: ~53 new tests for previously uncovered branches in executor.rs and evaluator.rs
- ✅ 617 tests passing (424 unit + 187 integration + 6 doc)
Phase 7.4 Features (current, complete):
- ✅
filter_facts_for_queryreturnsArc<[Fact]>— eliminates O(N) four-BTreeMap index rebuild on every non-rules query call - ✅
execute_querypath constructs zeroFactStorageobjects;execute_query_with_rulesstill converts forStratifiedEvaluator - ✅
PatternMatcher::from_slice(Arc<[Fact]>)constructor added - ✅
apply_or_clausesandevaluate_not_joinsignatures updated to acceptArc<[Fact]> - ✅ Evaluator loop:
accumulated_factscomputed once per iteration (was 4 separateget_asserted_facts()calls) - ✅ ~62–65% speedup on non-rules queries at 10K facts (
query/point_entity/10k: 22 ms → 8.6 ms;aggregation/count_scale/10k: 28 ms → 9.7 ms) - ✅ 4 new unit tests in
matcher.rs, 2 new unit tests inexecutor.rs(6 total) - ✅ Version bumped to v0.13.1
Phase 7.3 Features (current, complete):
- ✅
WhereClause::Or(Vec<Vec<WhereClause>>)andWhereClause::OrJoin { join_vars, branches }variants intypes.rs - ✅
(or branch1 branch2 ...)and(or-join [?v...] branch1 branch2 ...)in:whereclauses and rule bodies - ✅
(and ...)grouping clause to collect multiple clauses into a single branch - ✅
match_patterns_seededonPatternMatcher;evaluate_branchandapply_or_clauseshelpers inexecutor.rs - ✅
DependencyGraph::from_rulesrefactored with recursivecollect_clause_deps;Or/OrJoinbranches contribute positive dependency edges - ✅ Rules with
or/or-joinin bodies routed tomixed_rulespath inStratifiedEvaluator - ✅
tests/disjunction_test.rs: 16 integration tests (Phase 7.3) - ✅ Version bumped to v0.13.0
Core Features Implemented (Phase 6.2):
- ✅ Packed fact pages (
page_type = 0x02): ~25 facts per 4KB page (~25× space reduction) - ✅ LRU page cache (
cache.rs): approximate-LRU, read-lock on hits,Arc<Vec<u8>>entries - ✅
CommittedFactReadertrait +CommittedFactLoaderImpl: on-demand fact resolution - ✅ Pending
FactRef(page_id = 0): resolves to in-memory pending facts vec - ✅
FileHeaderv5:fact_page_formatbyte (0x02 = packed); auto v4→v5 migration on open - ✅
OpenOptions::page_cache_size(usize)builder method (default 256) - ✅ EAVT/AEVT range scans in
get_facts_by_entity/get_facts_by_attribute
Phase 6.1 Features (also complete):
- ✅ EAVT, AEVT, AVET, VAET covering indexes with bi-temporal keys
- ✅
FactRef { page_id, slot_index }: forward-compatible disk location pointer - ✅ Canonical value encoding (
encode_value) for sort-order-preserving comparisons - ✅ B+tree page serialisation for index persistence (
btree.rs) - ✅
FileHeaderv4:eavt/aevt/avet/vaet_root_page+index_checksum(CRC32) - ✅ Auto-rebuild on checksum mismatch
- ✅ Query optimizer:
IndexHint,select_index(), selectivity-basedplan()
Phase 5 Features (also complete):
- ✅ Fact-level sidecar WAL (
<db>.wal) with CRC32-protected binary entries - ✅ WAL-before-apply ordering: WAL fsynced before facts touch in-memory state
- ✅
FileHeaderv3 withlast_checkpointed_tx_count(replay deduplication) - ✅
WriteTransactionAPI (begin_write,commit,rollback) - ✅ Crash recovery: WAL replay on open, corrupt entries discarded at first bad CRC32
- ✅ Checkpoint: WAL flushed to
.graphfile, then WAL cleared - ✅ Thread-safe: concurrent readers + exclusive writer (Mutex + RwLock)
- ✅ File format v2→v3 migration on first checkpoint
Phase 4 Features (also complete):
- ✅ EAV data model with
tx_count,valid_from,valid_tofields - ✅
VALID_TIME_FOREVER = i64::MAXsentinel - ✅
FactStoragetemporal query methods (get_facts_as_of,get_facts_valid_at) - ✅ Parser: EDN maps,
:as-of,:valid-at, per-fact valid time overrides - ✅ Executor: 3-step temporal filter (tx-time → asserted → valid-time)
- ✅ File format v1→v2 migration
- ✅ UTC-only timestamp parsing (chrono, avoids GHSA-wcg3-cvx6-7396)
Phase 7.2b Features (also complete):
- ✅
BinOp(14 variants),UnaryOp(5 variants),ExprAST,WhereClause::Expr { expr, binding }intypes.rs - ✅ Filter predicates:
[(< ?age 30)],[(string? ?v)],[(starts-with? ?tag "work")],[(matches? ?email "...")] - ✅ Arithmetic bindings:
[(+ ?price ?tax) ?total], nested[(+ (* ?a 2) ?b) ?result], type-predicate binding[(integer? ?v) ?is-int] - ✅
parse_expr/parse_expr_clausewith parse-time regex validation;check_expr_safetyrecurses intonot/not-joinbodies - ✅ Dispatch at all 4 clause sites;
outer_vars_from_clauseupdated for binding variable scope - ✅
eval_expr/eval_binop/is_truthy/apply_expr_clausesinexecutor.rs;apply_expr_clauses_in_evaluatorinevaluator.rs - ✅
tests/predicate_expr_test.rs: 28 integration tests (Phase 7.2b) - ✅ Version bumped to v0.12.0
Phase 7.2a Features (also complete):
- ✅
count,count-distinct,sum,sum-distinct,min,maxaggregate functions in:findclause - ✅
:withgrouping clause — variables that participate in grouping but are excluded from output rows - ✅
AggFuncenum,FindSpecenum;DatalogQuery.findmigrated fromVec<String>toVec<FindSpec> - ✅
apply_aggregationpost-processing inexecutor.rs; parse-time validation - ✅
tests/aggregation_test.rs: 24 integration tests (Phase 7.2a) - ✅ Version bumped to v0.11.0
Phase 7.1 Features (also complete):
- ✅
src/query/datalog/stratification.rs:DependencyGraph,stratify()— negative dependency edges + Bellman-Ford cycle detection; negative cycles rejected at rule registration time - ✅
WhereClause::Not(Vec<WhereClause>)andWhereClause::NotJoin { join_vars, clauses }variants; all match arms updated - ✅
(not clause…)— stratified negation; all body variables must be pre-bound by outer clauses - ✅
(not-join [?v…] clause…)— existentially-quantified negation; onlyjoin_varsare shared from outer scope; remaining body variables are fresh - ✅ Safety validation at parse time (unbound join vars → parse error; nesting constraint enforced)
- ✅
StratifiedEvaluator: stratifies rules, evaluates strata in order;not/not-joinfilters applied per binding in mixed-rule strata - ✅
evaluate_not_joinfree function: handles bothPatternandRuleInvocationbody clauses - ✅
tests/negation_test.rs: 10 integration tests (Phase 7.1a) - ✅
tests/not_join_test.rs: 14 integration tests (Phase 7.1b) - ✅ Version bumped to v0.10.0
Phase 6.5 Features (also complete):
- ✅
src/storage/btree_v6.rs: proper on-disk B+tree withbuild_btreebulk-load andrange_scanleaf-chain traversal - ✅
OnDiskIndexReader+CommittedIndexReadertrait: page-cache-backed index lookup; no full in-memory BTreeMap - ✅
MutexStorageBackend<B>: backend mutex held per page read; cache-warm pages require no lock - ✅ FileHeader v6 (80 bytes): adds
fact_page_count u64field at bytes 72–80; auto v5→v6 migration - ✅
tests/btree_v6_test.rs: 8 integration tests (B+tree insert/scan, multi-page, concurrent correctness, v5→v6 migration) - ✅
test_concurrent_range_scans_correctnessunit test: 8 barrier-synchronised threads, all return identical results - ✅ Version bumped to v0.9.0; BENCHMARKS.md updated with v6 results
Phase 6.4b Features (also complete):
- ✅ Criterion benchmark suite at 1K–1M facts; results documented in
BENCHMARKS.md - ✅ heaptrack memory profiles: 10K=14.4MB / 100K=136MB / 1M=1.33GB peak heap (v5 baseline)
- ✅ Byte-layout unit tests pin all FileHeader v5 field offsets (
src/storage/mod.rs) - ✅ Byte-layout unit tests pin packed page header + record directory offsets (
src/storage/packed_pages.rs) - ✅ Dead
clapdependency removed;Cargo.tomlmetadata complete; version bumped to v0.8.0 - ✅ README trimmed (794 → 166 lines); detail offloaded to GitHub wiki
Phase 6.4a Features (also complete):
- ✅ Retraction semantics fix:
net_asserted_facts()computes net view per EAV triple infilter_facts_for_query - ✅
check_fact_sizes()early validation indb.rs: rejects oversized facts before WAL write - ✅
MAX_FACT_BYTESconstant (packed_pages.rs): 4 080 bytes — maximum serialised size per fact - ✅ 7 new retraction integration tests (
tests/retraction_test.rs) - ✅ 4 new edge case integration tests (
tests/edge_cases_test.rs)
Phase 3 Features (also complete):
- ✅ Datalog parser (EDN syntax)
- ✅ Pattern matching with variable unification
- ✅ Query executor (transact, retract, query)
- ✅ Recursive rules with semi-naive evaluation
- ✅ Transitive closure queries
- ✅ Persistent storage (postcard serialization)
- ✅ REPL with multi-line and comment support
- ✅ Fact creation, equality, retraction, entity references
- ✅ Transaction ID generation and ordering
- ✅
VALID_TIME_FOREVERsentinel,with_valid_time(),TransactOptions - ✅ All
Valuetypes (String, Integer, Float, Boolean, Ref, Keyword, Null)
Coverage: ~95%
Core Operations:
- ✅ Transact, retract, batch transact
- ✅ Get facts by entity/attribute, history tracking
Phase 4 (Bi-temporal):
- ✅
tx_countincrements,get_facts_as_of(),get_facts_valid_at() - ✅
load_fact()preserves originaltx_id/tx_count
Phase 5 (WAL helpers):
- ✅
get_all_facts(),restore_tx_counter(),allocate_tx_count()
Phase 6.1-6.2 (Index + CommittedFactReader):
- ✅
set_committed_reader()wires CommittedFactReader - ✅
get_facts_by_entity()uses EAVT range scan - ✅
get_facts_by_attribute()uses AEVT range scan - ✅
FactRef { page_id: 0 }resolved to pending facts;page_id >= 1via CommittedFactReader - ✅ MockLoader in tests verifies committed path
Coverage: ~93%
- ✅ Empty WAL round-trip
- ✅ Single-fact and multi-fact entry round-trips
- ✅ Reopen-and-append
- ✅ Bad magic header rejected
- ✅ Truncated entry stops replay (partial write discard)
- ✅
delete_file()removes WAL
Coverage: ~97%
- ✅ In-memory transact and query round-trip
- ✅ Explicit
WriteTransactioncommit and rollback - ✅
build_query_view()read-your-own-writes within transaction - ✅ Reentrant
begin_write()returns error - ✅ File-backed open, transact, reopen (persistence)
- ✅ WAL written before in-memory apply
- ✅ Auto-checkpoint threshold fires,
checkpoint()manual trigger - ✅ Concurrent
execute()during activeWriteTransaction
Coverage: ~93%
- ✅
FactReffield access - ✅
encode_valuesort order: integers, cross-type, floats, NaN canonicalization - ✅ EAVT key ordering by entity
- ✅ AVET key ordering by value bytes
- ✅ VAET only populated for
Value::Ref - ✅
Indexes::insertpopulates all four indexes
Coverage: ~98%
- ✅ Empty EAVT roundtrip (exactly 1 page)
- ✅ Small EAVT roundtrip (10 entries)
- ✅ Large EAVT roundtrip (150 entries, multi-page)
- ✅ Sort order preserved after serialise/deserialise
Coverage: ~90%
- ✅ Cache miss loads from backend
- ✅ Cache hit returns same
Arcwithout backend read - ✅ LRU eviction evicts correct (oldest) page
- ✅
put_dirty/flushwrites back to backend - ✅
invalidateremoves entry - ✅
cached_page_countreports correctly
Coverage: ~92%
- ✅ Single fact pack/unpack roundtrip
- ✅ Multiple facts pack/unpack roundtrip
- ✅ Correct
FactRefslot assignments - ✅ Oversized fact returns
Err(not panic) - ✅
read_all_from_pageswith known page IDs - ✅ Wrong page type returns
Err - ✅ Byte-layout pin: page header (bytes 0–11) field positions verified (Phase 6.4b)
- ✅ Byte-layout pin: record directory entries at byte 12+ verified (Phase 6.4b)
Coverage: ~93%
- ✅ v6 serialisation: 80 bytes, correct field offsets
- ✅ v6 roundtrip with all index root pages, checksum, and
fact_page_count - ✅ v3/v4/v5 headers accepted with appropriate zero-filling
- ✅ v6 header with <80 bytes rejected
- ✅ Header validation (magic, version range 1-6)
- ✅ Version 0 and 7 rejected
- ✅
FORMAT_VERSION == 6 - ✅ Byte-layout pin: all 11 fields at exact offsets with LE encoding verified (Phase 6.4b / v6 update)
Coverage: ~98%
- ✅ All tokens, numbers, strings, booleans, UUIDs, nil
- ✅ Transact/Retract/Query/Rule commands
- ✅
:as-of(counter + ISO 8601 timestamp) - ✅
:valid-at(timestamp +:any-valid-time) - ✅ EDN map
{:key val}with transaction-level valid time - ✅ Per-fact valid time override (4-element fact vector)
- ✅ Reject negative
:as-ofcounter and invalid timestamps
Coverage: ~98%
- Types: ~95% (7 tests)
- Matcher: ~85% (6 tests)
- Executor: ~94% (18 tests) — including temporal filter and optimizer integration
- Rule Registry: ~95% (6 tests)
- Recursive Evaluator: ~95% (10 tests)
- ✅ FileBackend create/write/read, persistence across close/reopen
- ✅ MemoryBackend write/read, error handling
Coverage: ~85%
- ✅ UTC timestamp parsing and formatting
- ✅ Chrono CVE GHSA-wcg3-cvx6-7396 avoidance verified
Coverage: ~90%
- ✅ 3-pattern and 4-pattern joins, self-joins, entity reference joins
- ✅ No results, partial matches, variable reuse, multiple values, empty database
- ✅ Transitive closure, cycles, long chains, diamond patterns
- ✅ Ancestor/descendant, family trees, multiple recursive predicates
- ✅ Concurrent rule registration (5 threads), concurrent queries with rules (10 threads)
- ✅ Read-heavy workload (50 threads), recursive evaluation concurrency
- ✅ No deadlocks (20 threads mixed), RwLock consistency (10 writers + 10 readers)
- ✅ As-of counter and timestamp snapshots
- ✅ Valid-at inside/outside/boundary, default filter, any-valid-time
- ✅ Combined bi-temporal (both dimensions), multi-entity valid ranges
- ✅ Basic persistence (file-backed transact and query)
- ✅ WAL replay after
mem::forgetcrash simulation - ✅ Stale WAL dedup via
last_checkpointed_tx_count - ✅ Corrupt/partial entry discard on recovery
- ✅ Manual checkpoint clears WAL and updates header
- ✅ Auto-checkpoint fires at threshold
- ✅ Explicit transaction crash safety and rollback
- ✅ Multi-transact rollback leaves no trace
- ✅ Concurrent reads while writer holds exclusive lock
- ✅ Implicit
execute()WAL ordering verified - ✅ v2→v3 format migration on checkpoint
- ✅ EAVT/AEVT/AVET/VAET save and reload roundtrip
- ✅ Bi-temporal queries still correct after index save/reload
- ✅ Recursive rules regression (indexes don't break rule evaluation)
- ✅ Index checksum mismatch triggers rebuild
- ✅ v3→v4 format migration on first save
- ✅ 1K facts correct after packed save/reload
- ✅ Packed file size < one-per-page estimate (compactness check)
- ✅ Bitemporal query correct after packed reload
- ✅ As-of query correct after packed reload
- ✅ Recursive rules unchanged after Phase 6.2
- ✅ Explicit transaction survives packed reload
- ✅
page_cache_sizeoption accepted without panic
- ✅ Assert then retract; current-time query returns no results
- ✅ Assert at tx=1, retract at tx=3;
:as-of 2shows fact,:as-of 4hides it - ✅ Assert, retract, re-assert; current-time query returns fact
- ✅ Retraction +
:any-valid-timecombo - ✅ Recursive rule: retracted edge not traversed (
:as-ofafter retraction) - ✅ Recursive rule: retracted edge is visible in historical snapshot before retraction
- ✅ Multiple retractions for different entities in same transaction
- ✅ Oversized fact in file-backed database returns
Err, not panic - ✅ In-memory database accepts facts of any size (no size limit)
- ✅ Fact at exactly
MAX_FACT_BYTESis accepted - ✅ Fact at
MAX_FACT_BYTES + 1is rejected with clear error message
- ✅ Single-page B+tree insert and range scan correctness
- ✅ Multi-page B+tree (leaf chain traversal across multiple pages)
- ✅ Range scan with exclusive upper bound
- ✅ Empty range scan returns empty result
- ✅ Concurrent range scans — 8 barrier-synchronised threads all return identical results
- ✅ v5 database opens and migrates to v6 on first checkpoint
- ✅ v6 database survives close/reopen with correct fact count
- ✅ Index lookup via
OnDiskIndexReaderreturns correctFactRefs
- ✅ Basic
not— exclude entities where a pattern matches - ✅
notwith multi-clause body - ✅
notin a rule body (stratification + derived negation) - ✅
notwith:as-oftime travel - ✅
notwith:valid-at - ✅ Negative cycle via
notat rule registration →Err, rule not registered - ✅
notwhere no entities match the body — all outer bindings survive - ✅ Safety check: unbound variable in
notbody → parse error - ✅ Nested
notrejected at parse time - ✅
notwithRuleInvocationin body — derived rule facts correctly negated end-to-end
- ✅ Basic
not-join— exclude entities where existentially-quantified dependency exists - ✅ Multiple join variables in
not-join - ✅ Multi-clause body with a local variable linking inner patterns
- ✅
not-joinin a rule body - ✅ Multi-stage filtering chain (two independent
not-joinrules applied progressively) - ✅
not-joinvsnotsemantic difference (inner-only variable) - ✅
not-joinwith:as-oftime travel - ✅ Unbound join variable → parse error naming the variable
- ✅ Nested
not-joinrejected at parse time - ✅
RuleInvocationinnot-joinbody — derived facts correctly negated end-to-end - ✅ No-match survival — when no entity satisfies the body, all outer bindings survive
- ✅
not-joinwith:valid-at - ✅ Negative cycle via
not-joinat rule registration →Err, rule not registered - ✅
notandnot-joincoexist in the same query
- ✅ Cumulative sum over ordered partition
- ✅ Running count and running min
- ✅ Running average
- ✅ Rank with ties (equal values share rank)
- ✅ Row-number (unique sequential position regardless of ties)
- ✅ Partition-by — window resets per group
- ✅ Descending order in window spec
- ✅ Mixed aggregate + window in same
:find - ✅ Single-row result (window function on one row)
- ✅ Empty-result edge case (no matching facts)
- ✅
lag/leadrejected at parse time
- ✅
custom_aggregate_geometric_mean— UDF aggregate registered and used in:find - ✅
custom_aggregate_empty_result— UDF aggregate on empty result set returns correct identity - ✅
custom_predicate_filter— UDF predicate in:wherefilters binding rows - ✅
udf_as_window_function— UDF aggregate used inside:overwindow spec - ✅
name_collision_builtin_aggregate— registering a UDF with a built-in name returnsErr - ✅
name_collision_udf_on_udf— registering a second UDF with the same name returnsErr - ✅
unknown_function_runtime_error— invoking an unregistered aggregate name at query time returnsErr - ✅
unknown_predicate_runtime_error— invoking an unregistered predicate name at query time returnsErr - ✅
thread_safety— concurrent UDF registration and query execution from multiple threads
- ✅
prepare_and_execute_entity_slot— entity$slotsubstituted at execute time; correct results returned - ✅
prepare_and_execute_value_slot— value$slotsubstituted at execute time; correct filtering - ✅
prepare_and_execute_as_of_tx_count—:as-of $txwithTxCountvariant; time-travel query returns correct snapshot - ✅
prepare_and_execute_as_of_timestamp—:as-of $txwithTimestampvariant (millis) - ✅
prepare_and_execute_valid_at_timestamp—:valid-at $datewithTimestampvariant - ✅
prepare_and_execute_valid_at_any—:valid-at $vawithAnyValidTimevariant; all time-windows returned - ✅
prepare_and_execute_combined_temporal_and_entity—:as-of $tx+ entity$slotsimultaneously (primary agentic loop pattern) - ✅
plan_is_reused_across_executions— samePreparedQueryexecuted twice with different bindings; both correct - ✅
error_missing_bind_value— missing$slotat execute time returnsErr - ✅
error_wrong_type_for_as_of—Valsupplied for:as-ofslot returns type-mismatchErr - ✅
error_wrong_type_for_valid_at—TxCountsupplied for:valid-atslot returns type-mismatchErr - ✅
error_wrong_type_for_entity—Valsupplied for entity slot returns type-mismatchErr - ✅
error_attribute_slot_rejected—$slotin attribute position rejected at prepare time - ✅
prepare_with_no_slots— static query prepared and executed correctly (no bindings needed) - ✅
prepare_transact_rejected— preparing a(transact ...)command returnsErr - ✅
execute_with_extra_bindings— extraBindValues beyond declared slots are silently ignored - ✅
multiple_slots_same_execute— multiple distinct$slotnames resolved in a singleexecute()call
Overall Code Coverage: ~94% (estimate)
By Category:
- ✅ Happy path: ~98%
- ✅ Core Datalog operations: ~95%
- ✅ Recursive rules: ~95%
- ✅ Bi-temporal queries: ~95%
- ✅ WAL and crash recovery: ~94%
- ✅ Transaction API: ~93%
- ✅ Covering indexes: ~94%
- ✅ Packed pages + LRU cache: ~91%
- ✅ Error handling: ~84% (raised from ~82% via edge case tests)
- ✅ Edge cases: ~90% (raised from ~87% via edge case + retraction tests)
- ✅ Concurrency: ~92%
- ✅ Performance benchmarks: Criterion suite run at 1K–1M facts; documented in
BENCHMARKS.md(Phase 6.4b)
- Datalog Core — Transact, retract, query
- Pattern Matching — Variable unification, multi-pattern joins
- Fact Storage — EAV model, history, retractions
- EDN Parsing — All Datalog syntax variations
- Storage Backends — File and memory persistence
- Recursive Rules — Semi-naive evaluation, fixed-point iteration
- Transitive Closure — Multi-hop reachability
- Cycle Handling — Graphs with cycles converge correctly
- Complex Queries — 3+ patterns, self-joins, entity references
- Concurrency — Thread-safe rule registration and querying
- Transaction Time —
tx_countincrements,get_facts_as_of()snapshots - Valid Time —
valid_from/valid_tofiltering, boundary semantics - Time Travel Queries —
:as-ofcounter and timestamp - Valid-at Queries — Point-in-time filter,
:any-valid-time - Combined Bi-temporal — Both dimensions in one query
- Transact with Valid Time — Batch-level and per-fact overrides
- File Format Migration — v1→v2 with correct temporal defaults
- WAL Format — CRC32-protected entries, partial-write discard
- Crash Recovery — WAL replay on open, dedup via
last_checkpointed_tx_count - Explicit Transactions —
begin_write/commit/rollback - WAL Ordering — WAL fsynced before in-memory apply (both implicit and explicit paths)
- Checkpoint — WAL flushed to
.graph, WAL deleted, header updated - Auto-checkpoint — Fires at configurable WAL entry threshold
- Thread Safety — Concurrent readers + exclusive writer verified with Barrier
- EAVT/AEVT/AVET/VAET — Four covering indexes with bi-temporal keys
- FactRef — Disk location pointer, slot_index always 0 in 6.1
- Value Encoding — Sort-order-preserving canonical encoding
- B+tree Persistence — Multi-page blob strategy, sort order preserved
- FileHeader v4 — Index root pages, CRC32 checksum
- Index Rebuild — Triggered by checksum mismatch on open
- Query Optimizer — Index hint selection, join reordering by selectivity
- Packed Pages — ~25 facts/page, header + directory + records layout
- FactRef Semantics —
page_id=0= pending,page_id>=1= committed via cache - CommittedFactReader — Trait + impl wired in PersistentFactStorage::load()
- LRU Page Cache — Read-lock on hits, Arc cloning, eviction correctness
- v4→v5 Migration — Reads one-per-page, repacks, saves with new format
- EAVT/AEVT Range Scans — O(log n) entity and attribute lookups
- Retraction Net View —
net_asserted_facts()groups by EAV triple, keeps highesttx_count - Current-Time Retraction — Retracted fact absent from query results with no
:as-of - As-Of Retraction — Retraction visible/invisible at correct tx boundary
- Re-Assert After Retract — Fact reappears when re-asserted
- Retraction in Recursive Rules — Retracted edges not traversed in rule derivation
- Oversized-Fact Early Validation —
check_fact_sizes()rejects before WAL write MAX_FACT_BYTESBoundary — Exact-size accepted, +1 rejected with clear error
- FileHeader v5 Field Offsets — All 10 fields pinned at exact byte positions (big-endian detection coverage)
- Packed Page Header Layout — page_type, reserved, record_count u16 LE, next_page u64 LE at bytes 0–11
- Packed Page Record Directory — (offset u16 LE, length u16 LE) per slot, starting at byte 12
- B+Tree Build + Range Scan —
build_btreeinserts andrange_scanretrieves with correct ordering - Multi-Page Leaf Chain — range scan correctly follows
next_leafpointers across page boundaries - Concurrent Range Scans — 8 barrier-synchronised threads, all return identical non-empty results
- v5→v6 Migration — database opened from v5 format migrates to v6 on first checkpoint
OnDiskIndexReaderFactRef Lookup — committed facts resolved correctly via page cacheMutexStorageBackend— cache-warm pages acquire no backend lock; cache-cold pages lock briefly
not— basic absence query excludes entities where pattern matchesnotin rule body — stratified mixed-rule evaluation applies negation per bindingnot-join— existentially-quantified exclusion with explicit join variablesnot-joinmulti-clause body — inner variables link patterns without escaping to outer scopenot-joinin rule body — negation inside derived rules- Negative cycle rejection —
not/not-joincreating a dependency cycle →Errat registration, rule not added - Safety validation — unbound variables in
notbody orjoin_vars→ parse error with variable name - Nesting constraint —
not-joininsidenotornot-join→ parse error RuleInvocationinnot-joinbody — derived facts in accumulated store correctly negated- Time travel with negation —
not-joinrespects:as-ofand:valid-attemporal filters notandnot-joincoexistence in the same query
- ⏳ Disjunction (
or/or-join) — Phase 7.3 - ⏳ Query optimizer improvements for new clause types (aggregation, expr, disjunction) — Phase 7.4
- ⏳ Prepared statements with temporal bind slots — Phase 7.6
- ⏳ Temporal metadata pseudo-attributes (
:db/valid-from,:db/valid-to,:db/tx-count) — Phase 7.7
- ⏳ Crash during checkpoint write (safe by construction — WAL not deleted until save succeeds; explicit test deferred to Phase 7.5)
- ⏳ Disjunction — Phase 7.3
- ⏳ Known
not-joinlimitation: when a rule B positively invokes rule A and both are stratum 0, single-pass mixed-rule evaluation means B may not see A's derived facts unless rules are declared in dependency order - ⏳
matches?pattern compiled per-row (no caching); will be optimised in Phase 7.9b (FunctionRegistry)
# Run all tests
cargo test
# Run tests quietly with summary
cargo test --quiet
# Run specific test suites
cargo test --lib # Unit tests (216)
cargo test --test bitemporal # Bi-temporal (10)
cargo test --test complex_queries # Complex queries (10)
cargo test --test recursive_rules # Recursive rules (9)
cargo test --test concurrency # Concurrency (7)
cargo test --test wal_test # WAL / crash recovery (12)
cargo test --test index_test # Covering indexes (6)
cargo test --test performance_test # Packed pages (7)
cargo test --test retraction_test # Retraction semantics (7)
cargo test --test edge_cases_test # Edge cases (4)
cargo test --test btree_v6_test # B+tree v6 (8)
cargo test --test negation_test # stratified not (10)
cargo test --test not_join_test # not-join (14)
cargo test --test aggregation_test # aggregation (24)
cargo test --test predicate_expr_test # arithmetic & predicate expr (28)
cargo test --test window_functions_test # window functions (12)
cargo test --test udf_test # user-defined functions (9)
cargo test --test prepared_statements_test # prepared statements (17)
# Run with output
cargo test -- --nocapturePhase 8.1 Status: ✅ COMPLETE
Test Quality: ✅ Excellent — High confidence in all Phase 3-8.1 features
Strengths:
- WAL crash safety verified with real
mem::forgetsimulation - Both implicit and explicit transaction write paths verified
- Thread safety proven with Barrier-synchronized concurrent tests
- Index persistence and CRC32 sync check verified
- Packed page compactness verified against one-per-page estimate
- CommittedFactReader wiring verified with MockLoader in unit tests
- Retraction semantics verified across current-time, as-of, and recursive-rule queries
- Oversized-fact early rejection verified for file-backed databases
- Criterion benchmarks validated performance at 1K–1M facts
- Byte-layout tests pin FileHeader v5/v6 and packed page header field offsets
- On-disk B+tree correctness and concurrent scan safety verified (Phase 6.5)
- Stratified negation (
not/not-join) verified: safety validation, stratification, negative cycle rejection, time-travel integration (Phase 7.1) - Aggregation verified: all 6 aggregate functions,
:withgrouping, bi-temporal + aggregate, rule + aggregate (Phase 7.2a) - Arithmetic & predicate expressions verified: all operators, silent-drop semantics, int/float promotion, regex validation, expr in not/rule body, bi-temporal + expr (Phase 7.2b)
- Disjunction (
or/or-join) verified: flat queries, rule bodies, nested or/not/expr, or-join with private variables, dependency graph (Phase 7.3) - Window functions verified: cumulative aggregates, rank/row-number, partition-by, desc ordering, mixed aggregate+window (Phase 7.7a)
- User-defined functions verified: custom aggregates, custom predicates, UDF as window function, name collision guards, runtime error handling, thread safety (Phase 7.7b)
- Prepared statements verified: entity/value/as-of/valid-at slot positions, AnyValidTime, combined temporal+entity (agentic loop pattern), plan reuse, all error paths (Phase 7.8)
- Public API surface verified via rustdoc doctests:
Minigraf::open,execute,prepare,repl,WriteTransaction,OpenOptions(Phase 7.9) - 795 tests covering all Phase 3-8.1 features (including browser WASM + WASI + cross-platform compat)
Confidence Level: ✅ Production-ready for Phase 8.1 scope
Readiness for Phase 8.2: ✅ Ready to proceed
The WebAssembly-capable (browser + WASI), publish-ready, prepared-statement-capable, UDF-capable, window-function-capable, disjunction + aggregation + arithmetic/predicate expression capable, stratified-negation-capable, on-disk B+tree indexed, packed, cached bi-temporal Datalog engine is solid, well-tested, documented, and benchmarked.
Next Steps: Begin Phase 8 (Cross-Platform Expansion — WASM, mobile, language bindings)