diff --git a/.github/workflows/policy.yml b/.github/workflows/policy.yml index b413d6db2..713331779 100644 --- a/.github/workflows/policy.yml +++ b/.github/workflows/policy.yml @@ -4,6 +4,7 @@ name: Policy # - msrv: the crate builds and its unit tests pass on `rust-version` # - semver-checks: no accidental public-API break against the latest release tag # - cargo-deny: dependency licenses, duplicate versions and sources (deny.toml) +# - golden-corpus: golden files in tests/golden/ are only ever added (#391) on: push: @@ -88,3 +89,25 @@ jobs: with: # Advisories are covered by cargo-audit.yml and osv-scanner.yml. command: check bans licenses sources + + golden-corpus: + name: Golden files are add-only + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Refuse modified, deleted or renamed golden files + env: + BASE_REF: ${{ github.base_ref }} + run: | + changed=$(git diff --name-only --diff-filter=MDRT "origin/${BASE_REF}...HEAD" -- \ + 'tests/golden/*.graph' 'tests/golden/*.wal') + if [ -n "$changed" ]; then + echo "::error::Golden files are never regenerated, only added (tests/golden/README.md):" + echo "$changed" + exit 1 + fi diff --git a/.gitignore b/.gitignore index e38b5879c..e4dbd321a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ minigraf-ffi/python/minigraf/minigraf_ffi/ minigraf-ffi/python/**/__pycache__/ minigraf-ffi/python/.venv/ minigraf-ffi/python/dist/ + +# golden-file generator build output (tests/golden/gen/*) +tests/golden/gen/*/target/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bed64d22..d236784da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **B+tree reads no longer follow leaf sibling links** (v8 storage format, PR 1 of 5; #374). Every index scan uses a cursor that walks down from the root and keeps the path, with a forward `seek` that climbs only as far as needed. This prepares for copy-on-write index pages, which cannot keep sibling links. The file format and query results are unchanged. Concurrent 100k-fact scans take 25–37 % less time and checkpointing after one fact at 10k facts 9 % less; single point lookups and attribute scans are 2–6 % slower (about 0.4 µs per lookup). A corrupted child pointer now fails with STG-013 (wrong page type) or INT-049 (cycle) instead of following a bad link. +### Tests + +- **Golden-file compatibility corpus (#391).** `tests/golden/` holds v7 files written by minigraf 2.0.3, each with a JSON manifest of its CRC32, `tx_count` and expected query results. They cover one checkpoint, several checkpoints, indexes rebuilt on open, a WAL left by a crash, a WAL whose entries were already checkpointed, and same-transaction multi-values (#371). `tests/golden_corpus_test.rs` opens a copy of each file and checks its manifest after open, after a checkpoint and reopen, and after one more write. Golden files are never regenerated, only added; a new `golden-corpus` job in `policy.yml` fails a pull request that changes one. The generator is a standalone crate pinned to `minigraf = "=2.0.3"` (`tests/golden/gen/v7/`). On `v3`, each v7 file must also carry a v8 meta page after its first open, and seven v8 files are frozen alongside them, written by the `v3` branch at `23aa57c` (`tests/golden/gen/v8/`): one checkpoint, seven copy-on-write checkpoints, a crash-left v2 WAL, an already-checkpointed v2 WAL, same-transaction multi-values, a v7 file migrated in place, and long values (a 4,068-byte string stored twice, the 64/65-byte inline boundary, a 1,000-byte attribute and keyword). All #371 queries now pass. + ## v2.0.3 — 2026-10-06 Patch release on the v2.x line with two data-integrity fixes, under the [support policy](PHILOSOPHY.md#support-policy). File format is unchanged (v7). No API changes. Upgrading is recommended for every v2.x user. diff --git a/CLAUDE.md b/CLAUDE.md index a6f5f954d..bed4d7914 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -193,7 +193,7 @@ Auto-migrates v7 → v8 on open (spec §9, with a backup meta page). v1–v6 are ## Test Coverage -**1236 tests** (1227 passing, 9 ignored; unit + integration + doc). +**1238 tests** (1229 passing, 9 ignored; unit + integration + doc). See `docs/TEST_COVERAGE.md` for the full per-file breakdown. **Testing conventions** — see the Testing Conventions section below before writing any tests. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92ae86877..0fb383d26 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,6 +100,13 @@ Bugs that can lose, corrupt or hide data are handled so that users can always se At release time, the maintainer updates the pinned issue and closes the issues whose fixes are in the release. +## Golden Files + +`tests/golden/` holds database files written by released versions, each with a manifest of the results it must give (#391). See [`tests/golden/README.md`](tests/golden/README.md). + +- Golden files are **never regenerated, only added**. CI fails a pull request that modifies, deletes or renames one. +- Every release that changes on-disk behavior (a new format version, a new page or WAL shape, a migration) adds golden files for it, with the recipe in a generator crate under `tests/golden/gen/`. + ## Philosophy Check Before submitting, ask yourself: diff --git a/Cargo.toml b/Cargo.toml index fa1e81c16..fe4a62be2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = ["."] -exclude = ["fuzz"] +exclude = ["fuzz", "tests/golden/gen/v7", "tests/golden/gen/v8"] resolver = "2" [package] diff --git a/README.md b/README.md index 6c2285e68..fef7e6c2d 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ let r2 = pq.execute(&[("tx", BindValue::TxCount(2)), ("entity", BindValue::Entit ```bash cargo run # interactive Datalog REPL -cargo test # run 1236 tests +cargo test # run 1238 tests cargo run < demos/demo_recursive.txt # recursive rules demo ``` diff --git a/ROADMAP.md b/ROADMAP.md index 8c99c4813..df8880166 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -66,7 +66,7 @@ Scope — format and storage integrity: Scope — release-gate testing and documentation: -- Golden-file compatibility corpus for every format version, frozen before v8 work goes further (#391) +- Golden-file compatibility corpus for every format version (#391; done on `v3`: six v7 files from 2.0.3 checked after migration, seven v8 files frozen at `23aa57c`) - SIGKILL crash test that checks committed data through EAVT, AEVT and full scan (#384) - Model-based test of transact/retract/checkpoint/reopen (#385) - Wider property tests: file-backed, batched, retracts, temporal, joins (#386) diff --git a/docs/TEST_COVERAGE.md b/docs/TEST_COVERAGE.md index 7271eaac1..76a0116e9 100644 --- a/docs/TEST_COVERAGE.md +++ b/docs/TEST_COVERAGE.md @@ -2,11 +2,11 @@ ## Coverage Summary -**Verified**: 2026-10-05 with `cargo test --quiet` +**Verified**: 2026-10-06 with `cargo test --quiet` -**Result**: 1,227 passing tests and 9 ignored tests (1,236 total) +**Result**: 1,229 passing tests and 9 ignored tests (1,238 total) -This covers the unreleased file-format-v8 work (#287, #371): the index-key value-collision fix, the v7→v8 migration, and the drop of formats v1–v6. It otherwise still covers the public database API, the Datalog engine, storage and recovery paths, v2.0.0's locking and structured-error-code changes, and v2.0.1's regression test for rebuilding indexes on open after several checkpoints (#370). Merged from the v2.x line: `tests/point_query_history_test.rs` and new storage unit tests check that attribute-narrowed bound-entity lookups return exactly what a full scan returns, and that `net_asserted_facts` matches its previous implementation on randomized histories (#323). Attribute-scan tests check that the AEVT range covers exactly the queried attribute, including non-ASCII names such as `:丿` and prefix siblings such as `:ab` for `:a` (#381). Incremental B+tree rebuild tests check that checkpoints which copy untouched index leaves produce exactly the entries and file checksum a full rebuild would, including over repeated checkpoints, that single-fact checkpoints do not fragment the index, and that a damaged slot directory is rejected rather than copied (#315). B+tree cursor tests check that every scan walks the tree, not the leaf `next_leaf` chain: full scans and start positions match the input, `seek` from the current position lands where a fresh descent from the root would (randomized, plus within a leaf, to a sibling, across subtrees, past the end, into the gap after a leaf, and backwards as a no-op), a seek to the sibling leaf reads exactly one page, reads are unchanged with every `next_leaf` zeroed, a child pointer into a non-B+tree page is STG-013, and a cycle in child pointers is an error, not a hang (v8 storage format PR 1, #374). Directory-sync unit tests check that creating the `.graph` file or the WAL, and deleting the WAL, fsync the parent directory after the file operation, and that reopening an existing file does not (#389). Disjunction tests check that `or-join` returns an empty result, not INT-031, when no rows reach it: on an empty database, after a pattern that matches nothing, after a `not` that removes every row, and in a rule body (#405). A stratification unit test builds the same two-rule negative cycle 64 times and checks that the INT-054 message names the same predicate pair every time (#410). Magic-sets tests check that a recursive rule with a literal start that recurses through another rule returns the same results as without the literal (#297). API tests check that `execute()` and `WriteTransaction::execute()` reject `$slot` bind tokens in every position with API-010 (#407). The REPL `run()` test now runs in a child process with null stdin, so it no longer hangs when the test runner's stdin is an open non-TTY stream (#415). v8 page-format tests (PR 2, #374, #388) check the 24-byte page header (a flipped bit, a wrong page id and a future generation each give their own STG code, legacy page types fail before the CRC, and a failed page is never cached), meta-page encoding, the free-list chain, WAL v2's base generation, and every row of the meta-selection table: both slots valid, a torn newest slot with the WAL's base generation (opens at the older one), a WAL newer than the valid meta, pages of the next generation on the free list or past `page_count` with no WAL (all STG-033, with the file unmodified), an unknown feature bit (STG-034), and a pre-release v8 single header (STG-032). Fault-injection tests crash `save()` at every page write (cleanly and with torn pages, including the meta write) and every sync, and crash a v7 migration at every write; each reopen finds exactly the old or the new checkpoint, and every reachable page plus the free list covers the file exactly. Twenty-five consecutive saves never write a page the previous meta references. Covering-key tests (PR 3, #433) check that byte order equals logical order for every key component and value type on random tuples (negative, zero and large integers, NaN and ±0.0, strings with 0x00, 64- and 65-byte strings, FOREVER, newest-transaction-first) and that every component round-trips; that prefix-compressed leaves and shortest-separator internal nodes round-trip, agree with a linear scan, keep `max(left) < s ≤ min(right)`, and reject an overflow; that the byte-key B+tree's builds, incremental rebuilds, cursor and `seek` match their expected sets; that dictionary ids are assigned once, never reused, shared between a keyword attribute and value, and deterministic; that a re-asserted or retracted long value writes no new value page and a forced hash collision still finds the right value; that one transaction with two timestamps is STG-037 and a missing dictionary entry STG-036; that committed reads by entity, entity and attribute, attribute, and in full equal an in-memory model across eight checkpoints and a reopen; that short-valued reads touch no value page; that a v7 file with every value type, long strings, refs and retractions migrates to exactly the same facts; that a pre-covering-key v8 file is STG-032; and that string values over 4,068 bytes and attributes or keywords over 1,024 bytes are WAL-003. A WAL test checks that reopening next to a fully checkpointed WAL does not reuse transaction numbers (#447). Copy-on-write checkpoint tests (PR 4, #434) check that lazy free-list pops and pushes conserve every page across page boundaries, partial head pages and chain exhaustion, never hand out a page freed in the same generation, and share unread chain pages unchanged; that a copy-on-write insert equals a sorted merge for random batches (including keys below the first and above the last, many into one leaf, root growth to depth 3, and 1 KB separators that split internal nodes), leaves the old tree readable and unchanged, frees exactly the replaced nodes, writes only new pages, writes exactly one leaf and two internal nodes for one entry in a depth-3 tree, and does not fragment leaves under single inserts; that a checkpoint after 1 or 100 right-edge facts writes at most the tree-depth difference more pages at 100,000 facts than at 10,000, and at most one path per new entry per tree for random keys; and that four reader threads querying throughout 40 checkpoints never fail and never see the fact count go backwards. Hardening tests (PR 5) damage an index leaf, an internal node, a dictionary leaf, a value page and a free-list page in turn. Every committed read or checkpoint that reaches the damaged page fails with STG-029, never with wrong or fewer rows. A query through `Minigraf` on a file with damaged leaves fails with STG-029, and the file still opens at its last checkpoint. A size test measures at most 200 bytes per fact (142 measured) on 144k facts in #433's shape; its 1.15M-fact variant is ignored locally and runs in the nightly smoke workflow. A shared dictionary-cache test checks that cached ident and timestamp lookups return the same facts and that the cache stays bounded. Merged from the v2.x line: storage and `edge_cases_test.rs` tests check that more than 65,535 uncheckpointed facts each resolve to their own fact through entity lookups and attribute scans, in memory and file-backed (#445). +This covers the unreleased file-format-v8 work (#287, #371): the index-key value-collision fix, the v7→v8 migration, and the drop of formats v1–v6. It otherwise still covers the public database API, the Datalog engine, storage and recovery paths, v2.0.0's locking and structured-error-code changes, and v2.0.1's regression test for rebuilding indexes on open after several checkpoints (#370). Merged from the v2.x line: `tests/point_query_history_test.rs` and new storage unit tests check that attribute-narrowed bound-entity lookups return exactly what a full scan returns, and that `net_asserted_facts` matches its previous implementation on randomized histories (#323). Attribute-scan tests check that the AEVT range covers exactly the queried attribute, including non-ASCII names such as `:丿` and prefix siblings such as `:ab` for `:a` (#381). Incremental B+tree rebuild tests check that checkpoints which copy untouched index leaves produce exactly the entries and file checksum a full rebuild would, including over repeated checkpoints, that single-fact checkpoints do not fragment the index, and that a damaged slot directory is rejected rather than copied (#315). B+tree cursor tests check that every scan walks the tree, not the leaf `next_leaf` chain: full scans and start positions match the input, `seek` from the current position lands where a fresh descent from the root would (randomized, plus within a leaf, to a sibling, across subtrees, past the end, into the gap after a leaf, and backwards as a no-op), a seek to the sibling leaf reads exactly one page, reads are unchanged with every `next_leaf` zeroed, a child pointer into a non-B+tree page is STG-013, and a cycle in child pointers is an error, not a hang (v8 storage format PR 1, #374). Directory-sync unit tests check that creating the `.graph` file or the WAL, and deleting the WAL, fsync the parent directory after the file operation, and that reopening an existing file does not (#389). Disjunction tests check that `or-join` returns an empty result, not INT-031, when no rows reach it: on an empty database, after a pattern that matches nothing, after a `not` that removes every row, and in a rule body (#405). A stratification unit test builds the same two-rule negative cycle 64 times and checks that the INT-054 message names the same predicate pair every time (#410). Magic-sets tests check that a recursive rule with a literal start that recurses through another rule returns the same results as without the literal (#297). API tests check that `execute()` and `WriteTransaction::execute()` reject `$slot` bind tokens in every position with API-010 (#407). The REPL `run()` test now runs in a child process with null stdin, so it no longer hangs when the test runner's stdin is an open non-TTY stream (#415). v8 page-format tests (PR 2, #374, #388) check the 24-byte page header (a flipped bit, a wrong page id and a future generation each give their own STG code, legacy page types fail before the CRC, and a failed page is never cached), meta-page encoding, the free-list chain, WAL v2's base generation, and every row of the meta-selection table: both slots valid, a torn newest slot with the WAL's base generation (opens at the older one), a WAL newer than the valid meta, pages of the next generation on the free list or past `page_count` with no WAL (all STG-033, with the file unmodified), an unknown feature bit (STG-034), and a pre-release v8 single header (STG-032). Fault-injection tests crash `save()` at every page write (cleanly and with torn pages, including the meta write) and every sync, and crash a v7 migration at every write; each reopen finds exactly the old or the new checkpoint, and every reachable page plus the free list covers the file exactly. Twenty-five consecutive saves never write a page the previous meta references. Covering-key tests (PR 3, #433) check that byte order equals logical order for every key component and value type on random tuples (negative, zero and large integers, NaN and ±0.0, strings with 0x00, 64- and 65-byte strings, FOREVER, newest-transaction-first) and that every component round-trips; that prefix-compressed leaves and shortest-separator internal nodes round-trip, agree with a linear scan, keep `max(left) < s ≤ min(right)`, and reject an overflow; that the byte-key B+tree's builds, incremental rebuilds, cursor and `seek` match their expected sets; that dictionary ids are assigned once, never reused, shared between a keyword attribute and value, and deterministic; that a re-asserted or retracted long value writes no new value page and a forced hash collision still finds the right value; that one transaction with two timestamps is STG-037 and a missing dictionary entry STG-036; that committed reads by entity, entity and attribute, attribute, and in full equal an in-memory model across eight checkpoints and a reopen; that short-valued reads touch no value page; that a v7 file with every value type, long strings, refs and retractions migrates to exactly the same facts; that a pre-covering-key v8 file is STG-032; and that string values over 4,068 bytes and attributes or keywords over 1,024 bytes are WAL-003. A WAL test checks that reopening next to a fully checkpointed WAL does not reuse transaction numbers (#447). Copy-on-write checkpoint tests (PR 4, #434) check that lazy free-list pops and pushes conserve every page across page boundaries, partial head pages and chain exhaustion, never hand out a page freed in the same generation, and share unread chain pages unchanged; that a copy-on-write insert equals a sorted merge for random batches (including keys below the first and above the last, many into one leaf, root growth to depth 3, and 1 KB separators that split internal nodes), leaves the old tree readable and unchanged, frees exactly the replaced nodes, writes only new pages, writes exactly one leaf and two internal nodes for one entry in a depth-3 tree, and does not fragment leaves under single inserts; that a checkpoint after 1 or 100 right-edge facts writes at most the tree-depth difference more pages at 100,000 facts than at 10,000, and at most one path per new entry per tree for random keys; and that four reader threads querying throughout 40 checkpoints never fail and never see the fact count go backwards. Hardening tests (PR 5) damage an index leaf, an internal node, a dictionary leaf, a value page and a free-list page in turn. Every committed read or checkpoint that reaches the damaged page fails with STG-029, never with wrong or fewer rows. A query through `Minigraf` on a file with damaged leaves fails with STG-029, and the file still opens at its last checkpoint. A size test measures at most 200 bytes per fact (142 measured) on 144k facts in #433's shape; its 1.15M-fact variant is ignored locally and runs in the nightly smoke workflow. A shared dictionary-cache test checks that cached ident and timestamp lookups return the same facts and that the cache stays bounded. Merged from the v2.x line: storage and `edge_cases_test.rs` tests check that more than 65,535 uncheckpointed facts each resolve to their own fact through entity lookups and attribute scans, in memory and file-backed (#445). Merged from the v2.x line: `tests/golden_corpus_test.rs` opens a copy of each committed golden file in `tests/golden/` (v7 files written by minigraf 2.0.3: one checkpoint, several checkpoints, indexes rebuilt on open, a crash-left WAL, an already-checkpointed WAL, same-transaction multi-values) and checks its manifest's CRC32, `tx_count` and query results after open, after a checkpoint and reopen, and after one more write; a second test checks that every golden file has a manifest (#391). On `v3` the same test checks that each v7 file has a v8 meta page after its first open and gives the same results after reopening the migrated file, and adds seven v8 golden files (one checkpoint, seven copy-on-write checkpoints, a crash-left v2 WAL, an already-checkpointed v2 WAL, same-transaction multi-values, a v7 file migrated in place, and long values at the size limits); the #371 queries, skipped on v2.x, run and pass. ### Covered Areas @@ -15,7 +15,7 @@ This covers the unreleased file-format-v8 work (#287, #371): the index-key value - **Storage correctness**: packed pages, B+tree indexes and range scans, cache behavior, file-header validation, migration (including v7→v8 index rebuild and v1–v6 rejection via `STG-028`), WAL replay, checksums, corruption handling, and fault injection. - **Reliability and concurrency**: concurrent reads and writes, rollback behavior, same-process handle exclusion, cross-process locking, PID namespaces, NFS and container lock behavior, and SIGKILL recovery. - **Public diagnostics**: structured parser, query, storage, WAL, API, and internal error codes; the error-code registry is checked against [`ERROR_REFERENCE.md`](ERROR_REFERENCE.md). -- **Compatibility and quality**: cross-platform format compatibility, property-based tests, long-haul smoke coverage, XTDB/Datomic semantic compatibility, grammar conformance, and rustdoc examples. +- **Compatibility and quality**: golden-file corpus of released file formats, cross-platform format compatibility, property-based tests, long-haul smoke coverage, XTDB/Datomic semantic compatibility, grammar conformance, and rustdoc examples. ### Ignored Tests diff --git a/docs/superpowers/plans/2026-10-06-golden-file-corpus.md b/docs/superpowers/plans/2026-10-06-golden-file-corpus.md new file mode 100644 index 000000000..37975d064 --- /dev/null +++ b/docs/superpowers/plans/2026-10-06-golden-file-corpus.md @@ -0,0 +1,35 @@ +# Golden-file corpus: implementation plan (#391) + +Spec: `docs/superpowers/specs/2026-10-06-golden-file-corpus-design.md`. + +## PR A: `main` (branch `test/391-golden-corpus`) + +1. **Generator crate** `tests/golden/gen/v7/` with `minigraf = "=2.0.3"` and + `crc32fast`, its own `[workspace]`, and a committed `Cargo.lock`. Add the + crate to root `workspace.exclude`. It holds one function per recipe: + `basic`, `multi_checkpoint`, `index_rebuilt`, `pending_wal` and `multivalue`. + It refuses to overwrite existing files and prints the CRC32 and `tx_count` + of each file. +2. **Generate** into `tests/golden/`. Check the version bytes (`xxd -l 8`) and + that `v7_pending_wal.graph.wal` exists. +3. **Manifests**: write the five `.json` files by hand from the recipes, as in + spec §5. +4. **Harness** `tests/golden_corpus_test.rs` with `READER = 2`, implementing + the steps in spec §6. Check that it is green, then that it catches a broken + manifest: change one expected row and one CRC, see both failures reported, + then revert. +5. **Policy job** `golden-corpus` in `.github/workflows/policy.yml`. This job + lets PRs add golden files but never modify or delete them. +6. **Docs**: `tests/golden/README.md`, CONTRIBUTING.md (the add-only rule), + `docs/TEST_COVERAGE.md`, CHANGELOG Unreleased, and the CLAUDE.md test count. +7. Run fmt, clippy, and `cargo test`. Open the PR to `main` and own CI until it + passes. + +## PR B: `v3` (after `main` is merged into `v3`) + +1. Set `READER = 3`. Add the migration checks: the header is v8 after open, and + the v1 WAL is replayed and then removed. +2. **Generator crate** `tests/golden/gen/v8/` pinned to a `v3` git rev. It + writes `basic`, `multi_checkpoint`, `pending_wal`, `multivalue`, + `migrated_from_v7` and `large_values`. +3. Write the v8 manifests and update the v3 docs. Open the PR to `v3`. diff --git a/docs/superpowers/specs/2026-10-06-golden-file-corpus-design.md b/docs/superpowers/specs/2026-10-06-golden-file-corpus-design.md new file mode 100644 index 000000000..42641ab0a --- /dev/null +++ b/docs/superpowers/specs/2026-10-06-golden-file-corpus-design.md @@ -0,0 +1,190 @@ +# Golden-file compatibility corpus (#391) + +**Issue:** #391 (parent #383, milestone v3.0.0) +**Status:** design +**Branches:** PR A on `main` (v7 corpus), PR B on `v3` (migration checks + v8 corpus) + +## 1. Problem + +`tests/fixtures/` holds `compat.graph`, a two-fact v7 file. On `v3` there is also +`v7_multivalue.graph` (#371). No committed file covers the shapes real users have +on disk: several checkpoints, an index rebuilt on open, a leftover WAL. v3.0.0 +migrates v7 to v8 one way, so the v7 shapes must be frozen with expected results +before the migration ships. v8 must be frozen too, now that its layout is settled +(v8 spec §11). + +## 2. Scope + +- **Supported versions.** On `v3`, v1–v6 are rejected with STG-028 + (`migration_matrix_test.rs` covers this), so "every supported version" means v7 + and v8. Real-data v1–v6 files are out of scope. Those versions only matter to + v2.x, which gets data-integrity fixes only. +- **`main` (v2.x)** gets the v7 corpus with read, checkpoint and reopen checks. That + guards v2.x patch releases against breaking their own format. +- **`v3`** gets the same v7 files (through the regular `main` → `v3` merge), checks + them after migration, and adds a v8 corpus. + +## 3. Layout + +``` +tests/golden/ + README.md rules + how each file was made + v7_basic.graph + v7_basic.json + v7_multi_checkpoint.graph + .json + v7_index_rebuilt.graph + .json + v7_pending_wal.graph + v7_pending_wal.graph.wal + .json + v7_stale_wal.graph + v7_stale_wal.graph.wal + .json + v7_multivalue.graph + .json + v8_*.graph / .wal / .json (PR B) + gen/v7/ standalone crate, minigraf = "=2.0.3" (crates.io) + gen/v8/ standalone crate, minigraf = { git, rev = } (PR B) +tests/golden_corpus_test.rs one harness, runs every manifest +``` + +Each generator crate has its own `[workspace]` table and a committed `Cargo.lock`. +It is listed in the root `workspace.exclude`, so `cargo build` and `cargo test` never +build it. `cargo package` skips directories that have their own `Cargo.toml`. + +## 4. Generators + +A generator records how a file was made. It is never re-run to replace a committed +file. `cargo run -- ` writes every file into ``. It refuses to run +if a target already exists, then prints each file's CRC32 and final `tx_count` for +the manifest. + +Shared dataset (`base`), three transactions: + +1. Entities `:alice`, `:bob` and `:carol` with every value type: string (ASCII, + Unicode `"Zoë 🚀"`, a 2,000-byte string), integer (including `i64::MIN` and + `i64::MAX`), float, boolean, keyword, and ref (`:alice :person/friend :bob`). +2. Valid-time facts: `:alice :employment/status` with two non-overlapping + `{:valid-from :valid-to}` windows, plus one open-ended window. +3. Retract `[:alice :person/age 30]`, assert `[:alice :person/age 31]`. + +v7 files (gen/v7, minigraf 2.0.3): + +| File | Recipe | +|---|---| +| `v7_basic` | `base`, one checkpoint | +| `v7_multi_checkpoint` | `base` with a checkpoint after each transaction, then 300 filler entities in 3 transacts with checkpoints between them, so the facts span several fact pages and saves | +| `v7_index_rebuilt` | Same as `v7_multi_checkpoint`, then flip byte 64 (`index_checksum`) and re-seal the header CRC (bytes 80..84), then open with 2.0.3, which rebuilds the indexes and rewrites the header (#370 path), then close | +| `v7_pending_wal` | `base` checkpointed, then 2 more transacts (one with a retraction) under `wal_checkpoint_threshold(usize::MAX)`, then `mem::forget` on the handle, as a crash would: the transactions live only in a v1 WAL | +| `v7_stale_wal` | As `v7_pending_wal`, but the handle is dropped. 2.0.3's `PersistentFactStorage` saves on drop even under `usize::MAX`, so the facts reach the file and the WAL holds only already-checkpointed entries (#447 shape; also what a crash between a checkpoint's save and its WAL delete leaves) | +| `v7_multivalue` | The #371 shape: `[:t/x :kind :k/a] [:t/x :kind :k/b]` plus 30 fillers in one transact, then `[:t/y :tag :g/a] [:t/y :tag :g/b]` and a batched retract of both, then checkpoint | + +v8 files (gen/v8, PR B, `v3` pinned at a commit after #452): + +| File | Recipe | +|---|---| +| `v8_basic` | `base`, one checkpoint | +| `v8_multi_checkpoint` | As for v7. COW checkpoints reuse pages from the free list and alternate meta slots A/B | +| `v8_pending_wal` | As for v7. WAL version 2 with `base_generation` | +| `v8_stale_wal` | As for v7, with a v2 WAL | +| `v8_multivalue` | The #371 shape, written natively | +| `v8_migrated_from_v7` | A copy of `v7_multi_checkpoint` opened by the pinned `v3` build, which migrates it, then closed | +| `v8_large_values` | Values that need value pages, near `MAX_VALUE_BYTES` | + +## 5. Manifest + +One JSON file per golden file, read with `serde_json` (already a dev-dependency). +The expected rows are **written by hand from the recipe**, not recorded from a +read. A recorded read would freeze a bug such as #371. + +```json +{ + "file": "v7_pending_wal.graph", + "wal": "v7_pending_wal.graph.wal", + "format": 7, + "written_by": "minigraf 2.0.3 (crates.io), tests/golden/gen/v7 recipe pending_wal", + "crc32": { "graph": "0x1a2b3c4d", "wal": "0x5e6f7081" }, + "tx_count": 5, + "queries": [ + { "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [["\"Alice\"", "31"], ["\"Bob\"", "42"]] }, + { "name": "multi-value via EAVT", + "query": "(query [:find ?v :where [:t/x :kind ?v]])", + "rows": [[":k/a"], [":k/b"]], + "min_reader": 3 } + ] +} +``` + +- Each value in `rows` is written in canonical text: strings as JSON-escaped literals + in quotes, integers in decimal, floats as Rust `{}` output, `true`/`false`, + keywords as `:ns/name`, `nil` for null. Queries never return entity ids or refs. + They join through them instead, because ids are not a stable output. +- Rows are compared as **sets**, since the v8 spec allows a different row order. +- `min_reader` skips a query on older readers. It is used only where a release + is known to be wrong (#371 on v2.x). Each use carries a `"known_issue"` field. +- Queries cover: every value type; EAVT, AEVT and AVET lookups; joins through refs; + `:as-of N` for each `tx_count`; `:valid-at` inside, between and outside windows; + `:any-valid-time`; `not`; and `count` aggregation. + +## 6. Harness (`tests/golden_corpus_test.rs`) + +`const READER: u32` is 2 on `main` and 3 on `v3`. The harness finds every +`tests/golden/*.json` and, for each one: + +1. **Frozen check.** The CRC32 of the committed `.graph` (and `.wal`) equals the + manifest value. +2. **Skip.** A manifest whose `format` this reader cannot open is skipped: v8 on + `main`. On `v3` nothing is skipped. +3. **Open.** Copy the file (and WAL) into a tempdir and open it. Check + `current_tx_count() == tx_count` and run every query. +4. **Persist.** Checkpoint, close, reopen, and run the queries again. On `v3`, the + header must now be v8 and the WAL must be gone. (v2.x keeps an already-checkpointed + WAL after a checkpoint, because replay counted no new entries. The data is correct, + so `main` does not check this.) +5. **Write after.** Transact one fact. `tx_count` becomes `tx_count + 1`. Reopen: + the new fact is present and the counter has not rewound (#447). +6. **Untouched.** The committed fixture's bytes are unchanged. The harness only + ever writes to the tempdir copy. + +PR B adds these `v3`-only checks (as delivered, the WAL-gone check skips manifests marked `"stale_wal": true`: v3 also keeps an already-checkpointed WAL until the next write is checkpointed): + +- The first open of each v7 file migrates it. Opening the same copy again + without a checkpoint gives the same results. +- `compat.graph` and `tests/fixtures/v7_multivalue.graph` stay where they are and + keep their current tests. They are not moved, because moving them breaks + `include_bytes!` users (`src/browser/mod.rs`). + +One `#[test]` per manifest would need a macro or build script. Instead, a single +test walks every manifest, collects all failures, and reports each one by manifest +and query name, without `{:?}` on `Value`/`Result` (CodeQL rule). One failing +manifest does not hide the others. + +## 7. Add-only rule + +- **CI.** A new `golden-corpus` job in `policy.yml` runs on pull requests. It fails + if `git diff --diff-filter=MDR origin/...HEAD -- 'tests/golden/*.graph' + 'tests/golden/*.wal'` lists anything. Manifests may gain queries, and reviewers + check edits to existing expectations. +- **CRC.** The CRC in the manifest catches a changed file even when the CI job + is bypassed. +- **Docs.** `tests/golden/README.md` and CONTRIBUTING.md: every release that + changes on-disk behavior adds golden files with a generator recipe. Golden files + are never regenerated, only added. + +## 8. Delivery + +**PR A (`main`)**: `tests/golden/` with the v7 files, manifests, README and +`gen/v7`; the harness with `READER = 2`; `workspace.exclude`; the policy job; and +docs (CONTRIBUTING, TEST_COVERAGE, CHANGELOG Unreleased, CLAUDE.md test count). No +release needed. This is test-only. + +**PR B (`v3`)**, after `main` is merged into `v3`: `READER = 3`; the migration +checks; `gen/v8` and the v8 files; v3 docs. The format is frozen from this point: +a change to v8 needs a new golden file and a new feature bit, never an edit. + +## 9. Risks + +- **The 2.0.3 crate differs from the `v2.0.3` tag.** crates.io holds the published + source, which is what users ran. The generator records the version, and its + `Cargo.lock` pins the dependency tree. +- **Floats in manifests.** Only literals with an exact decimal form are used + (`1.5`, `-0.25`), so formatting cannot drift. +- **Wall-clock `tx_id`.** Files hold real timestamps. Queries use `:as-of N` + (tx_count) and explicit valid-time strings, never "now"-relative times. Facts + asserted without `:valid-from` are valid from their `tx_id`, so `:valid-at` + queries use dates after 2026-10-06 or explicit windows only. diff --git a/tests/golden/README.md b/tests/golden/README.md new file mode 100644 index 000000000..4f6e6408c --- /dev/null +++ b/tests/golden/README.md @@ -0,0 +1,89 @@ +# Golden-file compatibility corpus + +Database files written by released versions of Minigraf, each with a manifest of +the results it must give (#391). `tests/golden_corpus_test.rs` opens a copy of every +file with the current build and checks its manifest: + +1. The committed files still match the CRC32 values in the manifest. +2. After open, `tx_count` and every query result match the manifest. +3. After a checkpoint and a reopen, they still match. +4. One more write moves `tx_count` past the file's value and survives a reopen. + +On v3 and later, a v7 file must also have a v8 meta page after its first open +(it is migrated in place), give the same results when the migrated copy is +reopened, and lose its WAL at the checkpoint. + +Manifests for a newer format than the reader supports are skipped. + +## Rules + +- **Golden files are never regenerated, only added.** A `.graph` or `.wal` file here + is a record of what a released version wrote. CI (`policy.yml`, job + `golden-corpus`) fails a pull request that modifies, deletes or renames one. +- **Every release that changes on-disk behavior adds golden files** for that + behavior, with a recipe in a generator crate. +- **Expected rows are written by hand from the recipe**, not recorded from a read. + A recorded read would freeze a bug as expected output. +- `"stale_wal": true` marks a WAL whose entries are all already in the file. A + checkpoint then has nothing to do, so the WAL stays until the next write is + checkpointed; the test does not require it to be gone. +- A query that a release is known to get wrong carries `"min_reader"` (the first + major version that gets it right) and `"known_issue"`. + +## Manifest format + +```json +{ + "file": "v7_basic.graph", + "wal": null, + "format": 7, + "written_by": "minigraf 2.0.3 (crates.io), tests/golden/gen/v7 recipe basic", + "crc32": { "graph": "0x368e4f45", "wal": null }, + "tx_count": 4, + "queries": [ + { "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [["31"]] } + ] +} +``` + +Rows are compared as sets. Each value is written as canonical text: strings as +JSON-escaped literals in quotes, integers in decimal, floats as Rust `{}` output, +`true`/`false`, keywords as `:ns/name`, and `nil` for null. Queries never return +entity ids, because ids are not a stable output. + +## Files + +| File | Written by | Shape | +|---|---|---| +| `v7_basic` | 2.0.3 | Shared dataset (every value type, refs, Unicode, a 2,000-byte string, valid-time windows, a retraction), one checkpoint | +| `v7_multi_checkpoint` | 2.0.3 | Shared dataset with a checkpoint after each transaction, plus 300 filler entities over three more checkpoints | +| `v7_index_rebuilt` | 2.0.3 | `v7_multi_checkpoint` with a damaged `index_checksum`, opened once by 2.0.3, which rebuilt the indexes and rewrote the header. Stale pages remain past `page_count` | +| `v7_pending_wal` | 2.0.3 | Shared dataset checkpointed, then two transactions held only in a v1 WAL (the session ended without running destructors, as a crash would) | +| `v7_stale_wal` | 2.0.3 | Same as `v7_pending_wal`, but the session closed normally: the facts are in the file, and the WAL next to it holds only entries already checkpointed (#447 shape) | +| `v7_multivalue` | 2.0.3 | Same-transaction multi-values and a batched retract (#371). v2.x reads these wrong | +| `v8_basic` | `v3` at 23aa57c | Shared dataset, one checkpoint | +| `v8_multi_checkpoint` | `v3` at 23aa57c | As `v7_multi_checkpoint`: seven copy-on-write checkpoints, alternating meta slots, reused free pages | +| `v8_pending_wal` | `v3` at 23aa57c | As `v7_pending_wal`, with a v2 WAL (records its base generation) | +| `v8_stale_wal` | `v3` at 23aa57c | As `v7_stale_wal`, with a v2 WAL | +| `v8_multivalue` | `v3` at 23aa57c | The #371 shape, written natively | +| `v8_migrated_from_v7` | `v3` at 23aa57c | `v7_multi_checkpoint` migrated in place (v8 spec §9) | +| `v8_large_values` | `v3` at 23aa57c | Shared dataset plus a 4,068-byte string stored twice (deduplicated) with one copy retracted, 64- and 65-byte strings (inline and value-page boundary), a 1,000-byte attribute and a 1,000-byte keyword | + +## Generators + +`gen/v7/` is a standalone crate pinned to `minigraf = "=2.0.3"` from crates.io, +with a committed `Cargo.lock`. It records how each v7 file was made. It is not part +of the workspace and is never re-run to replace a file: it refuses to write over an +existing one. + +`gen/v8/` is the same for the v8 files, pinned by git rev to the `v3` branch at +`23aa57c5e46e7e78fa823425cc74cff3764f1763`, after the v8 format froze and before +v3.0.0 was published. There is no v8 `index_rebuilt` file: v8 never rebuilds an +index on open. + +```sh +cd tests/golden/gen/v7 && cargo run -- +cd tests/golden/gen/v8 && cargo run -- +``` diff --git a/tests/golden/gen/v7/Cargo.lock b/tests/golden/gen/v7/Cargo.lock new file mode 100644 index 000000000..c238c53b9 --- /dev/null +++ b/tests/golden/gen/v7/Cargo.lock @@ -0,0 +1,438 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "minigraf" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46a3a991ca47660196a3595826a080fc31f1a0f0d9a1dd043969e96a18d1fa4" +dependencies = [ + "anyhow", + "chrono", + "crc32fast", + "postcard", + "regex-lite", + "serde", + "uuid", +] + +[[package]] +name = "minigraf-golden-gen-v7" +version = "0.0.0" +dependencies = [ + "crc32fast", + "minigraf", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "uuid" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +dependencies = [ + "getrandom", + "js-sys", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] diff --git a/tests/golden/gen/v7/Cargo.toml b/tests/golden/gen/v7/Cargo.toml new file mode 100644 index 000000000..d1d015ba0 --- /dev/null +++ b/tests/golden/gen/v7/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "minigraf-golden-gen-v7" +version = "0.0.0" +edition = "2024" +publish = false +description = "Records how the v7 golden files in tests/golden/ were written (#391). Never re-run to replace a committed file." + +# Standalone: not a member of the minigraf workspace. +[workspace] + +[dependencies] +minigraf = "=2.0.3" +crc32fast = "1.4" diff --git a/tests/golden/gen/v7/src/main.rs b/tests/golden/gen/v7/src/main.rs new file mode 100644 index 000000000..aabfd80b4 --- /dev/null +++ b/tests/golden/gen/v7/src/main.rs @@ -0,0 +1,228 @@ +//! Writes the v7 golden files in `tests/golden/` with minigraf 2.0.3 (#391). +//! +//! This program records how each committed file was made. It is never re-run to +//! replace one: golden files are only ever added. Usage: +//! +//! ```text +//! cargo run -- +//! ``` +//! +//! It refuses to run if any target file already exists in ``, and prints +//! each file's CRC32 and final `tx_count` for its manifest. + +use std::error::Error; +use std::path::{Path, PathBuf}; + +use minigraf::{Minigraf, OpenOptions}; + +type Res = Result>; +type Recipe = fn(&Path) -> Res<()>; + +const RECIPES: &[(&str, Recipe)] = &[ + ("v7_basic", basic), + ("v7_multi_checkpoint", multi_checkpoint), + ("v7_index_rebuilt", index_rebuilt), + ("v7_pending_wal", pending_wal), + ("v7_stale_wal", stale_wal), + ("v7_multivalue", multivalue), +]; + +fn main() -> Res<()> { + let out = PathBuf::from(std::env::args().nth(1).ok_or("usage: ")?); + for (name, _) in RECIPES { + for ext in ["graph", "graph.wal"] { + let p = out.join(format!("{name}.{ext}")); + if p.exists() { + return Err( + format!("{} exists; golden files are never regenerated", p.display()).into(), + ); + } + } + } + for (name, recipe) in RECIPES { + let path = out.join(format!("{name}.graph")); + recipe(&path)?; + // Hash before anything else opens the file: an open can write to it. + print!("{name}: graph=0x{:08x}", crc(&path)?); + let wal = wal_path(&path); + if wal.exists() { + print!(" wal=0x{:08x}", crc(&wal)?); + } + println!(" tx_count={}", tx_count_of_copy(&path)?); + } + Ok(()) +} + +fn crc(path: &Path) -> Res { + Ok(crc32fast::hash(&std::fs::read(path)?)) +} + +/// Reads `tx_count` from a scratch copy, so the written file stays as it is. +fn tx_count_of_copy(path: &Path) -> Res { + let scratch = path.with_extension("probe.graph"); + std::fs::copy(path, &scratch)?; + if wal_path(path).exists() { + std::fs::copy(wal_path(path), wal_path(&scratch))?; + } + let n = open_no_auto_checkpoint(&scratch)?.current_tx_count(); + std::fs::remove_file(&scratch)?; + let _ = std::fs::remove_file(wal_path(&scratch)); + Ok(n) +} + +fn wal_path(path: &Path) -> PathBuf { + let mut s = path.as_os_str().to_os_string(); + s.push(".wal"); + PathBuf::from(s) +} + +/// Opens without the checkpoint-on-close, so only explicit checkpoints write the file. +fn open_no_auto_checkpoint(path: &Path) -> Res { + Ok(OpenOptions::new() + .wal_checkpoint_threshold(usize::MAX) + .path(path) + .open()?) +} + +/// The shared dataset: four transactions (tx_count 1..=4). +const BASE: &[&str] = &[ + // tx 1: every value type, refs, Unicode and a 2,000-byte string. + r#"(transact [[:alice :person/name "Alice"] + [:alice :person/age 30] + [:alice :person/height 1.5] + [:alice :person/active true] + [:alice :person/role :role/admin] + [:alice :person/friend :bob] + [:alice :person/motto "Zoë 🚀"] + [:bob :person/name "Bob"] + [:bob :person/age 42] + [:bob :person/height -0.25] + [:bob :person/active false] + [:bob :person/role :role/user] + [:bob :person/friend :carol] + [:carol :person/name "Carol"] + [:carol :person/age -9223372036854775808] + [:carol :person/score 9223372036854775807] + [:carol :person/bio "BIO"]])"#, + // tx 2: valid-time windows, one per entity. + r#"(transact [[:carol :employment/status :intern {:valid-from "2020-01-01" :valid-to "2021-01-01"}] + [:alice :employment/status :employee {:valid-from "2021-01-01" :valid-to "2024-01-01"}] + [:bob :employment/status :contractor {:valid-from "2022-01-01"}]])"#, + // tx 3: retraction. + r#"(retract [[:alice :person/age 30]])"#, + // tx 4: re-assertion with a new value. + r#"(transact [[:alice :person/age 31]])"#, +]; + +/// The 2,000-byte string stored as `:carol :person/bio`. +fn bio() -> String { + "0123456789".repeat(200) +} + +fn run_base(db: &Minigraf, checkpoint_each: bool) -> Res<()> { + for cmd in BASE { + db.execute(&cmd.replace("\"BIO\"", &format!("\"{}\"", bio())))?; + if checkpoint_each { + db.checkpoint()?; + } + } + Ok(()) +} + +/// 300 filler entities in three transacts (tx_count 5..=7), with a checkpoint +/// after each, so the facts span several fact pages and saves. +fn run_fillers(db: &Minigraf) -> Res<()> { + for batch in 0..3 { + let mut cmd = String::from("(transact ["); + for i in (batch * 100)..(batch * 100 + 100) { + cmd.push_str(&format!( + "[:f/e{i} :filler/n {i}] [:f/e{i} :filler/group :g/{}] ", + i % 3 + )); + } + cmd.push_str("])"); + db.execute(&cmd)?; + db.checkpoint()?; + } + Ok(()) +} + +/// `base`, one checkpoint. tx_count 4. +fn basic(path: &Path) -> Res<()> { + let db = open_no_auto_checkpoint(path)?; + run_base(&db, false)?; + db.checkpoint()?; + Ok(()) +} + +/// `base` with a checkpoint after each transaction, then the fillers. tx_count 7. +fn multi_checkpoint(path: &Path) -> Res<()> { + let db = open_no_auto_checkpoint(path)?; + run_base(&db, true)?; + run_fillers(&db)?; + Ok(()) +} + +/// `multi_checkpoint`, then a damaged `index_checksum` (header re-sealed), so +/// 2.0.3 rebuilds the indexes on open and rewrites the header (#370 path). +fn index_rebuilt(path: &Path) -> Res<()> { + multi_checkpoint(path)?; + let mut bytes = std::fs::read(path)?; + bytes[64] ^= 0xFF; // index_checksum, bytes 64..68 + let mut h = [0u8; 80]; + h.copy_from_slice(&bytes[..80]); + let seal = crc32fast::hash(&h); // header_checksum covers bytes 0..80 + bytes[80..84].copy_from_slice(&seal.to_le_bytes()); + std::fs::write(path, &bytes)?; + let db = open_no_auto_checkpoint(path)?; + drop(db); + Ok(()) +} + +/// `base` checkpointed, then two transactions written to the WAL by a session that +/// ends without dropping the handle (a crash after the WAL fsync). tx_count 6. +fn pending_wal(path: &Path) -> Res<()> { + let db = wal_session(path)?; + // Skip every destructor, as a killed process would. The lock is released at exit. + std::mem::forget(db); + if !wal_path(path).exists() { + return Err("pending_wal: expected a WAL next to the file".into()); + } + Ok(()) +} + +/// As `pending_wal`, but the handle is dropped. 2.0.3 then saves the facts into the +/// file and leaves the WAL in place, so every WAL entry is already checkpointed. +/// A crash between a checkpoint's save and its WAL delete leaves the same shape +/// (#447). tx_count 6. +fn stale_wal(path: &Path) -> Res<()> { + drop(wal_session(path)?); + if !wal_path(path).exists() { + return Err("stale_wal: expected a WAL next to the file".into()); + } + Ok(()) +} + +fn wal_session(path: &Path) -> Res { + basic(path)?; + let db = open_no_auto_checkpoint(path)?; + db.execute(r#"(transact [[:dave :person/name "Dave"] [:dave :person/age 25]])"#)?; + db.execute(r#"(retract [[:bob :person/active false]])"#)?; + Ok(db) +} + +/// The #371 shape: same-transaction multi-values and a batched retract. tx_count 3. +fn multivalue(path: &Path) -> Res<()> { + let db = open_no_auto_checkpoint(path)?; + let mut cmd = + String::from(r#"(transact [[:t/x :kind :k/a] [:t/x :kind :k/b] [:t/x :note "two"] "#); + for i in 0..30 { + cmd.push_str(&format!(r#"[:t/f-{i} :kind :k/c] [:t/f-{i} :note "f"] "#)); + } + cmd.push_str("])"); + db.execute(&cmd)?; + db.execute("(transact [[:t/y :tag :g/a] [:t/y :tag :g/b]])")?; + db.execute("(retract [[:t/y :tag :g/a] [:t/y :tag :g/b]])")?; + db.checkpoint()?; + Ok(()) +} diff --git a/tests/golden/gen/v8/Cargo.lock b/tests/golden/gen/v8/Cargo.lock new file mode 100644 index 000000000..727a7cc61 --- /dev/null +++ b/tests/golden/gen/v8/Cargo.lock @@ -0,0 +1,437 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "minigraf" +version = "2.0.3" +source = "git+https://github.com/project-minigraf/minigraf?rev=23aa57c5e46e7e78fa823425cc74cff3764f1763#23aa57c5e46e7e78fa823425cc74cff3764f1763" +dependencies = [ + "anyhow", + "chrono", + "crc32fast", + "postcard", + "regex-lite", + "serde", + "uuid", +] + +[[package]] +name = "minigraf-golden-gen-v8" +version = "0.0.0" +dependencies = [ + "crc32fast", + "minigraf", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "uuid" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +dependencies = [ + "getrandom", + "js-sys", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] diff --git a/tests/golden/gen/v8/Cargo.toml b/tests/golden/gen/v8/Cargo.toml new file mode 100644 index 000000000..3df8127aa --- /dev/null +++ b/tests/golden/gen/v8/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "minigraf-golden-gen-v8" +version = "0.0.0" +edition = "2024" +publish = false +description = "Records how the v8 golden files in tests/golden/ were written (#391). Never re-run to replace a committed file." + +# Standalone: not a member of the minigraf workspace. +[workspace] + +[dependencies] +# The `v3` branch after the v8 format froze (PRs #442-#452), before v3.0.0 was published. +minigraf = { git = "https://github.com/project-minigraf/minigraf", rev = "23aa57c5e46e7e78fa823425cc74cff3764f1763" } +crc32fast = "1.4" diff --git a/tests/golden/gen/v8/src/main.rs b/tests/golden/gen/v8/src/main.rs new file mode 100644 index 000000000..8ada0f309 --- /dev/null +++ b/tests/golden/gen/v8/src/main.rs @@ -0,0 +1,257 @@ +//! Writes the v8 golden files in `tests/golden/` with the `v3` branch at +//! 23aa57c, after the v8 format froze (#391). +//! +//! This program records how each committed file was made. It is never re-run to +//! replace one: golden files are only ever added. Usage: +//! +//! ```text +//! cargo run -- +//! ``` +//! +//! It refuses to run if any target file already exists in ``, and prints +//! each file's CRC32 and final `tx_count` for its manifest. + +use std::error::Error; +use std::path::{Path, PathBuf}; + +use minigraf::{Minigraf, OpenOptions}; + +type Res = Result>; +type Recipe = fn(&Path) -> Res<()>; + +const RECIPES: &[(&str, Recipe)] = &[ + ("v8_basic", basic), + ("v8_multi_checkpoint", multi_checkpoint), + ("v8_pending_wal", pending_wal), + ("v8_stale_wal", stale_wal), + ("v8_multivalue", multivalue), + ("v8_migrated_from_v7", migrated_from_v7), + ("v8_large_values", large_values), +]; + +fn main() -> Res<()> { + let out = PathBuf::from(std::env::args().nth(1).ok_or("usage: ")?); + for (name, _) in RECIPES { + for ext in ["graph", "graph.wal"] { + let p = out.join(format!("{name}.{ext}")); + if p.exists() { + return Err( + format!("{} exists; golden files are never regenerated", p.display()).into(), + ); + } + } + } + for (name, recipe) in RECIPES { + let path = out.join(format!("{name}.graph")); + recipe(&path)?; + // Hash before anything else opens the file: an open can write to it. + print!("{name}: graph=0x{:08x}", crc(&path)?); + let wal = wal_path(&path); + if wal.exists() { + print!(" wal=0x{:08x}", crc(&wal)?); + } + println!(" tx_count={}", tx_count_of_copy(&path)?); + } + Ok(()) +} + +fn crc(path: &Path) -> Res { + Ok(crc32fast::hash(&std::fs::read(path)?)) +} + +/// Reads `tx_count` from a scratch copy, so the written file stays as it is. +fn tx_count_of_copy(path: &Path) -> Res { + let scratch = path.with_extension("probe.graph"); + std::fs::copy(path, &scratch)?; + if wal_path(path).exists() { + std::fs::copy(wal_path(path), wal_path(&scratch))?; + } + let n = open_no_auto_checkpoint(&scratch)?.current_tx_count(); + std::fs::remove_file(&scratch)?; + let _ = std::fs::remove_file(wal_path(&scratch)); + Ok(n) +} + +fn wal_path(path: &Path) -> PathBuf { + let mut s = path.as_os_str().to_os_string(); + s.push(".wal"); + PathBuf::from(s) +} + +/// Opens without the checkpoint-on-close, so only explicit checkpoints write the file. +fn open_no_auto_checkpoint(path: &Path) -> Res { + Ok(OpenOptions::new() + .wal_checkpoint_threshold(usize::MAX) + .path(path) + .open()?) +} + +/// The shared dataset: four transactions (tx_count 1..=4). +const BASE: &[&str] = &[ + // tx 1: every value type, refs, Unicode and a 2,000-byte string. + r#"(transact [[:alice :person/name "Alice"] + [:alice :person/age 30] + [:alice :person/height 1.5] + [:alice :person/active true] + [:alice :person/role :role/admin] + [:alice :person/friend :bob] + [:alice :person/motto "Zoë 🚀"] + [:bob :person/name "Bob"] + [:bob :person/age 42] + [:bob :person/height -0.25] + [:bob :person/active false] + [:bob :person/role :role/user] + [:bob :person/friend :carol] + [:carol :person/name "Carol"] + [:carol :person/age -9223372036854775808] + [:carol :person/score 9223372036854775807] + [:carol :person/bio "BIO"]])"#, + // tx 2: valid-time windows, one per entity. + r#"(transact [[:carol :employment/status :intern {:valid-from "2020-01-01" :valid-to "2021-01-01"}] + [:alice :employment/status :employee {:valid-from "2021-01-01" :valid-to "2024-01-01"}] + [:bob :employment/status :contractor {:valid-from "2022-01-01"}]])"#, + // tx 3: retraction. + r#"(retract [[:alice :person/age 30]])"#, + // tx 4: re-assertion with a new value. + r#"(transact [[:alice :person/age 31]])"#, +]; + +/// The 2,000-byte string stored as `:carol :person/bio`. +fn bio() -> String { + "0123456789".repeat(200) +} + +fn run_base(db: &Minigraf, checkpoint_each: bool) -> Res<()> { + for cmd in BASE { + db.execute(&cmd.replace("\"BIO\"", &format!("\"{}\"", bio())))?; + if checkpoint_each { + db.checkpoint()?; + } + } + Ok(()) +} + +/// 300 filler entities in three transacts (tx_count 5..=7), with a checkpoint +/// after each, so the facts span several fact pages and saves. +fn run_fillers(db: &Minigraf) -> Res<()> { + for batch in 0..3 { + let mut cmd = String::from("(transact ["); + for i in (batch * 100)..(batch * 100 + 100) { + cmd.push_str(&format!( + "[:f/e{i} :filler/n {i}] [:f/e{i} :filler/group :g/{}] ", + i % 3 + )); + } + cmd.push_str("])"); + db.execute(&cmd)?; + db.checkpoint()?; + } + Ok(()) +} + +/// `base`, one checkpoint. tx_count 4. +fn basic(path: &Path) -> Res<()> { + let db = open_no_auto_checkpoint(path)?; + run_base(&db, false)?; + db.checkpoint()?; + Ok(()) +} + +/// `base` with a checkpoint after each transaction, then the fillers. tx_count 7. +fn multi_checkpoint(path: &Path) -> Res<()> { + let db = open_no_auto_checkpoint(path)?; + run_base(&db, true)?; + run_fillers(&db)?; + Ok(()) +} + +/// `base` checkpointed, then two transactions written to the WAL by a session that +/// ends without dropping the handle (a crash after the WAL fsync). tx_count 6. +fn pending_wal(path: &Path) -> Res<()> { + let db = wal_session(path)?; + // Skip every destructor, as a killed process would. The lock is released at exit. + std::mem::forget(db); + if !wal_path(path).exists() { + return Err("pending_wal: expected a WAL next to the file".into()); + } + Ok(()) +} + +/// As `pending_wal`, but the handle is dropped. `PersistentFactStorage` then saves +/// the facts into the file and leaves the WAL in place, so every WAL entry is +/// already checkpointed. +/// A crash between a checkpoint's save and its WAL delete leaves the same shape +/// (#447). tx_count 6. +fn stale_wal(path: &Path) -> Res<()> { + drop(wal_session(path)?); + if !wal_path(path).exists() { + return Err("stale_wal: expected a WAL next to the file".into()); + } + Ok(()) +} + +fn wal_session(path: &Path) -> Res { + basic(path)?; + let db = open_no_auto_checkpoint(path)?; + db.execute(r#"(transact [[:dave :person/name "Dave"] [:dave :person/age 25]])"#)?; + db.execute(r#"(retract [[:bob :person/active false]])"#)?; + Ok(db) +} + +/// The #371 shape: same-transaction multi-values and a batched retract. tx_count 3. +fn multivalue(path: &Path) -> Res<()> { + let db = open_no_auto_checkpoint(path)?; + let mut cmd = + String::from(r#"(transact [[:t/x :kind :k/a] [:t/x :kind :k/b] [:t/x :note "two"] "#); + for i in 0..30 { + cmd.push_str(&format!(r#"[:t/f-{i} :kind :k/c] [:t/f-{i} :note "f"] "#)); + } + cmd.push_str("])"); + db.execute(&cmd)?; + db.execute("(transact [[:t/y :tag :g/a] [:t/y :tag :g/b]])")?; + db.execute("(retract [[:t/y :tag :g/a] [:t/y :tag :g/b]])")?; + db.checkpoint()?; + Ok(()) +} + +/// `tests/golden/v7_multi_checkpoint.graph` (written by 2.0.3), opened once by this +/// build, which migrates it to v8 in place (v8 spec §9), then closed. tx_count 7. +fn migrated_from_v7(path: &Path) -> Res<()> { + let v7 = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../v7_multi_checkpoint.graph"); + std::fs::copy(v7, path)?; + drop(Minigraf::open(path)?); + Ok(()) +} + +/// The long values in `large_values`: (attribute, value literal). +fn large_facts() -> Vec<(String, String)> { + let max = &"0123456789abcdef".repeat(255)[..4068]; // MAX_VALUE_BYTES + vec![ + (":blob/max".into(), format!("\"{max}\"")), + // The same long value again: stored once in value pages. + (":blob/max-again".into(), format!("\"{max}\"")), + // 64 bytes stays inline in the key; 65 goes to a value page. + (":blob/inline-64".into(), format!("\"{}\"", "i".repeat(64))), + (":blob/paged-65".into(), format!("\"{}\"", "p".repeat(65))), + // A 1,000-byte attribute name and a 1,000-byte keyword value (MAX_IDENT_BYTES 1,024). + (format!(":blob/{}", "a".repeat(994)), "1".into()), + (":blob/kw".into(), format!(":k/{}", "k".repeat(997))), + ] +} + +/// `base`, then one transact of the long values and a retract of one of them, +/// one checkpoint. tx_count 6. +fn large_values(path: &Path) -> Res<()> { + let db = open_no_auto_checkpoint(path)?; + run_base(&db, false)?; + let mut cmd = String::from("(transact ["); + for (a, v) in large_facts() { + cmd.push_str(&format!("[:big {a} {v}] ")); + } + cmd.push_str("])"); + db.execute(&cmd)?; + let (a, v) = &large_facts()[1]; + db.execute(&format!("(retract [[:big {a} {v}]])"))?; + db.checkpoint()?; + Ok(()) +} diff --git a/tests/golden/v7_basic.graph b/tests/golden/v7_basic.graph new file mode 100644 index 000000000..bfbdfd999 Binary files /dev/null and b/tests/golden/v7_basic.graph differ diff --git a/tests/golden/v7_basic.json b/tests/golden/v7_basic.json new file mode 100644 index 000000000..819ab4b7a --- /dev/null +++ b/tests/golden/v7_basic.json @@ -0,0 +1,263 @@ +{ + "file": "v7_basic.graph", + "wal": null, + "format": 7, + "written_by": "minigraf 2.0.3 (crates.io), tests/golden/gen/v7 recipe basic", + "crc32": { + "graph": "0x368e4f45", + "wal": null + }, + "tx_count": 4, + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "3" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ], + [ + "\"Bob\"", + "false" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + } + ] +} diff --git a/tests/golden/v7_index_rebuilt.graph b/tests/golden/v7_index_rebuilt.graph new file mode 100644 index 000000000..536cfa8d3 Binary files /dev/null and b/tests/golden/v7_index_rebuilt.graph differ diff --git a/tests/golden/v7_index_rebuilt.json b/tests/golden/v7_index_rebuilt.json new file mode 100644 index 000000000..7445d6366 --- /dev/null +++ b/tests/golden/v7_index_rebuilt.json @@ -0,0 +1,309 @@ +{ + "file": "v7_index_rebuilt.graph", + "wal": null, + "format": 7, + "written_by": "minigraf 2.0.3 (crates.io), tests/golden/gen/v7 recipe index_rebuilt", + "crc32": { + "graph": "0xbe8357a7", + "wal": null + }, + "tx_count": 7, + "notes": "indexes rebuilt on open by 2.0.3 after index_checksum damage; page_count shrank, stale pages remain past it", + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "3" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ], + [ + "\"Bob\"", + "false" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "filler count", + "query": "(query [:find (count ?e) :where [?e :filler/n ?n]])", + "rows": [ + [ + "300" + ] + ] + }, + { + "name": "filler group", + "query": "(query [:find (count ?e) :where [?e :filler/group :g/1]])", + "rows": [ + [ + "100" + ] + ] + }, + { + "name": "filler sum", + "query": "(query [:find (sum ?n) :where [?e :filler/n ?n]])", + "rows": [ + [ + "44850" + ] + ] + }, + { + "name": "filler point", + "query": "(query [:find ?g :where [:f/e250 :filler/group ?g]])", + "rows": [ + [ + ":g/1" + ] + ] + }, + { + "name": "filler as-of 5", + "query": "(query [:find (count ?e) :as-of 5 :where [?e :filler/n ?n]])", + "rows": [ + [ + "100" + ] + ] + } + ] +} diff --git a/tests/golden/v7_multi_checkpoint.graph b/tests/golden/v7_multi_checkpoint.graph new file mode 100644 index 000000000..235fedb99 Binary files /dev/null and b/tests/golden/v7_multi_checkpoint.graph differ diff --git a/tests/golden/v7_multi_checkpoint.json b/tests/golden/v7_multi_checkpoint.json new file mode 100644 index 000000000..31a7791fb --- /dev/null +++ b/tests/golden/v7_multi_checkpoint.json @@ -0,0 +1,308 @@ +{ + "file": "v7_multi_checkpoint.graph", + "wal": null, + "format": 7, + "written_by": "minigraf 2.0.3 (crates.io), tests/golden/gen/v7 recipe multi_checkpoint", + "crc32": { + "graph": "0x0fb687cd", + "wal": null + }, + "tx_count": 7, + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "3" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ], + [ + "\"Bob\"", + "false" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "filler count", + "query": "(query [:find (count ?e) :where [?e :filler/n ?n]])", + "rows": [ + [ + "300" + ] + ] + }, + { + "name": "filler group", + "query": "(query [:find (count ?e) :where [?e :filler/group :g/1]])", + "rows": [ + [ + "100" + ] + ] + }, + { + "name": "filler sum", + "query": "(query [:find (sum ?n) :where [?e :filler/n ?n]])", + "rows": [ + [ + "44850" + ] + ] + }, + { + "name": "filler point", + "query": "(query [:find ?g :where [:f/e250 :filler/group ?g]])", + "rows": [ + [ + ":g/1" + ] + ] + }, + { + "name": "filler as-of 5", + "query": "(query [:find (count ?e) :as-of 5 :where [?e :filler/n ?n]])", + "rows": [ + [ + "100" + ] + ] + } + ] +} diff --git a/tests/golden/v7_multivalue.graph b/tests/golden/v7_multivalue.graph new file mode 100644 index 000000000..7bbdd40d3 Binary files /dev/null and b/tests/golden/v7_multivalue.graph differ diff --git a/tests/golden/v7_multivalue.json b/tests/golden/v7_multivalue.json new file mode 100644 index 000000000..268c53128 --- /dev/null +++ b/tests/golden/v7_multivalue.json @@ -0,0 +1,80 @@ +{ + "file": "v7_multivalue.graph", + "wal": null, + "format": 7, + "written_by": "minigraf 2.0.3 (crates.io), tests/golden/gen/v7 recipe multivalue", + "crc32": { + "graph": "0xde15da22", + "wal": null + }, + "tx_count": 3, + "queries": [ + { + "name": "note", + "query": "(query [:find ?n :where [:t/x :note ?n]])", + "rows": [ + [ + "\"two\"" + ] + ] + }, + { + "name": "filler kinds", + "query": "(query [:find (count ?e) :where [?e :kind :k/c]])", + "rows": [ + [ + "30" + ] + ] + }, + { + "name": "multi-value via eavt", + "query": "(query [:find ?v :where [:t/x :kind ?v]])", + "rows": [ + [ + ":k/a" + ], + [ + ":k/b" + ] + ], + "min_reader": 3, + "known_issue": "#371: same-transaction multi-values read back as one value on v2.x" + }, + { + "name": "multi-value via aevt", + "query": "(query [:find ?v :where [?e :kind ?v] [?e :note \"two\"]])", + "rows": [ + [ + ":k/a" + ], + [ + ":k/b" + ] + ], + "min_reader": 3, + "known_issue": "#371: same-transaction multi-values read back as one value on v2.x" + }, + { + "name": "batched retract", + "query": "(query [:find ?t :where [:t/y :tag ?t]])", + "rows": [], + "min_reader": 3, + "known_issue": "#371: same-transaction multi-values read back as one value on v2.x" + }, + { + "name": "multi-value as-of 2", + "query": "(query [:find ?t :as-of 2 :where [:t/y :tag ?t]])", + "rows": [ + [ + ":g/a" + ], + [ + ":g/b" + ] + ], + "min_reader": 3, + "known_issue": "#371: same-transaction multi-values read back as one value on v2.x" + } + ] +} diff --git a/tests/golden/v7_pending_wal.graph b/tests/golden/v7_pending_wal.graph new file mode 100644 index 000000000..6da62adf6 Binary files /dev/null and b/tests/golden/v7_pending_wal.graph differ diff --git a/tests/golden/v7_pending_wal.graph.wal b/tests/golden/v7_pending_wal.graph.wal new file mode 100644 index 000000000..51503ced5 Binary files /dev/null and b/tests/golden/v7_pending_wal.graph.wal differ diff --git a/tests/golden/v7_pending_wal.json b/tests/golden/v7_pending_wal.json new file mode 100644 index 000000000..82af24c87 --- /dev/null +++ b/tests/golden/v7_pending_wal.json @@ -0,0 +1,279 @@ +{ + "file": "v7_pending_wal.graph", + "wal": "v7_pending_wal.graph.wal", + "format": 7, + "written_by": "minigraf 2.0.3 (crates.io), tests/golden/gen/v7 recipe pending_wal", + "crc32": { + "graph": "0xcfc67322", + "wal": "0x6b6dea21" + }, + "tx_count": 6, + "notes": "tx 5 and 6 live only in the v1 WAL: the writing session ended without running destructors, as a crash would", + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ], + [ + "\"Dave\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ], + [ + "\"Dave\"", + "25" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "4" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ], + [ + "\"Dave\"" + ] + ] + }, + { + "name": "bob active as-of 5", + "query": "(query [:find ?b :as-of 5 :where [:bob :person/active ?b]])", + "rows": [ + [ + "false" + ] + ] + } + ] +} diff --git a/tests/golden/v7_stale_wal.graph b/tests/golden/v7_stale_wal.graph new file mode 100644 index 000000000..7f14b201e Binary files /dev/null and b/tests/golden/v7_stale_wal.graph differ diff --git a/tests/golden/v7_stale_wal.graph.wal b/tests/golden/v7_stale_wal.graph.wal new file mode 100644 index 000000000..0585d5104 Binary files /dev/null and b/tests/golden/v7_stale_wal.graph.wal differ diff --git a/tests/golden/v7_stale_wal.json b/tests/golden/v7_stale_wal.json new file mode 100644 index 000000000..e89df3f05 --- /dev/null +++ b/tests/golden/v7_stale_wal.json @@ -0,0 +1,280 @@ +{ + "file": "v7_stale_wal.graph", + "wal": "v7_stale_wal.graph.wal", + "format": 7, + "written_by": "minigraf 2.0.3 (crates.io), tests/golden/gen/v7 recipe stale_wal", + "crc32": { + "graph": "0x15703fbe", + "wal": "0xd171e032" + }, + "tx_count": 6, + "notes": "tx 5 and 6 are in the file and also in the v1 WAL, whose entries are all already checkpointed (#447 shape)", + "stale_wal": true, + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ], + [ + "\"Dave\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ], + [ + "\"Dave\"", + "25" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "4" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ], + [ + "\"Dave\"" + ] + ] + }, + { + "name": "bob active as-of 5", + "query": "(query [:find ?b :as-of 5 :where [:bob :person/active ?b]])", + "rows": [ + [ + "false" + ] + ] + } + ] +} diff --git a/tests/golden/v8_basic.graph b/tests/golden/v8_basic.graph new file mode 100644 index 000000000..d63882f1d Binary files /dev/null and b/tests/golden/v8_basic.graph differ diff --git a/tests/golden/v8_basic.json b/tests/golden/v8_basic.json new file mode 100644 index 000000000..67421535f --- /dev/null +++ b/tests/golden/v8_basic.json @@ -0,0 +1,263 @@ +{ + "file": "v8_basic.graph", + "wal": null, + "format": 8, + "written_by": "minigraf v3 branch at 23aa57c (format v8 frozen), tests/golden/gen/v8 recipe basic", + "crc32": { + "graph": "0x154ae019", + "wal": null + }, + "tx_count": 4, + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "3" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ], + [ + "\"Bob\"", + "false" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + } + ] +} diff --git a/tests/golden/v8_large_values.graph b/tests/golden/v8_large_values.graph new file mode 100644 index 000000000..cd41b4c29 Binary files /dev/null and b/tests/golden/v8_large_values.graph differ diff --git a/tests/golden/v8_large_values.json b/tests/golden/v8_large_values.json new file mode 100644 index 000000000..50dac665b --- /dev/null +++ b/tests/golden/v8_large_values.json @@ -0,0 +1,331 @@ +{ + "file": "v8_large_values.graph", + "wal": null, + "format": 8, + "written_by": "minigraf v3 branch at 23aa57c (format v8 frozen), tests/golden/gen/v8 recipe large_values", + "crc32": { + "graph": "0x0594f7b1", + "wal": null + }, + "tx_count": 6, + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "3" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ], + [ + "\"Bob\"", + "false" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "max value", + "query": "(query [:find ?v :where [:big :blob/max ?v]])", + "rows": [ + [ + "\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123\"" + ] + ] + }, + { + "name": "max value via avet", + "query": "(query [:find (count ?e) :where [?e :blob/max \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123\"]])", + "rows": [ + [ + "1" + ] + ] + }, + { + "name": "deduplicated value retracted", + "query": "(query [:find ?v :where [:big :blob/max-again ?v]])", + "rows": [] + }, + { + "name": "deduplicated value as-of 5", + "query": "(query [:find ?v :as-of 5 :where [:big :blob/max-again ?v]])", + "rows": [ + [ + "\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123\"" + ] + ] + }, + { + "name": "64 bytes inline", + "query": "(query [:find ?v :where [:big :blob/inline-64 ?v]])", + "rows": [ + [ + "\"iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\"" + ] + ] + }, + { + "name": "65 bytes paged", + "query": "(query [:find ?v :where [:big :blob/paged-65 ?v]])", + "rows": [ + [ + "\"ppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp\"" + ] + ] + }, + { + "name": "1,000-byte attribute", + "query": "(query [:find ?v :where [:big :blob/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ?v]])", + "rows": [ + [ + "1" + ] + ] + }, + { + "name": "1,000-byte keyword value", + "query": "(query [:find ?v :where [:big :blob/kw ?v]])", + "rows": [ + [ + ":k/kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk" + ] + ] + } + ] +} diff --git a/tests/golden/v8_migrated_from_v7.graph b/tests/golden/v8_migrated_from_v7.graph new file mode 100644 index 000000000..f4b04a28f Binary files /dev/null and b/tests/golden/v8_migrated_from_v7.graph differ diff --git a/tests/golden/v8_migrated_from_v7.json b/tests/golden/v8_migrated_from_v7.json new file mode 100644 index 000000000..dce7c0d6d --- /dev/null +++ b/tests/golden/v8_migrated_from_v7.json @@ -0,0 +1,309 @@ +{ + "file": "v8_migrated_from_v7.graph", + "wal": null, + "format": 8, + "written_by": "minigraf v3 branch at 23aa57c (format v8 frozen), tests/golden/gen/v8 recipe migrated_from_v7", + "crc32": { + "graph": "0x66c3671f", + "wal": null + }, + "tx_count": 7, + "notes": "v7_multi_checkpoint.graph migrated in place by the v3 build (v8 spec section 9)", + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "3" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ], + [ + "\"Bob\"", + "false" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "filler count", + "query": "(query [:find (count ?e) :where [?e :filler/n ?n]])", + "rows": [ + [ + "300" + ] + ] + }, + { + "name": "filler group", + "query": "(query [:find (count ?e) :where [?e :filler/group :g/1]])", + "rows": [ + [ + "100" + ] + ] + }, + { + "name": "filler sum", + "query": "(query [:find (sum ?n) :where [?e :filler/n ?n]])", + "rows": [ + [ + "44850" + ] + ] + }, + { + "name": "filler point", + "query": "(query [:find ?g :where [:f/e250 :filler/group ?g]])", + "rows": [ + [ + ":g/1" + ] + ] + }, + { + "name": "filler as-of 5", + "query": "(query [:find (count ?e) :as-of 5 :where [?e :filler/n ?n]])", + "rows": [ + [ + "100" + ] + ] + } + ] +} diff --git a/tests/golden/v8_multi_checkpoint.graph b/tests/golden/v8_multi_checkpoint.graph new file mode 100644 index 000000000..2b161ac03 Binary files /dev/null and b/tests/golden/v8_multi_checkpoint.graph differ diff --git a/tests/golden/v8_multi_checkpoint.json b/tests/golden/v8_multi_checkpoint.json new file mode 100644 index 000000000..390a7e9e2 --- /dev/null +++ b/tests/golden/v8_multi_checkpoint.json @@ -0,0 +1,309 @@ +{ + "file": "v8_multi_checkpoint.graph", + "wal": null, + "format": 8, + "written_by": "minigraf v3 branch at 23aa57c (format v8 frozen), tests/golden/gen/v8 recipe multi_checkpoint", + "crc32": { + "graph": "0xe58f83db", + "wal": null + }, + "tx_count": 7, + "notes": "seven copy-on-write checkpoints: meta slots alternate and freed pages are reused", + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "3" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ], + [ + "\"Bob\"", + "false" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ] + ] + }, + { + "name": "filler count", + "query": "(query [:find (count ?e) :where [?e :filler/n ?n]])", + "rows": [ + [ + "300" + ] + ] + }, + { + "name": "filler group", + "query": "(query [:find (count ?e) :where [?e :filler/group :g/1]])", + "rows": [ + [ + "100" + ] + ] + }, + { + "name": "filler sum", + "query": "(query [:find (sum ?n) :where [?e :filler/n ?n]])", + "rows": [ + [ + "44850" + ] + ] + }, + { + "name": "filler point", + "query": "(query [:find ?g :where [:f/e250 :filler/group ?g]])", + "rows": [ + [ + ":g/1" + ] + ] + }, + { + "name": "filler as-of 5", + "query": "(query [:find (count ?e) :as-of 5 :where [?e :filler/n ?n]])", + "rows": [ + [ + "100" + ] + ] + } + ] +} diff --git a/tests/golden/v8_multivalue.graph b/tests/golden/v8_multivalue.graph new file mode 100644 index 000000000..024521273 Binary files /dev/null and b/tests/golden/v8_multivalue.graph differ diff --git a/tests/golden/v8_multivalue.json b/tests/golden/v8_multivalue.json new file mode 100644 index 000000000..05cde5a76 --- /dev/null +++ b/tests/golden/v8_multivalue.json @@ -0,0 +1,72 @@ +{ + "file": "v8_multivalue.graph", + "wal": null, + "format": 8, + "written_by": "minigraf v3 branch at 23aa57c (format v8 frozen), tests/golden/gen/v8 recipe multivalue", + "crc32": { + "graph": "0xa90d5933", + "wal": null + }, + "tx_count": 3, + "queries": [ + { + "name": "note", + "query": "(query [:find ?n :where [:t/x :note ?n]])", + "rows": [ + [ + "\"two\"" + ] + ] + }, + { + "name": "filler kinds", + "query": "(query [:find (count ?e) :where [?e :kind :k/c]])", + "rows": [ + [ + "30" + ] + ] + }, + { + "name": "multi-value via eavt", + "query": "(query [:find ?v :where [:t/x :kind ?v]])", + "rows": [ + [ + ":k/a" + ], + [ + ":k/b" + ] + ] + }, + { + "name": "multi-value via aevt", + "query": "(query [:find ?v :where [?e :kind ?v] [?e :note \"two\"]])", + "rows": [ + [ + ":k/a" + ], + [ + ":k/b" + ] + ] + }, + { + "name": "batched retract", + "query": "(query [:find ?t :where [:t/y :tag ?t]])", + "rows": [] + }, + { + "name": "multi-value as-of 2", + "query": "(query [:find ?t :as-of 2 :where [:t/y :tag ?t]])", + "rows": [ + [ + ":g/a" + ], + [ + ":g/b" + ] + ] + } + ] +} diff --git a/tests/golden/v8_pending_wal.graph b/tests/golden/v8_pending_wal.graph new file mode 100644 index 000000000..4fee46815 Binary files /dev/null and b/tests/golden/v8_pending_wal.graph differ diff --git a/tests/golden/v8_pending_wal.graph.wal b/tests/golden/v8_pending_wal.graph.wal new file mode 100644 index 000000000..411fb2c42 Binary files /dev/null and b/tests/golden/v8_pending_wal.graph.wal differ diff --git a/tests/golden/v8_pending_wal.json b/tests/golden/v8_pending_wal.json new file mode 100644 index 000000000..0f7714947 --- /dev/null +++ b/tests/golden/v8_pending_wal.json @@ -0,0 +1,279 @@ +{ + "file": "v8_pending_wal.graph", + "wal": "v8_pending_wal.graph.wal", + "format": 8, + "written_by": "minigraf v3 branch at 23aa57c (format v8 frozen), tests/golden/gen/v8 recipe pending_wal", + "crc32": { + "graph": "0xfb128823", + "wal": "0xadf4bcf8" + }, + "tx_count": 6, + "notes": "tx 5 and 6 live only in the v2 WAL: the writing session ended without running destructors, as a crash would", + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ], + [ + "\"Dave\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ], + [ + "\"Dave\"", + "25" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "4" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ], + [ + "\"Dave\"" + ] + ] + }, + { + "name": "bob active as-of 5", + "query": "(query [:find ?b :as-of 5 :where [:bob :person/active ?b]])", + "rows": [ + [ + "false" + ] + ] + } + ] +} diff --git a/tests/golden/v8_stale_wal.graph b/tests/golden/v8_stale_wal.graph new file mode 100644 index 000000000..438e0e99f Binary files /dev/null and b/tests/golden/v8_stale_wal.graph differ diff --git a/tests/golden/v8_stale_wal.graph.wal b/tests/golden/v8_stale_wal.graph.wal new file mode 100644 index 000000000..4f76bf798 Binary files /dev/null and b/tests/golden/v8_stale_wal.graph.wal differ diff --git a/tests/golden/v8_stale_wal.json b/tests/golden/v8_stale_wal.json new file mode 100644 index 000000000..668245c00 --- /dev/null +++ b/tests/golden/v8_stale_wal.json @@ -0,0 +1,280 @@ +{ + "file": "v8_stale_wal.graph", + "wal": "v8_stale_wal.graph.wal", + "format": 8, + "written_by": "minigraf v3 branch at 23aa57c (format v8 frozen), tests/golden/gen/v8 recipe stale_wal", + "crc32": { + "graph": "0xcb871f3e", + "wal": "0x2722a987" + }, + "tx_count": 6, + "notes": "tx 5 and 6 are in the file and also in the v2 WAL, whose entries are all already checkpointed", + "stale_wal": true, + "queries": [ + { + "name": "all names", + "query": "(query [:find ?n :where [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ], + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ], + [ + "\"Dave\"" + ] + ] + }, + { + "name": "ages now", + "query": "(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "31" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ], + [ + "\"Dave\"", + "25" + ] + ] + }, + { + "name": "ages as-of 1", + "query": "(query [:find ?n ?a :as-of 1 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Alice\"", + "30" + ], + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "ages as-of 3 (alice retracted)", + "query": "(query [:find ?n ?a :as-of 3 :where [?e :person/name ?n] [?e :person/age ?a]])", + "rows": [ + [ + "\"Bob\"", + "42" + ], + [ + "\"Carol\"", + "-9223372036854775808" + ] + ] + }, + { + "name": "eavt lookup", + "query": "(query [:find ?a :where [:alice :person/age ?a]])", + "rows": [ + [ + "31" + ] + ] + }, + { + "name": "avet lookup", + "query": "(query [:find ?n :where [?e :person/role :role/admin] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"" + ] + ] + }, + { + "name": "keywords", + "query": "(query [:find ?n ?r :where [?e :person/role ?r] [?e :person/name ?n]])", + "rows": [ + [ + "\"Alice\"", + ":role/admin" + ], + [ + "\"Bob\"", + ":role/user" + ] + ] + }, + { + "name": "ref join", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "ref join two hops", + "query": "(query [:find ?n :where [:alice :person/friend ?f] [?f :person/friend ?g] [?g :person/name ?n]])", + "rows": [ + [ + "\"Carol\"" + ] + ] + }, + { + "name": "vaet reverse ref", + "query": "(query [:find ?n :where [?e :person/friend :carol] [?e :person/name ?n]])", + "rows": [ + [ + "\"Bob\"" + ] + ] + }, + { + "name": "floats", + "query": "(query [:find ?n ?h :where [?e :person/name ?n] [?e :person/height ?h]])", + "rows": [ + [ + "\"Alice\"", + "1.5" + ], + [ + "\"Bob\"", + "-0.25" + ] + ] + }, + { + "name": "unicode", + "query": "(query [:find ?m :where [:alice :person/motto ?m]])", + "rows": [ + [ + "\"Zoë 🚀\"" + ] + ] + }, + { + "name": "i64 max", + "query": "(query [:find ?s :where [:carol :person/score ?s]])", + "rows": [ + [ + "9223372036854775807" + ] + ] + }, + { + "name": "long string", + "query": "(query [:find ?b :where [:carol :person/bio ?b]])", + "rows": [ + [ + "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"" + ] + ] + }, + { + "name": "valid-at inside window", + "query": "(query [:find ?s :valid-at \"2022-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "valid-at earlier window", + "query": "(query [:find ?s :valid-at \"2020-06-01\" :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ] + ] + }, + { + "name": "valid-at before all windows", + "query": "(query [:find ?s :valid-at \"2019-01-01\" :where [?e :employment/status ?s]])", + "rows": [] + }, + { + "name": "valid now", + "query": "(query [:find ?s :where [?e :employment/status ?s]])", + "rows": [ + [ + ":contractor" + ] + ] + }, + { + "name": "any valid time", + "query": "(query [:find ?s :valid-at :any-valid-time :where [?e :employment/status ?s]])", + "rows": [ + [ + ":intern" + ], + [ + ":employee" + ], + [ + ":contractor" + ] + ] + }, + { + "name": "count names", + "query": "(query [:find (count ?e) :where [?e :person/name ?n]])", + "rows": [ + [ + "4" + ] + ] + }, + { + "name": "booleans", + "query": "(query [:find ?n ?b :where [?e :person/name ?n] [?e :person/active ?b]])", + "rows": [ + [ + "\"Alice\"", + "true" + ] + ] + }, + { + "name": "not", + "query": "(query [:find ?n :where [?e :person/name ?n] (not [?e :person/active true])])", + "rows": [ + [ + "\"Bob\"" + ], + [ + "\"Carol\"" + ], + [ + "\"Dave\"" + ] + ] + }, + { + "name": "bob active as-of 5", + "query": "(query [:find ?b :as-of 5 :where [:bob :person/active ?b]])", + "rows": [ + [ + "false" + ] + ] + } + ] +} diff --git a/tests/golden_corpus_test.rs b/tests/golden_corpus_test.rs new file mode 100644 index 000000000..6dc899a03 --- /dev/null +++ b/tests/golden_corpus_test.rs @@ -0,0 +1,269 @@ +//! Golden-file compatibility corpus (#391). +//! +//! Every `tests/golden/*.json` manifest names a committed `.graph` file (and +//! optionally its `.wal`), the CRC32 of each, the expected `tx_count`, and a list of +//! queries with their expected rows. This test opens a copy of each file with the +//! current build and checks the manifest before and after a checkpoint, then after +//! one more write. Golden files are never regenerated, only added: see +//! `tests/golden/README.md`. +#![cfg(not(target_arch = "wasm32"))] + +use std::collections::BTreeSet; +use std::path::{Path, PathBuf}; + +use minigraf::{Minigraf, QueryResult, Value}; +use serde_json::Value as Json; + +/// The major version of this reader, matched against a query's `min_reader`. +const READER: u64 = 3; + +/// The newest file format this reader opens. Manifests for newer formats are skipped. +const MAX_FORMAT: u64 = 8; + +fn golden_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/golden") +} + +fn wal_path(path: &Path) -> PathBuf { + let mut s = path.as_os_str().to_os_string(); + s.push(".wal"); + PathBuf::from(s) +} + +/// Canonical text for one value, as written in manifest `rows`. +fn render(v: &Value) -> String { + match v { + Value::String(s) => serde_json::to_string(s).unwrap(), + Value::Integer(i) => i.to_string(), + Value::Float(f) => f.to_string(), + Value::Boolean(b) => b.to_string(), + Value::Keyword(k) if k.starts_with(':') => k.clone(), + Value::Keyword(k) => format!(":{k}"), + Value::Null => "nil".to_string(), + // Entity ids are not a stable output; manifests never return them. + Value::Ref(_) => "#ref".to_string(), + } +} + +/// True when meta slot A or B (pages 0 and 1) is a valid-looking v8 meta page: +/// `"MGRF"`, version 8, `"META"` (v8 spec §4.1). +fn has_v8_meta(path: &Path) -> bool { + let bytes = std::fs::read(path).unwrap(); + bytes.chunks(4096).take(2).any(|page| { + page.len() >= 12 + && &page[0..4] == b"MGRF" + && page[4..8] == 8u32.to_le_bytes() + && &page[8..12] == b"META" + }) +} + +fn parse_crc(s: &str) -> u32 { + u32::from_str_radix(s.trim_start_matches("0x"), 16).expect("manifest crc32 must be hex") +} + +struct Manifest { + name: String, + json: Json, +} + +fn load_manifests() -> Vec { + let mut out: Vec = std::fs::read_dir(golden_dir()) + .expect("tests/golden must exist") + .map(|e| e.unwrap().path()) + .filter(|p| p.extension().is_some_and(|x| x == "json")) + .map(|p| Manifest { + name: p.file_name().unwrap().to_string_lossy().into_owned(), + json: serde_json::from_slice(&std::fs::read(&p).unwrap()) + .expect("manifest must be valid JSON"), + }) + .collect(); + out.sort_by(|a, b| a.name.cmp(&b.name)); + out +} + +/// Runs every query that applies to this reader and records each mismatch. +fn check_queries(m: &Manifest, db: &Minigraf, phase: &str, failures: &mut Vec) { + for q in m.json["queries"] + .as_array() + .expect("queries must be an array") + { + let qname = q["name"].as_str().expect("query name"); + if q["min_reader"].as_u64().is_some_and(|r| r > READER) { + continue; + } + let expected: BTreeSet> = q["rows"] + .as_array() + .expect("rows must be an array") + .iter() + .map(|row| { + row.as_array() + .expect("row must be an array") + .iter() + .map(|c| c.as_str().expect("cell must be a string").to_string()) + .collect() + }) + .collect(); + let got: BTreeSet> = match db.execute(q["query"].as_str().expect("query")) { + Ok(QueryResult::QueryResults { results, .. }) => results + .iter() + .map(|row| row.iter().map(render).collect()) + .collect(), + Ok(_) => { + failures.push(format!("{} [{phase}] {qname}: not a query result", m.name)); + continue; + } + Err(e) => { + failures.push(format!("{} [{phase}] {qname}: error {}", m.name, e.code())); + continue; + } + }; + if got != expected { + failures.push(format!( + "{} [{phase}] {qname}: expected {expected:?}, got {got:?}", + m.name + )); + } + } +} + +fn check_tx_count(m: &Manifest, db: &Minigraf, want: u64, phase: &str, f: &mut Vec) { + let got = db.current_tx_count(); + if got != want { + f.push(format!( + "{} [{phase}] tx_count: expected {want}, got {got}", + m.name + )); + } +} + +fn check_manifest(m: &Manifest, failures: &mut Vec) { + let dir = golden_dir(); + let file = m.json["file"].as_str().expect("manifest file"); + let src = dir.join(file); + let wal = m.json["wal"].as_str().map(|w| dir.join(w)); + + // 1. Frozen: the committed files match the manifest CRCs. + let src_bytes = std::fs::read(&src).expect("golden file must exist"); + let wal_bytes = wal + .as_ref() + .map(|w| std::fs::read(w).expect("golden WAL must exist")); + let crc = &m.json["crc32"]; + if crc32fast::hash(&src_bytes) != parse_crc(crc["graph"].as_str().expect("crc32.graph")) { + failures.push(format!("{}: {file} does not match its crc32", m.name)); + } + match (&wal_bytes, crc["wal"].as_str()) { + (Some(b), Some(c)) if crc32fast::hash(b) == parse_crc(c) => {} + (None, None) => {} + _ => failures.push(format!("{}: WAL does not match its crc32", m.name)), + } + + // 2. Skip formats this reader cannot open. + if m.json["format"].as_u64().expect("format") > MAX_FORMAT { + return; + } + + // 3. Open a copy; never touch the committed file. + let tmp = tempfile::tempdir().unwrap(); + let path = tmp.path().join(file); + std::fs::write(&path, &src_bytes).unwrap(); + if let Some(b) = &wal_bytes { + std::fs::write(wal_path(&path), b).unwrap(); + } + let tx_count = m.json["tx_count"].as_u64().expect("tx_count"); + { + let db = match Minigraf::open(&path) { + Ok(db) => db, + Err(e) => { + failures.push(format!("{}: open failed with {}", m.name, e.code())); + return; + } + }; + check_tx_count(m, &db, tx_count, "open", failures); + check_queries(m, &db, "open", failures); + } + + // On v3, the first open migrates a v7 file to v8 in place. + if !has_v8_meta(&path) { + failures.push(format!("{}: no v8 meta page after open", m.name)); + } + + // Reopen the migrated copy, then checkpoint. + { + let db = Minigraf::open(&path).unwrap(); + check_tx_count(m, &db, tx_count, "migrated", failures); + check_queries(m, &db, "migrated", failures); + db.checkpoint().unwrap(); + } + + // 4. Persist: reopen after the checkpoint, which removes the WAL. A WAL whose + // entries are all checkpointed already (`"stale_wal": true`) gives the + // checkpoint nothing to do, so it stays until the next write is checkpointed. + let stale_wal = m.json["stale_wal"].as_bool().unwrap_or(false); + if !stale_wal && wal_path(&path).exists() { + failures.push(format!("{}: WAL still present after checkpoint", m.name)); + } + { + let db = Minigraf::open(&path).unwrap(); + check_tx_count(m, &db, tx_count, "reopen", failures); + check_queries(m, &db, "reopen", failures); + + // 5. Write after: the counter moves on from the file's floor (#447). + db.execute("(transact [[:golden/probe :golden/n 1]])") + .unwrap(); + check_tx_count(m, &db, tx_count + 1, "write", failures); + } + { + let db = Minigraf::open(&path).unwrap(); + check_tx_count(m, &db, tx_count + 1, "write-reopen", failures); + match db.execute("(query [:find ?n :where [:golden/probe :golden/n ?n]])") { + Ok(QueryResult::QueryResults { results, .. }) if results.len() == 1 => {} + _ => failures.push(format!("{} [write-reopen]: probe fact missing", m.name)), + } + check_queries(m, &db, "write-reopen", failures); + } + + // 6. Untouched: the committed files are unchanged. + if std::fs::read(&src).unwrap() != src_bytes { + failures.push(format!("{}: committed {file} was modified", m.name)); + } +} + +#[test] +fn golden_corpus() { + let manifests = load_manifests(); + assert!(!manifests.is_empty(), "tests/golden holds no manifests"); + let mut failures = Vec::new(); + for m in &manifests { + check_manifest(m, &mut failures); + } + assert!( + failures.is_empty(), + "golden corpus failures:\n{}", + failures.join("\n") + ); +} + +/// Every golden file has a manifest, so none goes unchecked. +#[test] +fn every_golden_file_has_a_manifest() { + let manifests = load_manifests(); + let named: BTreeSet = manifests + .iter() + .flat_map(|m| { + let mut v = vec![m.json["file"].as_str().unwrap().to_string()]; + if let Some(w) = m.json["wal"].as_str() { + v.push(w.to_string()); + } + v + }) + .collect(); + for entry in std::fs::read_dir(golden_dir()).unwrap() { + let name = entry.unwrap().file_name().to_string_lossy().into_owned(); + if name.ends_with(".graph") || name.ends_with(".wal") { + assert!( + named.contains(&name), + "golden file without a manifest: {name}" + ); + } + } +}