8000 docs: #181 done in every binding; browser valid-at test flake by adityamukho · Pull Request #742 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **A fact a rule derives has no metadata** (#548). `:db/valid-from`, `:db/valid-to`, `:db/tx-count` and `:db/tx-id` read from a derived fact (`(reach ?x ?y) [?x :reach ?y] [?x :db/tx-count ?c]`) now bind `nil`, and a constant (`[?x :db/tx-count 1]`) never matches one; a stored fact with the same attribute keeps its own. They used to bind the rule evaluator's working values: the time the query ran, forever, and a counter of its own. `:db/valid-at` still binds the query's valid time.
- **A `not` or nested `or` inside an `or` branch sees a variable an expression of the branch binds** (#551). The executor applied a branch's patterns, then its nested `or`s, then its `not`s, then its expressions, so a `not` or nested `or` reading a variable that only an expression of the branch binds saw it unbound. A branch now runs in the order a `:where` does (#625), and the `not` sees the binding. For example, over `[:x :n 1] [:y :n 2] [:z :n 5]`, `[:find ?e :where [?e :n ?v] (or-join [?e ?v] (and [(+ ?v 1) ?w] (not [?f :n ?w])) [?e :n 5])]` returned `:z` (the `not` matched any fact, so the first branch kept no row) and now returns `:y` and `:z`. Only such branches change.
- **An unknown predicate is an error when the query opens, wherever it is** (#551). Every predicate a query names, in an `or` branch (of the query or of a rule it evaluates) or a `not` / `not-join` body, is now looked up when the query opens, as top-level predicates and `:find` functions already were (#414): an unknown one fails with `QRY-007` whatever the data. Before, the executor looked it up only when a row reached it. In an `or` branch, a query whose rows never reached the predicate returned an answer, and now fails. In a `not` / `not-join` body the failure was swallowed: the body had no row, so the `not` kept every outer row and the query returned a wrong answer instead of an error.
- **UniFFI bindings raise API errors as `MiniGrafError.Api`** (#181). Python, Java, Android and Swift reported every `API-` code (a bad argument, a closed object, a read-only write, and the new bind-value codes) as `MiniGrafError.Other`; they now raise the new `MiniGrafError.Api`, with the same message. Code that matched `Other` for these must match `Api`, and an exhaustive `when` (Kotlin) or `switch` (Swift) over `MiniGrafError` needs the new case.
- **Bind-value mistakes are `API` errors** (#181). A missing bind value, a bind slot in an attribute position and a bind value of the wrong kind for its slot were `INT-033`, `INT-034` and `INT-035`, codes for internal faults, although each is the caller's mistake. They are now `API-022`, `API-023` and `API-024`, with the same messages, from `prepare`, `PreparedQuery::execute` and `PreparedQuery::query`. A caller that matched the `INT` codes must match the new ones. `INT-033`–`INT-035` are deprecated (no longer emitted, still registered, never reused).

### Added
Expand All @@ -62,6 +63,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Cursor API: `Minigraf::query()` and `PreparedQuery::query()`** (#432). Both return a `Cursor` that hands a query's rows back in batches: `vars()`, `next_batch(max_rows) -> Result<Option<Batch>, _>`, `close()`, and an iterator over rows. The answer is fixed when the cursor opens, so writes that commit while it is open do not change it. A cursor borrows nothing from the database handle: it can outlive it and move to another thread. For now the answer is still computed when the cursor opens, under the same limits as `execute()`; the API is shaped so the engine can later produce rows as they are pulled without breaking callers. `query()` rejects `transact`, `retract` and `rule` with the new `API-012`, and a query with bind slots with `API-010`. `execute()` is unchanged. Like a fact log, a cursor pins the committed generation it read (#543): checkpoints and `rebuild_indexes()` run while it is open but do not reuse that generation's pages, so on a database taking many writes the file grows by the pages checkpoints replace until the cursor is closed or dropped. `BrowserDb.query()` cursors do the same: the pages they hold keep their bytes in memory and IndexedDB until a later commit after the cursor closes. Rust API only for now; the bindings follow.
- **`Minigraf::verify()` and `Minigraf::rebuild_indexes()`** (#373). Page checksums turn a torn or rotted page into an error, but they cannot see a page that is valid and says the wrong thing. `verify()` walks every committed page and returns an `IntegrityReport` with one structured error per finding: an index that holds different facts than EAVT or a different count than the meta page (new `STG-038`), keys out of order or a page reached twice (new `STG-039`), dictionary maps that disagree (new `STG-040`), ids with no dictionary entry (`STG-036`), and leaked, doubly used or wrongly free pages (`STG-035`). It keeps no per-fact state: indexes are compared by an order-independent digest. `rebuild_indexes()` rebuilds EAVT, AEVT, AVET and VAET from an intact index that agrees with another one (or the only intact one), derives the free list again, and commits like a checkpoint, including uncheckpointed writes; a crash at any point leaves the previous checkpoint. If no index can serve as the source, or the dictionary is damaged, it fails with new `STG-041` and writes nothing. Both are Rust API only for now; the bindings follow.
- **`BrowserDb.prepare`** (#181). The browser binding gets prepared queries: `db.prepare(text)` parses and validates a `(query ...)` once and returns a `BrowserPreparedQuery`, whose `execute(bindings)` returns the JSON `execute` returns and whose `query(bindings)` returns a `BrowserCursor`. Bindings are an object of slot name to a plain value (a boolean; a `number`, an integer when `Number.isInteger` accepts it and a float otherwise; a `bigint`; a string; `null`) or a typed `{ type, value }` form for the other kinds (`float` with no fractional part, `keyword`, `ref`, `entity`, `txCount`, `timestamp`, `anyValidTime`). A value that is not a bind value is a `TypeError`, and a typed value that does not fit its type (a bad UUID, a negative tx count) is `API-017`. The other bindings follow.
- **Prepared queries in every binding** (#181). `prepare(text)` returns a prepared query with `execute(bindings)` (the JSON `execute` returns) and `query(bindings)` (the binding's cursor) in Python, Java, Android, Swift, Node.js and C, as `BrowserDb.prepare` does in the browser. Bindings map slot names (without the `$`) to plain host values (booleans, integers, floats, strings, null/`None`/`nil`, and a UUID type where the language has one, which binds an entity) or to a typed bind value for the other kinds (keyword, ref, entity, tx count, timestamp, any valid time). A prepared query sees the database as it is on each run. Where the binding has no `close` (Python, Java, Android, Swift, browser), a prepared query keeps its database open; in Node.js and C, closing the database releases its prepared queries, so the path can be reopened, and a prepared query used afterwards fails with `API-018`.
- **`BindValue::Entity` and `BindValue::Val(Value::Ref(..))` fit each other's slots** (#181). An entity slot (`[$e :attr ?v]`) takes `Val(Value::Ref(uuid))`, and a value, expression or rule-argument slot takes `Entity(uuid)` as the ref. Before, each was `INT-035` in the other's position, and a rule argument needed `Val(Value::Ref(..))`. A binding can now pass a host UUID without knowing where its slot is.

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ See `docs/TEST_COVERAGE.md` for the full per-file breakdown.

Current plan (see `ROADMAP.md` and the milestones):
- **v2.x** (`main`, format v7) now gets only data-integrity and security fixes as patch releases (v2.0.3 is the first); v2.0.2 was the last planned v2.x release.
- **v3.0.0** (format v8 and data integrity, plus `:limit` #306, query profiler #185, set membership predicates #316 and UniFFI `PreparedQuery` #181, UDF registration in every binding #180; tracker #383) is developed on the long-lived `v3` branch. All v3 PRs target `v3`; `main` stays untouched. CI runs on PRs to `v3` the same as on PRs to `main`. Do not merge `main` into `v3` routinely: `main` only gets backports of fixes already on `v3`. Shared CI and workflow changes land on both branches as separate PRs. Just before the v3.0.0 cut, merge `main` into `v3` once (merge commit; source conflicts resolved to `v3`, `main`'s v2.0.x CHANGELOG sections brought over) so the cut is a fast-forward. At the first v3 release, the current `main` is copied to `v2` and `v3` becomes the new `main`.
- **v3.0.0** (format v8 and data integrity, plus `:limit` #306, query profiler #185, set membership predicates #316 and prepared queries in every binding #181, UDF registration in every binding #180; tracker #383) is developed on the long-lived `v3` branch. All v3 PRs target `v3`; `main` stays untouched. CI runs on PRs to `v3` the same as on PRs to `main`. Do not merge `main` into `v3` routinely: `main` only gets backports of fixes already on `v3`. Shared CI and workflow changes land on both branches as separate PRs. Just before the v3.0.0 cut, merge `main` into `v3` once (merge commit; source conflicts resolved to `v3`, `main`'s v2.0.x CHANGELOG sections brought over) so the cut is a fast-forward. At the first v3 release, the current `main` is copied to `v2` and `v3` becomes the new `main`.
- **v3.next** (the release after v3.0.0) holds new features (lag/lead #182, sliding row frames #183, rule persistence #241, OPFS `BrowserDb` #441).
- Known v2.x data issue: same-transaction multi-valued facts (#371). Fix ships in v3.0.0. All v2.x known issues are listed in pinned issue #421 (`known-issue` label); `data-integrity`/`corruption`/`durability` issues fixed in a v2.x patch stay open until that patch is published; issues fixed on the unreleased `v3` branch close on merge (closing keywords do not fire on `v3`, so close them with `gh issue close`) and stay listed in #421 (CONTRIBUTING.md).
- Support policy and tiers: PHILOSOPHY.md §7 and §10. v2.x gets data-integrity and security fixes for 12 months after v3.0.0. Tier 1 = Rust + Python + browser WASM (from v3.0.0); other bindings are Tier 2.
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Scope — features:

- Query profiler (#185)
- Set membership predicates (#316)
- Prepared queries in every binding (#181; core and `BrowserDb.prepare` done on `v3`, the other bindings follow)
- Prepared queries in every binding (#181; done on `v3`: core, `BrowserDb.prepare`, and Python, Java, Android, Swift, Node.js and C on their `v3` branches)
- UDF registration in every binding: the UniFFI ones, C, Node and the browser `BrowserDb` (#180)
- `OpenOptions` exposed over UniFFI through `open_with_options`, so embedders can suppress the close-time checkpoint (#322; done on `v3` in every binding with #465)

Expand Down
7 changes: 6 additions & 1 deletion src/browser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,12 @@ mod prepared_tests {
),
(
"(query [:find ?x :valid-at $v :where [:a :n ?x]])",
typed("timestamp", Some(JsValue::from_f64(js_sys::Date::now()))),
// A minute ahead: the fact's valid-from is the Rust clock at
// transact, which `Date.now()` can trail.
typed(
"timestamp",
Some(JsValue::from_f64(js_sys::Date::now() + 60_000.0)),
),
),
(
"(query [:find ?x :valid-at $v :where [:a :n ?x]])",
Expand Down
Loading
0