Repository navigation
feat(engine): not / not-join on the streaming engine, join equality in negation bodies (#546 PR 1) - #624
Merged
Conversation
…path (#546) not / not-join bodies are matched from a seed of the outer row's imported values (match_patterns_seeded) instead of substituting them as literals, so a float matches by IEEE == and a keyword equals the ref it names on the per-row paths too, as the exclusion-set paths already did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
AntiJoin keeps the outer rows whose body has no row. A closed body runs once and its rows' keys of the imported variables form a hash set; any other body, and one whose first pattern's entity is imported, runs once per outer row from a Seed of the imported cells (Operator::rewind). Bodies are ordered by optimizer::plan_seeded, which counts imported variables as bound. A test-only switch (optimizer::SUBPLANS, minigraf::__subplans under test-oracle) forces either form; the plan-independence and property tests compare both. 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! |
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
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 1 of 2 for #546 (streaming engine part 4 of 9, #473):
notandnot-join. Spec:docs/superpowers/specs/2026-10-16-negation-disjunction-engine-design.md(#614). Plan:docs/superpowers/plans/2026-10-18-546-negation-disjunction.md(added here). PR 2 doesor/or-join. #546 stays open until then. This does not close #473.What changes
Executor: one comparison rule in negation bodies (spec §3.1, §3.6 item 1; breaking). Four places substituted the outer row's values into a
not/not-joinbody as literals, so an imported float matched withinf64::EPSILON:not_body_matches, the rule-querynotpath,evaluate_not_join, and thenotloop inStratifiedEvaluator. They now match the body from a seed of the imported values (seeded_body_rows,match_patterns_seeded), which compares them as joins do: floats by IEEE==, and a keyword equal to the ref of the entity it names. The exclusion-set paths already did this. Example:substitute_pattern/substitute_valuehad no callers left and are removed. The prepared-query substitution is separate code and is unchanged.Engine:
AntiJoin(spec §4).JoinKeys in a hash set.Seedoperator that holds the imported cells. So does a body whose first pattern's entity comes from the outer row: that costs one index probe per row and holds nothing. The newOperator::rewind(seed)restarts the body tree for each row, and the body stops at its first row.optimizer::plan_seeded, which counts imported variables as bound. Deferrednots run last, as on the executor. Anotwith no variables runs right after the first pattern.or(until PR 2).Test switch.
optimizer::SUBPLANS(minigraf::__subplans,test-oracleonly) forces bodies to run hashed or correlated.Deviation from the spec
§4.4 runs a body correlated when its first pattern's entity is a constant or imported. Here only an imported entity makes it correlated. A body with a constant entity, such as
(not [:p1 :ok true]), reads the same range for every outer row, so it is read once and hashed. Recorded in the plan (decision 7).Measurements (#394 envelope, bench machine, base
v3b21708a vs this PR, two rounds with base and PR on different NUMA nodes, swapped between rounds)p50 of rounds 1 / 2, and peak RSS (round 1).
not_probe=[:find ?e :where [?e :group 7] (not [?e :age 7])](correlated);not_join_hash=[:find ?e :where [?e :group 7] [?e :age ?a] (not-join [?a] [?f :group 17] [?f :age ?a] [(< ?a 50)])](hashed; onv3the expression sends it to the executor's per-row path).v3v3→ PRnot_probenot_probenot_probenot_join_hashnot_join_hashnot_join_hashEvery 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).
join_3_scanat 10M: 13.2 / 13.5 s onv3, 13.6 / 13.4 s here.recursive_ancestorsat 10M is capped on both.Tests
1578 tests (1568 passing, 10 ignored), up from 1565.
tests/negation_equality_test.rs(5 tests): the changed answers on every executor path, throughexecuteand a cursor, in memory and after a checkpoint.tests/plan_independence_test.rs: every query now also runs with bodies forced hashed and forced correlated (12 runs per query). The matrix gains streaming engine 4/9: not, not-join, or and Distinct (#473) #546's negation shapes.plan_seededand the switch.I checked that the oracle catches a broken
AntiJoin: inverting either body form's test failsnegation_matches_executor.Docs site: project-minigraf/minigraf-docs#23
Codex review (read-only, whole diff against the spec): no findings; one speculative concern about
plan_seededplacement of pseudo-attribute patterns, which the oracle and plan-independence matrix (pseudo-attributes in bodies, both body forms, reordering on/off) do not reproduce.🤖 Generated with Claude Code
https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz