8000 feat(engine): rule queries on the streaming engine (#548 PR 2) by adityamukho · Pull Request #670 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

feat(engine): rule queries on the streaming engine (#548 PR 2) - #670

Merged
adityamukho merged 2 commits into
v3from
feat/548-recursive
Oct 20, 2026
Merged

adityamukho merged 2 commits into
v3from
feat/548-recursive

Conversation

@adityamukho
Copy link
Copy Markdown
Collaborator

Part 6 of #473, second of three PRs for #548 (plan steps 8–15 of docs/superpowers/plans/2026-10-20-548-recursive.md; spec docs/superpowers/specs/2026-10-20-recursive-engine-design.md §5).

What

Rule queries run on the streaming engine.

  • Relations (engine/relation.rs): per evaluated predicate, derived rows of two cells (entity, value), a set by encode_value identity (kinds distinct, keyword ≠ ref, -0.0 ≠ 0.0, one NaN), seeded with the predicate's stored facts. Rows are appended when a round ends, so Old / Delta / Full are ranges of one Vec; an entity index serves probes. Magic seeds are rows.
  • Scans (engine/scan.rs): after the committed and pending ranges, the rows of the relations the pattern names (:p → p; a variable or _ attribute → every relation). Delta reads derived rows only. Derived rows have nil metadata; :db/valid-at binds the query's valid time.
  • Plans (engine/plan.rs): plan_rules collects the evaluated predicates and strata as StratifiedEvaluator does, plans every rule body with the existing Builder (rule calls read as patterns), and plans the query body the same way. For rounds after the first, a rule gets one run per top-level call of its own stratum (that call Delta, earlier ones Old, later ones Full, the delta pattern first). A rule that reads its stratum another way (in an or branch, a :p / variable-attribute pattern, :db/valid-at) is naive: all Full every round.
  • Recursive (engine/recursive.rs): strata in order, rounds to a fixpoint; heads checked against the relation, QRY-013 / INT-022 as on the executor; INT-020 after 1,000 rounds or max_derived_facts derived facts.
  • Routing (DatalogExecutor::open_rule_query): a rule query runs on the engine when its body and every rule body plan, outside a write transaction, without an :as-of timestamp; the executor otherwise. Errors in the executor's order. The oracle compares both.

Size gate

This PR goes over the temporary 1,230 KiB gate, by about 15 KiB on CI's toolchain (+34 KiB locally: 1,247 → 1,281 KiB; v3 was ~1,211 KiB on CI). The growth is the new rule planner, Recursive and the derived scan source; the executor's rule path stays until #551. Approved: gate raised to 1,260 KiB until #551 (binary-size.yml, PHILOSOPHY.md §4).

Measured

#394 envelope at 100K facts, bench machine, one NUMA node, base origin/v3 vs this branch, two alternating runs:

shape p50 peak RSS
recursive_ancestors 2.72 s → 1.40 ms 253 → 4 MiB

Every other shape within run-to-run noise in both orders. 1M/10M and the 5M-derived acceptance test come in PR 3.

Tests

  • tests/recursive_engine_test.rs (10): transitive closure (linear, non-linear), mutual recursion, not/or strata recursing (Recursion through a rule with not / not-join / or returns too few rows #633's cases), naive rules, merged visibility (magic attribute included), identity, nil metadata, heads, both INT-020 limits, rule queries with aggregates/not-join/or/windows; in memory, pending and committed, oracle on.
  • Engine unit tests: routing, derived rows across batches before/after checkpoint; relation.rs identity and ranges. Two routing tests updated: rule calls in not/or now run on the engine.
  • Every existing rule suite now reaches the engine with the oracle.
  • 1,701 tests (1,691 passing, 10 ignored); with test-oracle 1,705 passing.

Codex review: no findings.

Deviations from the plan

  • Head arguments are always variables (PRS-114), so there is no constant-head path; a non-variable head would fall back to the executor.
  • Size gate moved (above), as the plan requires the PR to say first.

Docs site: project-minigraf/minigraf-docs (branch v3/548-recursive), architecture page's streaming-engine section.

Part of #473 and #548 (both closed by hand after PR 3).

🤖 Generated with Claude Code

https://claude.ai/code/session_019kY61Y11gcRD91Y6b7jbGT

adityamukho and others added 2 commits October 21, 2026 01:24
Recursive derives each stratum's relations by semi-naive rounds over id
rows: round 1 reads every relation in full; later rounds run each rule
once per top-level invocation of its stratum (that one Delta, those before
Old, after Full); a rule reading its stratum otherwise runs in full every
round. Scans read derived rows after the committed and pending ranges,
with nil metadata. Rule bodies and the query body plan with the existing
Builder, rule invocations read as patterns. Rule queries route to the
engine unless the executor must run them (write transaction, :as-of
timestamp, a body the engine does not plan). Binary-size gate raised to
1,260 KiB until #551.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kY61Y11gcRD91Y6b7jbGT
@codecov
codecov Bot commented Oct 20, 2026
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.93088% with 22 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/query/engine/recursive.rs 88.50% 10 Missing ⚠️
src/query/engine/scan.rs 88.13% 7 Missing ⚠️
src/query/engine/relation.rs 95.74% 2 Missing ⚠️
src/query/datalog/executor.rs 97.29% 1 Missing ⚠️
src/query/engine/mod.rs 96.87% 1 Missing ⚠️
src/query/engine/plan.rs 99.38% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0