Skip to content

Disjunction (OR): Strategy B — materialized union - #3

Closed
oflatt-claude wants to merge 2 commits into
mainfrom
disjunction-materialized
Closed

Disjunction (OR): Strategy B — materialized union#3
oflatt-claude wants to merge 2 commits into
mainfrom
disjunction-materialized

Conversation

@oflatt-claude

Copy link
Copy Markdown
Owner

Implements OR disjunction in rule bodies via a materialized union (Strategy B from docs/disjunction-design.md). This is the native, non-rule-splitting implementation.

(rule ((OR ((edge x y))
           ((edge y x))))       ; symmetric closure
      ((connected x y)))

How it works

OR is a real AST fact (GenericFact::Or). Each disjunction is lowered (src/ast/disjunction.rs, before typechecking) to:

  • an internal relation R_or(V) keyed on the branches' common variables V = ⋂ᵢ vars(branchᵢ),
  • one auxiliary rule per branch that inserts V into R_or,
  • the OR in the body replaced by a single atom R_or(V).

The rest of the rule joins R_or as an ordinary relation. Sorts for V are looked up on demand via typecheck_facts, so no core-relations changes are needed — it reuses seminaive evaluation and the worst-case-optimal join unchanged.

Properties vs. rule-splitting: no rule blowup (Σ branches, not ), results deduplicated (a relation is a set). Cost: one extra derivation step of latency, so rules run to a fixpoint.

Semantics: only variables common to every branch may cross the OR boundary (used in the actions or the rest of the body); a branch-local variable used outside is a compile error (OrBranchLocalEscapes). Nested and multiple ORs are handled bottom-up.

Tests

  • tests/disjunction.rs: 9/9 (strategy-independent — run to fixpoint, assert on the DB)
  • full .egg harness: 747/747 (desugar, 32-thread, proof treatments)
  • make nits: clean

Relationship to the other PRs

  • Syntax/semantics/design + rule-splitting reference: the first disjunction PR.
  • Strategy C (native union node in the free-join engine): a separate branch/PR, for comparison.

🤖 Generated with Claude Code

oflatt and others added 2 commits July 1, 2026 20:38
Introduce `(OR (branch) (branch) ...)` in rule queries, where each branch is a
parenthesized list of facts (a conjunctive subquery); the rule matches when any
branch matches. Only variables bound in every branch may appear in the actions.

This commit establishes the surface syntax, semantics, behavioral tests, and a
design doc. The current backend is a parse-time rule-splitting prototype that
distributes a body into the cartesian product of branch choices; per review it
will be replaced by a materialized-union subquery (design doc Strategy B) so the
disjunction is executed natively without rule blowup or redundant firing.

- src/ast/parse.rs: parse and expand `OR`; handle multi-command `fail` expansion
- tests/disjunction.rs: 6 behavioral tests (semantics + rejection cases)
- docs/disjunction-design.md: syntax, semantics, and efficient-execution design
- CHANGELOG.md: feature entry

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the parse-time rule-splitting prototype with a materialized-union
lowering. `OR` is now a real AST fact (`GenericFact::Or`); each disjunction is
compiled to an internal relation keyed on the branches' common variables,
populated by one auxiliary rule per branch, and the `OR` in the body becomes a
single atom over that relation. This avoids rule blowup and redundant firing and
reuses seminaive evaluation and the worst-case-optimal join unchanged; the cost
is one extra derivation step of latency (run to fixpoint).

- egglog-ast: add `GenericFact::Or`; recurse in Display/visit/map/map_symbols
- src/ast/parse.rs: parse `(OR (branch)...)` into `Fact::Or`
- src/ast/disjunction.rs: lower `OR` to relation + per-branch rules; enforce the
  common-variable interface rule; look up sorts via `typecheck_facts`
- src/typechecking.rs: `OrBranchLocalEscapes` / `EmptyOrBranch` errors
- tests/disjunction.rs: strategy-independent behavioral tests (run to fixpoint)
- docs/disjunction-design.md, CHANGELOG.md: document the implemented strategy

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@oflatt-claude

Copy link
Copy Markdown
Owner Author

Superseded by #5, which implements Strategy B as a query-planner materialization (union bag joined via tree decomposition) instead of an egglog→egglog transformation. The egglog→egglog approach here added internal relations/rules and a fixpoint-iteration of latency, which is too slow.

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.

2 participants