Add :or branch support to the DBSP-standard engine#12
Merged
Conversation
Drops in the spec (specs/dbsp-or.md) and implementation plan (specs/dbsp-or-plan.md) for or-branch support in the standard DBSP engine, then lands T1: tag every triple descriptor returned by compile-pattern and every triple plan node returned by pattern-plan with :kind :triple, and route assemble-pattern through a case-based dispatcher. Silent refactor; existing dbsp_test.clj coverage stays green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
assemble-triple now returns {:stream :handles :leaves} — flat
per-call vectors that an :or branch in a later phase will populate
with multiple entries. plan->circuit concatenates :handles into
:inputs and :leaves into a new top-level :leaves vector. push-deltas!
walks the leaves rather than (:patterns plan), so it no longer
assumes one input per top-level pattern. DbspQuery carries :leaves
alongside :inputs.
Silent refactor — no change to delta output. New assemble-leaves-test
pins the public shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
compile-pattern now recognises [:or branches] alongside [:triple ...]. Each branch is compiled recursively, so a branch can be either a triple (:kind :triple) or a nested or (:kind :or) — nesting is preserved, not flattened. :vars on an :or descriptor follows the encounter order of its first branch (which matches the static engine's contract that all branches share the same free-variable set). Branches of any other clause-type (:and, :not, :predicate, :fn) throw err/unsupported-ex naming the offending clause. The default case of the top-level compile-pattern dispatch is unified with this same unsupported-ex path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pattern-plan now branches on the descriptor's :kind: :triple keeps the existing planning logic; :or recursively plans each branch with the same target so every branch's :out-vars matches the :or block's. A nested :or branch produces a nested :kind :or plan node, mirroring the descriptor tree. The outer plan function is untouched — it only reads :vars from descriptors and :out-vars from plans, both of which are populated uniformly by :triple and :or pattern-plan calls. Assembly for :or plans lands in T5; this commit covers the plan layer in isolation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
assemble-pattern now has an :or arm. assemble-or recursively wires each branch, folds the branch streams left-to-right with PlusOp, and applies DistinctOp on the union to enforce set-union semantics. The returned :handles and :leaves are flat vectors concatenated across all branches in plan order, so nested :or nodes contribute all their leaves to the top-level plan->circuit result. Single-branch :or skips the PlusOp fold (reduce over an empty `rest` returns the initial stream untouched) and just wires Distinct on the sole branch's projection. k-branch :or therefore produces exactly (k - 1) plus operators and exactly one distinct per :or node — and a nested (or A (or B C)) produces two of each, mirroring the descriptor tree, as called out in the spec's open question on redundant distincts. End-to-end query execution under :standard for or-bearing queries is unlocked by this commit; phase-3 tests (T6/T7) cover the runtime behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six new deftests exercising :or through q-inc/transact/consume-delta!: - single-branch (or B) delta matches the bare B delta - two-branch :or returns the union of disjoint branches - overlapping branches collapse via DistinctOp; partial retract emits no delta, full retract emits -1 - :or joined with an outer triple — adds and retracts - 2-var :or joined into a chain (city + or [name | last-name]) - :or as the only :where pattern All pass first run after T5 — the assemble-or wiring composes the existing operators correctly without further fix-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new deftests covering recursive :or assembly at runtime: - (or A (or B C)) produces the same per-transaction delta multiset as (or A B C), verified by replaying the same tx sequence against both - nested :or joined with an outer triple returns the expected rows - 3-level-deep nesting (or A (or B (or C D))) matches the flat form, exercising the recursive descent through assemble-or All pass first run; no implementation change was needed for nested or because compile-pattern, pattern-plan, and assemble-pattern all dispatch on :kind and recurse through :or branches uniformly. Completes the dbsp-or task list (T1–T7); the :standard engine now supports `or` clauses with triple branches and arbitrary nesting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
hooray.dbsp) to support(or …)clauses with triple branches and arbitrary nesting. Implements the spec inspecs/dbsp-or.mdper the plan inspecs/dbsp-or-plan.md.:ornode compiles to aPlusOpchain of its branch streams fed into aDistinctOp. Nested:oris handled recursively (oneDistinctper:ornode, mirroring the descriptor tree) rather than flattened.PlusOp,DistinctOp) — no Kotlin changes, no changes tohooray.query/hooray.incremental/hooray.core.Approach
Seven tasks, each in its own commit:
:kind :triple; routeassemble-patternthrough a:kinddispatcher. Silent refactor.plan->circuitreturns a flat:leavesvector (one entry per leaf triple, carrying:order) parallel to:inputs;push-deltas!walks:leavesinstead of:patterns. Silent refactor.compile-patternrecognises[:or branches], recursively compiles branches (each:kind :tripleor:kind :or), rejects:and/:not/:predicate/:fnbranches witherr/unsupported-ex.pattern-plandispatches on:kind;:orrecursively plans each branch with the same target so every branch's:out-varsmatches the:orblock's.assemble-orwiresPlusOpchain +DistinctOpper:ornode. Single-branch:orskipsPlusOp(onlyDistinct); k-branch:orproducesk-1plus operators and exactly one distinct.:or: single-branch equivalence with bare triple, disjoint union, overlap-collapse via Distinct, outer join, 2-var:orin a chain,or-only query.:or: nested-equals-flat on per-tx delta multisets, nested with outer join, 3-level-deep nesting.Out of scope
Branches inside
:orother than triples (:and,:not, predicates, functions). Those still throwunsupported-exat plan time. The:wcojengine does not yet supportoreither, so no cross-engine equivalence harness was extended — the statichooray.query/queryengine and hand-computed deltas serve as the test oracle.Test plan
./gradlew test— full suite green./gradlew build— cleanorg.hooray.*,hooray.query,hooray.incremental, orhooray.coremodified(binding [h/*dbsp-version* :standard] (h/q-inc node q))foror-bearing queries🤖 Generated with Claude Code