Repository navigation
feat(engine): or and or-join on the streaming engine (#546 PR 2) - #626
Merged
Merged
Conversation
apply_or_clauses' unseeded paths keyed branch rows on exact Values, so a keyword never met a ref to the entity it names, while the seeded path (a branch with not) matched them. Key on normalize_join_value in both; dedup keys stay exact (spec §3.6 item 3, §5.3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
The unseeded path keyed every branch row on the outer variables any branch names, so with (or [?e :a ?x] [?f :b ?x]) no row met an outer row, while the seeded path matched both branches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
) The joined path dropped such a branch's rows; the per-row path, taken when a branch reads outer metadata or an outer variable in an expression, kept them. Take the per-row path for it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
With no :valid-at, an or branch read the wall clock again instead of the snapshot's now, as the rule path already avoids. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
Union runs each branch as a not body is planned: hashed when closed (read once into a table of its new cells by imported-variable JoinKeys), correlated from a Seed otherwise; Distinct follows, over every variable, as the executor's Value keys compare. A query whose first clause to run is an or starts from the empty row. A rule invocation or nested or in a branch, a branch predicate the registry may lack, an or whose branches bind their own sources' metadata, and a branch not reading only what the branch's expression binds stay on the executor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
…est (#546) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This was referenced Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 2 of 2 for #546 (streaming engine part 4 of 9, #473):
orandor-join. Spec:docs/superpowers/specs/2026-10-16-negation-disjunction-engine-design.md§3.5, §3.6 item 3, §5. Plan:docs/superpowers/plans/2026-10-18-546-negation-disjunction.md, steps 10–15 and decision 11, which this PR adds. This does not close #473; #546 is closed by hand after merge.Executor: one answer on every
orpath (breaking)apply_or_clauseshas two paths. The joined path evaluates each branch alone and joins its rows to the outer rows. The per-row path starts each branch from the outer row and is taken when a branch has anot, reads outer metadata, or has an expression over an outer variable. Their answers differed. All of the following now give the per-row (spec) answer:Values, so a keyword never met a ref to the entity it names. With[:alice :friend :bob] [:bob :name "B"],[:find ?e ?n :where [?e :friend ?f] (or [?f :name ?n] [?f :alias ?n])]returned nothing; it now returns:alice "B". Dedup keys stay exact (§5.2).(or [?e :a ?x] [?f :b ?x])matched nothing. Each row now meets the outer row on the variables it binds itself.or-joinbranch that does not bind every join variable lost its rows on the joined path. It now takes the per-row path, so(or-join [?e] [?x :a 1] [?e :b 2])keeps every outer row when some entity has:a 1.:db/valid-atin a branch read the wall clock when the query had no:valid-at. It now reads the query's "now" (branch_valid_at), as the rule path already did.Items 2–4 are not in the spec. I found 2 and 3 while comparing the two paths for this plan, and the oracle found 4. Recorded in plan decision 11.
Engine:
UnionandDistinct(spec §5)or/or-joinrun after the clausesoptimizer::planplaces, in text order, before the deferrednots, as on the executor. A query whose first clause to run is anorstarts from the empty row (Seednow starts with one).notbody (Builder::sub): hashed when closed (read once into a table of its new cells byJoinKeys of the imported variables), otherwise correlated (per outer row from aSeed). A branch's nestednots are placed or deferred as in a query.closednow also requires a nestednotto read only imported variables that the branch's patterns bind.Unionextends each outer row by every row of every branch,BATCH_ROWSat a time. When the branches bind nothing new (everyor-join, and anorthat only filters), it returns an outer row once, from the first branch with a row.Distinctkeeps one row per set of equal rows over every variable, as the executor'sValuekeys compare:0.0 == -0.0, a keyword never equals a ref, and a row holding NaN is never a duplicate.or; a branch predicate the registry may lack (the executor reportsQRY-007only when a row reaches it); anorwhose branches bind their own sources' hidden metadata variables; and a branch whosenotreads a variable only the branch's expression binds.Measurements (#394 envelope, bench machine, base
v3c501f54 vs this PR, two rounds with base and PR on different NUMA nodes, swapped between rounds)or_2=[:find ?e :where [?e :group 7] [?e :age ?a] (or-join [?a] (and [?f :group 17] [?f :age ?a] [(< ?a 30)]) (and [?g :group 27] [?g :age ?a] [(> ?a 60)]))](both branches hashed). p50 of rounds 1 / 2, and peak RSS (round 1):v3v3→ PREvery other workload is unchanged within noise at all three scales. Sub-100 µs shapes flip between about 20 and 50 µs on both builds, as in #602.
recursive_ancestorsat 10M is capped on both.Tests
1588 tests (1578 passing, 10 ignored), up from 1578.
tests/or_equality_test.rs(5 tests): the changed answers, throughexecuteand a cursor, in memory and after a checkpoint.Distinct, and that a hashed branch is read once and a correlated one runs only for rows earlier branches did not match.tests/plan_independence_test.rs: the matrix gains 15 disjunction shapes, run with branches chosen, forced hashed and forced correlated.or/or-joinover bound variables; its reference evaluator deduplicates the rows they keep.I checked that the tests catch a broken operator. A
Distinctthat keeps duplicates, and hashedUnionkeys without keyword/ref normalisation, each fail the oracle test. AUnionthat drops branch rows fails the property test.Found along the way, not fixed here: #625. An expression that reads a variable only an
orbinds drops every row, on both paths and on v2.0.7.Docs site: project-minigraf/minigraf-docs#24
Codex review (read-only, whole diff against the spec and plan): no findings.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz