8000 fix(query): run an expression reading an or-bound variable after the ors (#625) by adityamukho · Pull Request #629 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

fix(query): run an expression reading an or-bound variable after the ors (#625) - #629

Merged
adityamukho merged 2 commits into
v3from
fix/625-or-expr
Oct 19, 2026
Merged

adityamukho merged 2 commits into
v3from
fix/625-or-expr

Conversation

@adityamukho
@adityamukho adityamukho commented Oct 19, 2026 •
Copy link
Copy Markdown
Collaborator

Part of #625 (data-integrity known issue; stays open until the v2.x patch ships, then a main backport follows).

Problem

optimizer::plan places expressions among the patterns and nots; or / or-join run after the planned list. An expression whose variable only an or binds was appended to the end of that list, so it ran before ?x was bound and dropped every row:

(transact [[:a :n 1] [:b :n 2] [:c :ok true]])
(query [:find ?e ?x :where [?e :n _] (or [?e :n ?x] [?e :ok ?x]) [(> ?x 1)]])
;; was: No results found.   now: :b 2

Same on the executor, the streaming engine and in rule bodies.

Fix

  • plan_seeded defers an expression that reads a variable no pattern in the list and no expression placed before it binds (seeded variables count as bound). Deferred expressions come first in the deferred list, in text order, ahead of the deferred nots, which may read what they bind.
  • Executor (both the plain and the rules path): the deferred pass after apply_or_clauses now runs Expr too. Expressions cost 0, so the stable cheapest-first sort keeps them ahead of the nots.
  • Rule-body evaluator: applies deferred expressions after its or pass (its deferred was always empty before).
  • Engine: already builds deferred clauses after the Union steps, in the top-level plan and in hashed and correlated bodies. Only the comment changed.

Tests

  • tests/plan_independence_test.rs (+3): the issue's query, a mixed pattern/or expression, a binding-expression chain, a not reading a deferred expression's output, a query calling a rule, and a rule body. Every plan mode on executor and cursor, with the oracle.
  • optimizer unit tests: the deferral itself (replaces test_expr_unbound_var_goes_to_end), chains of placed expressions stay planned, deferred order is exprs then nots.
  • tests/disjunction_test.rs (+2): the same cases without test-oracle, so the coverage job reaches the rule-body path.
  • cargo test: 1592 tests (1582 passing, 10 ignored). --features test-oracle all pass. clippy (default and --all-features) and fmt clean.

Review

Codex (read-only) traced every plan caller and found them consistent. It raised one case, a binding expression placed before an or that binds its input. The parser rejects that with INT-018, so it cannot happen.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz

adityamukho and others added 2 commits October 19, 2026 18:35
…ors (#625)

optimizer::plan places expressions among the patterns and nots; or /
or-join run after the planned list. An expression whose variable only an
or binds was appended to the end of that list, so it ran before the or
bound it and dropped every row -- on the executor, the streaming engine
and in rule bodies.

plan_seeded now defers an expression that reads a variable no pattern in
the list and no expression placed before it binds, ahead of the deferred
nots and in text order. The executor's two deferred passes run such
expressions after apply_or_clauses (expressions cost 0, so the stable
cheapest-first sort keeps them before the nots), the rule-body evaluator
applies them after its or pass, and the engine already builds the
deferred clauses after its Union steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz
…helper

The coverage job runs without test-oracle, so the rule-body branch of the
fix went uncovered (evaluator.rs branches 80.36%, floor 84%).

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

Codecov Report

❌ Patch coverage is 96.77419% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/query/datalog/optimizer.rs 90.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@adityamukho
adityamukho merged commit 764daea into v3 Oct 19, 2026
25 checks passed
@adityamukho
adityamukho deleted the fix/625-or-expr branch October 19, 2026 13:59
adityamukho added a commit that referenced this pull request Oct 19, 2026
…ors (#625, backport) (#630)

Backport of #629 from v3. optimizer::plan appended an expression whose
variable only an or / or-join binds to the end of the planned list, and
the ors run after that list, so the expression saw the variable unbound
and dropped every row, in queries and in rule bodies.

plan now defers an expression that reads a variable no pattern in the
list and no expression placed before it binds, ahead of the deferred
nots and in text order. Both executor deferred passes run it after
apply_or_clauses (via apply_expr_clauses; expressions cost 0, so the
stable cheapest-first sort keeps them first), and the rule-body
evaluator applies it after its or pass.


Claude-Session: https://claude.ai/code/session_01PtCdzbJMSS1V8qEPaJ4jXz

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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