Phase 0.2.A: CrossTableFilter — Multi-Table Join Prototype (Node.js)
Labels: testing, joins, cross-table, phase-0.2.A
Architecture approval: ✅ 2026-02-14 (6/6 unanimous)
Proposal: PHASE_0_2_A_v1.1_Proposal.md (full specification)
Estimated effort: ~11.5h (budget 14h)
Motivation
RI currently supports only N:1 gather joins (child→parent lookup). Physics workflows require the reverse: select parents → see their children (1:N), cascade through hierarchies (1:N:M), and traverse junction tables (M:N).
The Full Join Brainstorming (5/5 internal + 6/6 Arch Team) established that all join cardinalities reduce to one new primitive: the inverse index. This phase prototypes and validates it in Node.js before client-side integration.
Consumers: RootInteractive, AliasDataFrame, RDataFrameDSL, GroupBy regression — all need the same primitive.
What
Two core functions + test suite:
buildInverseIndex(fk_array, parent_count, options?)
Maps parent → children. Two variants:
- InverseIndexCSR (unsorted FK):
{offsets, indices} — standard CSR, always correct
- SortedRangeIndex (sorted FK):
{starts, counts} — contiguous ranges, cache-friendly
Both O(N) to build. Sentinels (default -1) excluded. Type-checked input.
CrossTableFilter.compute(inverse_index, selected_parents)
Returns Uint8Array include-mask (1=keep, 0=exclude) over child table. O(selected × avg_children).
AO2D-like test fixture (5 tables)
Collisions (100) → Tracks (5K, sorted FK) → Clusters (50K, sorted FK)
→ V0s (200, multi-ref: posTrackId + negTrackId)
McLabels (5K, junction) ↔ McParticles (10K)
Sentinels: 5% tracks unassigned, 2% McLabels unassigned
Seeded PRNG (seed=42), parameterized sizes.
Test Matrix (15 cases)
| ID |
Test |
Pattern |
| TC-INV-01 |
Build CSR — correctness |
Basic |
| TC-INV-02 |
Build SortedRange — correctness |
Basic |
| TC-INV-03 |
CSR vs SortedRange invariance |
Core invariance |
| TC-INV-04 |
Sentinel exclusion |
Boundary |
| TC-INV-05 |
Empty parent (0 children) |
Boundary |
| TC-CTF-01 |
Single parent selection |
Filter |
| TC-CTF-02 |
Range selection (10 parents) |
Filter |
| TC-CTF-03 |
Select all → all non-sentinel children |
Filter |
| TC-CTF-04 |
Select none → empty mask |
Filter |
| TC-CTF-05 |
3-level cascade (coll→tracks→clusters) |
Cascade |
| TC-CTF-06 |
M:N via junction (tracks↔MC particles) |
Junction |
| TC-CTF-07 |
Multi-reference (V0 pos/neg daughters) |
Multi-ref |
| TC-CTF-08 |
Round-trip invariance |
Invariance |
| TC-PERF-01 |
SortedRange vs CSR performance |
Benchmark |
| TC-COMPAT-01 |
N:1 lookup semantics unchanged |
Backward compat |
Deliverables
test/crossTableFilter/
├── buildInverseIndex.mjs # Core: CSR + SortedRangeIndex
├── CrossTableFilter.mjs # Core: parent selection → child mask
├── ao2d_test_fixture.mjs # 5-table generator
├── test_inverse_index.mjs # TC-INV-01–05
├── test_cross_table_filter.mjs # TC-CTF-01–08
├── test_performance.mjs # TC-PERF-01
├── test_backward_compat.mjs # TC-COMPAT-01
└── README.md
Environment: Node.js ≥ 18. Zero external dependencies.
Not in Scope
- Common Layer 0 descriptor across RI/AliasDataFrame/RDataFrameDSL (Arch Team parallel)
- Bokeh/browser integration (future phase)
- Multi-table HistoNdCDS (Phase 2)
- UI drill-down/breadcrumbs (Phase 4)
- Bidirectional propagation child→parent (Phase 1b)
Definition of Done
Phase 0.2.A: CrossTableFilter — Multi-Table Join Prototype (Node.js)
Labels:
testing,joins,cross-table,phase-0.2.AArchitecture approval: ✅ 2026-02-14 (6/6 unanimous) Proposal:PHASE_0_2_A_v1.1_Proposal.md(full specification) Estimated effort: ~11.5h (budget 14h)Motivation
RI currently supports only N:1 gather joins (child→parent lookup). Physics workflows require the reverse: select parents → see their children (1:N), cascade through hierarchies (1:N:M), and traverse junction tables (M:N).
The Full Join Brainstorming (5/5 internal + 6/6 Arch Team) established that all join cardinalities reduce to one new primitive: the inverse index. This phase prototypes and validates it in Node.js before client-side integration.
Consumers: RootInteractive, AliasDataFrame, RDataFrameDSL, GroupBy regression — all need the same primitive.
What
Two core functions + test suite:
buildInverseIndex(fk_array, parent_count, options?)Maps parent → children. Two variants:
{offsets, indices}— standard CSR, always correct{starts, counts}— contiguous ranges, cache-friendlyBoth O(N) to build. Sentinels (default -1) excluded. Type-checked input.
CrossTableFilter.compute(inverse_index, selected_parents)Returns
Uint8Arrayinclude-mask (1=keep, 0=exclude) over child table. O(selected × avg_children).AO2D-like test fixture (5 tables)
Test Matrix (15 cases)
Deliverables
Environment: Node.js ≥ 18. Zero external dependencies.
Not in Scope
Definition of Done
buildInverseIndexcorrect for CSR and SortedRange (TC-INV-01, 02)node --test