quack/mask-risc: DuckDB parity W-B + W-A — GroupReduce fold, AVG, SUM(CASE), NOT EXISTS, NULL-aware aggregates - #1265
Conversation
…e terminal
T0 fold law, second step: GROUP BY with COUNT / MIN / MAX no longer fans
out to a K-program forest over a materialized kept mask. It lowers to ONE
program ending in Terminal::GroupReduce { mask, key, fold }, writing a
K-slot Out::I64 sink.
mask-risc
- Terminal::GroupReduce, GroupKey::{Lane, Via{fk,key}},
GroupFold::{Count, MinI32, MaxI32} with seed() (0 / i64::MAX / i64::MIN);
Value::GroupReduced. The sink is seeded once, before the first tile.
- exec dispatches the six (address, fold) combinations onto the ndarray
keyed-reduction family (ndarray #320: masked_group_{count_u32,min_i32,
max_i32}{,_via}); reference.rs carries the row-at-a-time oracle arm and
the shared validation (U32 key lanes, I32 value lane, Out::I64 len >= 1).
- tests/foreign.rs: every key x fold against the oracle over n in
{1,63,64,130,1000,5000}, dirty sinks, multi-tile, both hops dropping,
plus the refusal matrix.
quack
- Agg::GroupReduce { key: GroupAddr::{Local, Via}, agg: GroupAgg }.
- lower_group_by_semantic now folds Count/Min/Max as well as SUM; Rows
stays irreducible forest residue. The #1262 test is re-pinned: it now
asserts each fold matches the forest group-by-group, with an empty
MIN/MAX group's OptI32(None) corresponding to the fold seed.
DuckDB harness: 5 new oracle-generated cases (never hand-edited):
group_min_cc, group_max_cc, group_max_cc_sparse (3 empty groups -> SQL
NULL), join_group_count_country and join_group_min_country (key through
l.partner_id, fused). group_count_cc now runs the folded path, with the
K-program forest kept as a comparison METRIC. 21/21 match DuckDB 1.5.5;
the existing 16 expected values are unchanged.
Depends on ndarray #320 (the keyed-reduction family).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG
…ive basis Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Essentials Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (12)
✨ Finishing Touches📝 Generate docstrings
Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_90f99800-fa90-42b1-99dc-5a96b247b740) |
|
Blocker:
The one red check on #320 is a CI cache/CPU mismatch (SIGILL), explained on that PR, and its failed job has been re-run. Once #320 is on ndarray master, this PR's CI re-runs clean with no change here. Generated by Claude Code |
…aware aggregates
Eight new DuckDB-checked cases, none needing a new primitive. Each is an
existing fold under a filter the query composes:
- SUM(CASE WHEN p THEN x ELSE 0 END): the CASE arm is a predicate, so it
moves into the mask and the aggregate is the plain masked SUM
(sum_case_posted_cc3).
- AVG: lower_avg / lower_group_avg lower to two folds over the SAME filter
(SUM, COUNT; or GroupSum*, GroupReduce{Count} keyed Local or Via),
finished by avg_finish outside the hot path (avg_posted, group_avg_cc,
join_group_avg_country). Encoded with {:?}; matches DuckDB's float digits
exactly. Two passes is correct but not minimal: a fused sum+count sink is
a later keyed-reduction member.
- NOT EXISTS: Not around the factored fk predicate, one program, no
partner-side mask (anti_join_not_country3). An fk naming no partner makes
EqU32Via false, so Not keeps the row: NOT EXISTS semantics, not `<>`.
- NULLs: the fixture gains one derived nullable column (line.discount, NULL
on rid % 5 == 3; no RNG draw, so no existing value moves) and its
validity plane. COUNT(col) / SUM(col) / AVG(col) AND that plane into the
filter (count_col_discount_posted, sum_discount_posted,
avg_discount_posted). COUNT(col) is asserted to differ from COUNT(*).
29/29 DuckDB cases match DuckDB 1.5.5 (30 harness tests incl. the drift
guard). The existing 21 expected values are unchanged; line.csv changed only
by the appended column.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_1a289a76-2dbe-4222-a624-f86c77fd7b08) |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_ea8d1609-b47a-4cbf-b655-058ba46a6aaf) |
What
Two parity waves. Every case's expected value is generated by DuckDB, never hand-edited. 29/29 cases match DuckDB 1.5.5, and the original 16 expected values are unchanged.
W-B: fold grouped COUNT/MIN/MAX (commit
219d4a81)Before,
GROUP BYwithCOUNT,MINorMAXran as K+1 programs over an O(N) kept mask. It now runs as one program ending inTerminal::GroupReduce { mask, key, fold }and writing a K-slotOut::I64sink. This is the same T0 fold #1262 applied toSUM.GroupKey::{Lane, Via{fk,key}}andGroupFold::{Count, MinI32, MaxI32}.GroupFold::seed()returns 0,i64::MAXori64::MIN. All three are outside the i32 range, so a slot still at its seed is an empty group, i.e. SQLNULL.Value::GroupReduced.execdispatches onto the ndarray keyed-reduction family;reference.rsgets the oracle arm and the validation.Agg::GroupReduce { key: GroupAddr::{Local, Via}, agg: GroupAgg }.lower_group_by_semanticnow folds Count/Min/Max;Rowsstays forest residue.NULLmatching the seed.group_min_cc,group_max_cc,group_max_cc_sparse(3 empty groups →NULL),join_group_count_countryandjoin_group_min_country.group_count_ccnow runs folded, with the forest kept as a comparison METRIC.W-A: shapes that need no new primitive (commit
cb8062e0)SUM(CASE WHEN p THEN x ELSE 0 END):pgoes into the mask and the aggregate is the plain masked SUM (sum_case_posted_cc3).AVG:lower_avg/lower_group_avgrun SUM and COUNT over the same filter, keyed Local or Via;avg_finishdoes the final divide outside the hot path.avg_posted,group_avg_cc,join_group_avg_country).NOT EXISTS:Notaround the factored fk predicate, as one program with no partner-side mask (anti_join_not_country3).<>.line.discount, with no RNG draw so no existing value moves.COUNT(col),SUM(col)andAVG(col).COUNT(col)is asserted to differ fromCOUNT(*).Verification
lance-graph-mask-riscandlance-graph-quack(30 harness tests, including the fixture drift guard), and for the dependentslance-graph-sap,r2il-mask-abi-probeandd-diamond-1-probe.lgj-abipassescargo check --tests.-D warningsand fmt are clean.NULL.Notdropped; validity plane dropped; NULL written as0.Depends on
AdaWorldAPI/ndarray#320, which adds the keyed-reduction family
masked_group_{count_u32,min_i32,max_i32}{,_via}. It is green and still a draft. CI here syncs ndarray master, soclippy/cats/member-tests/testfail withE0432until #320 merges.Board
.claude/board/entries/2026-09-22-quack-duckdb-parity-t0-keyed-reduction.mdholds:🤖 Generated with Claude Code
https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG