linq: table point-lookup conjunct extraction — where(key==X && residual) probes#3106
Merged
Merged
Conversation
…al) probes extract_key_probe peels the left-assoc && spine: the leftmost conjunct must be the key-equality (keys are unique, so under short-circuit everything right of it runs at most once on both paths — no purity gate needed on the residual); try_table_point_lookup coalesces a leading run of consecutive where_ calls before extraction, and the emission evaluates the residual on the probed element only, routing false to the same miss path. any/count with a residual ride the element probe; residual-first conjunct order still declines to the scan. Bench trio: point_lookup / point_lookup_residual (newly fused) / point_lookup_scan (flipped to residual-first as the decline control). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rges at flatten time try_table_point_lookup is only reached after flatten_linq's collapse_chained_wheres pass, so a leading run of consecutive where_ calls is always ONE call here; the nw-loop generality could never trigger. Restore the original chain-shape match — the conjunct peel in extract_key_probe alone covers both the explicit && spelling and the chained-where spelling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
point_lookup_residual (the pre-stage-B scan shape) probes at 466 ns/op INTERP vs the residual-first scan control's 9967 (~21x); 1270 vs 9049 JIT (~7x) — 0.0 ns/elem in the matrix, same cell as the bare probe. LINQ_TO_TABLE.md status -> stage B, findings recorded, conjunct-extraction deferred edge removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Expands LINQ TableAdapter point-lookup folding so a where(key == X && residual...) predicate can still fold to an O(1) table probe, evaluating residual conjuncts only on the probed element (with a leftmost-key-equality rule to preserve scan semantics/side effects).
Changes:
- Enhances
try_table_point_lookupto extract a leftmostkey == Xconjunct from a compound&&predicate and treat the remaining conjuncts as a post-probe residual filter. - Extends tests and benchmarks to cover residual-true/false behavior, miss-path equivalence, predicate-form
any/count, chainedwheremerging, and residual evaluation count. - Updates user-facing fold-pattern documentation and benchmark result matrices to reflect the new fused shape.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/linq/test_linq_table_source.das | Adds/extends regression tests for residual-conjunct probing semantics and edge cases. |
| doc/source/reference/linq_fold_patterns.rst | Documents residual-conjunct point-lookup folding and the leftmost-conjunct rule. |
| daslib/linq_fold_table.das | Implements conjunct extraction + residual handling in table point-lookup folding. |
| benchmarks/sql/table.das | Adds a residual-probe benchmark and adjusts the scan control to force decline. |
| benchmarks/sql/results.md | Updates benchmark matrix text and incorporates the new benchmark row/results. |
| benchmarks/sql/LINQ_TO_TABLE.md | Updates stage/status notes and records the stage-B findings and semantics. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.
What
Stage B of the table arc: the point-lookup matcher now extracts a key-equality conjunct from a compound
wherepredicate instead of requiring the whole predicate to be a barekey == X.where(kv.key == X && residual)— including the chained spellingwhere(key == X) |> where(residual), whichcollapse_chained_wheresmerges into the same shape at flatten time — folds the whole walk to an O(1) probe that evaluates the residual on the probed element only, routing a false residual to the same miss path the key-miss takes.Semantics rule
The key-equality must be the leftmost conjunct. That positioning is what makes the fold exact, not a simplification:
&&short-circuit every conjunct right of the key-equality runs at most once on both paths (scan and probe) — no invariance/purity gate is needed on the residual. A bump-counter regression test pins this (residual runs exactly once, fused or not).All probe terminators ride it:
first/first_or_default(± trailingselect) take the element probe with the residual check inserted before the hit return; predicate-formany(p)/count(p)with a residual bind the probed element and returnhit && residual/? 1 : 0. Residual-free shapes keep their existing barekey_existsemissions.The second commit removes a where-run coalescing loop the first draft carried:
try_table_point_lookupis only reached aftercollapse_chained_wheres, so the matcher can never see twowhere_entries — the conjunct peel alone covers both spellings.Numbers (m7, 2026-06-11 sweep)
point_lookup_residual— the exact shape that was a full scan before this PR — probes at 466 ns/op INTERP vs the scan control's 9967 (~21×) and 1270 vs 9049 JIT (~7×); it normalizes to 0.0 ns/elem in the results.md matrix, same cell as the bare probe. The bench trio replaces the old pair:point_lookup(bare probe) /point_lookup_residual(newly fused) /point_lookup_scan(flipped to residual-FIRST conjunct order so it remains an honest decline control).Tests
8 new/extended sub-tests in
test_table_point_lookup: hit + residual-true/false, miss,any/countwith residual, three-conjunct chains with trailing select, thekey == 2 && key == 3oddity (probes 2, residual re-check fails — agrees with the scan), keys-lane residual, the two-where spelling, residual-eval-count pinning,firstpanic on a failed residual, and residual-first staying a scan.Gates
utils/lint/main.das --quiet): 0 issues.jitted_scripts), jit_tests 295/295-Wclean, pdflatex pass 1 OK (linq_fold_patterns.rst table-source row updated)🤖 Generated with Claude Code