Repository navigation
DDIR: join fragments, a(x, y) * b(y, z), an experimental surface form (spike) - #1085
Open
frankmcsherry wants to merge 3 commits into
Open
frankmcsherry wants to merge 3 commits into
frankmcsherry wants to merge 3 commits into
Conversation
This was referenced Nov 13, 2026
frankmcsherry
force-pushed
the
ddir-join-fragments
branch
from
November 14, 2026 01:30
80cdb20 to
6db526b
Compare
a(x, y) * b(y, z), expanded to ordinary joins (spike)a(x, y) * b(y, z), with one flatmap after the joins (spike)
frankmcsherry
force-pushed
the
ddir-join-fragments
branch
from
November 14, 2026 03:35
6db526b to
81b2249
Compare
a(x, y) * b(y, z), with one flatmap after the joins (spike)a(x, y) * b(y, z), with each test at its earliest join (spike)
…rm (spike) A join fragment is a pipeline source made of atoms joined by `*`. Fragments are an experimental surface form, not part of the plan language or a stable interface: they expand to plain DDP before path resolution and lowering, and no later stage or emitted plan contains them. Each atom names an existing binding and gives a pattern for its key and its value. Shared variables become join keys, and literals, variants and repeated variables become tests. A bare constructor such as `None` means its empty payload; an unknown name is a variable. The rows are every bound variable once, in order of first appearance, as the key, with a unit value; a following `map` builds the head. The expansion is fixed by the text, with no planner. Atoms join in written order, each against its binding's own arrangement: one new arrangement per `*`, of the rows so far, and none at the first `*` when the first two keys are written the same. Every later key must mention a variable bound earlier, so no join moves every row to one constant key; a cross join is written as plain DDP. Key variables are read from the join's key slot. Each literal or equality test is a plain `filter` as soon as its fields are bound, and tests never form arrangements. A variant binds its payload through `enlist unweave` in a `flatmap`, which can also produce the next join's key. When nothing is left to bind, the output layout goes into the last join's lambda. Co-Authored-By: GPT-6 Sol (Codex) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H6wB4H99TLw4tqu4x48pXL
frankmcsherry
force-pushed
the
ddir-join-fragments
branch
from
November 14, 2026 12:39
81b2249 to
374ad91
Compare
a(x, y) * b(y, z), with each test at its earliest join (spike)a(x, y) * b(y, z), an experimental surface form (spike)
…ds one The parser now expands each fragment into plain DDP as soon as it reads it. `Expr::Fragment`, its arms in lowering and the printer, and the separate expansion walk before lowering are gone, and even the parsed view shows the expansion. The expansion itself is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H6wB4H99TLw4tqu4x48pXL
… key restricts nothing When the first two atoms' keys were written the same, the first join used the first atom's arrangement even if that key held tests, so rows the tests reject were paired before being dropped. With `a(Some(x), v) * b(Some(x), w)` and 10,000 `None` keys on each side, that paired every `None` with every `None`: 2.32 s, against 4.4 ms now. Now every `*` forms a new arrangement of the rows so far, except at the first `*` when the first two keys are the same unrestricted variables. A restricted first key (a literal, a variant, a repeated variable) is tested first, and the surviving rows are arranged under the same key values, so they stay on their workers. Tests on the first atom's value still follow a direct first join. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H6wB4H99TLw4tqu4x48pXL
frankmcsherry
force-pushed
the
ddir-join-fragments
branch
from
November 14, 2026 14:44
32ab4db to
ab26688
Compare
frankmcsherry
marked this pull request as ready for review
November 14, 2026 14:45
This branch has not been deployed
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.
A spike, as a draft: a join fragment syntax for DDIR that expands to the joins, filters and arrangements a hand-written plan would use, with each test at the same point in the joins.
Fragments are an experimental surface form, not part of the plan language or a stable interface: the parser expands each one into plain DDP as it reads it, so no syntax tree, later stage or emitted plan contains one, not even the parsed view. They may change or be removed.
Three commits on master-next 8b03a29: the fragments (374ad91), expansion at parse time (eb0f63f), and the direct-first-join rule (ab26688). A text-to-text alternative that kept the core untouched (#1090, closed) was compared; a review preferred this structure with expansion at parse time.
The syntax
let,set,input,importor scope path) and gives a pattern for its key and its value.User(u),Type::Variant(x)) and repeated variables become tests. A bare constructor such asNonemeans its empty payload; a name that is not a constructor is a variable.mapbuilds the head.distinct.The expansion
The plan is fixed by the text; there is no planner.
*, of the rows so far; none at the first*when the first two keys are the same unrestricted variables.a(Some(x), v) * b(Some(x), w)and 10,000Nonekeys on each side, reusinga's arrangement and testing afterwards took 2.32 s; testing first takes 4.4 ms.(),Empty()) would move every incoming row to one key, so it is rejected; a cross join is written as plain DDP.filteras soon as its fields are bound: on the first atom's rows before its first rekey, or right after the join that binds a later field. After a direct first join, the first atom's value tests follow that join. Tested fields leave the stream before the next arrangement.enlist unweavein aflatmap, with noheadand no tag column. Thatflatmapcan produce the next join's key directly.mapstays its own stage.For example,
message(_, (1, who, parent)) * message(parent, (_, to, _))filters comments before rekeying byparent:and
members(doc, Group(g)) * groups(g, m)binds and rekeys in one step:Evidence
Eight programs were rewritten with fragments: SCC, access control, LDBC IC3, IS6, BI11, BI14 and IC13, and the transaction resolver.
placejoins; BI14's message-kind test runs before the parent-key arrangement.cargo test --release -p interactivepasses: 337 passed, 9 ignored, including 26 fragment tests that pin each placement, check each error message, and run a fragment over its own feedback variable inside a loop.Nonebound as a variable, nested[]) are fixed.What remains
map. The author'smapafter a fragment is a separate stage, where hand-written plans often put the head in the last join's lambda. In 30 of the 39 fragments that is one more linear stage after the last join than the original (8 tie, and the demux has one fewer); there is no fusion in this draft.let _call0 = … inbindings.maplists every variable in order; with seven or nine variables that is positional again.Ok(x),Err(e)orNone(), fails because corgi'sinjectneeds the inactive lane's shape; onlySome(x)works. A test records it.[]. An empty list inside a list literal needs its element shape from a sibling constant;[[]]alone is a clear DDIR error.Not intended to land as is.
Implemented by GPT-6 Sol (Codex), with earlier rebase and test additions by Claude Code; reviewed, tested and opened by Claude Code.
🤖 Generated with Claude Code
https://claude.ai/code/session_01H6wB4H99TLw4tqu4x48pXL