Skip to main content

parquet/arrow/arrow_reader/selection/
algebra.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5//! Set algebra backing [`RowSelection::and_then`], [`RowSelection::intersection`]
6//! and [`RowSelection::union`]
7//!
8//! Each operation has two implementations, picked by the backing of its
9//! operands: a merge of the [`RowSelector`] runs, and a bitwise variant over
10//! [`BooleanBuffer`] masks.
11
12use super::boolean::{MaskSelection, boolean_mask_from_selectors};
13use super::{MaskRunIter, RowSelection, RowSelectionInner, RowSelector};
14use arrow_buffer::{BooleanBuffer, BooleanBufferBuilder, MutableBuffer, bit_util};
15use std::cmp::Ordering;
16use std::iter::Peekable;
17
18// Use word-at-a-time expansion for masks with at least 8192 rows, roughly
19// 75% outer selectivity and 5% inner selectivity. Smaller or sparser inputs
20// use set indices to avoid scanning every output word.
21// In `outer.and_then(inner)`, outer selects from the original rows, and inner
22// selects from those selected rows: inner.len() == outer.count_set_bits().
23// The selectivity constants are divisors: at most 1/4 dropped, at least 1/20 kept.
24const AND_THEN_DENSE_MASK_MIN_LEN: usize = 8192;
25const AND_THEN_DENSE_MASK_MAX_DROPPED_FRACTION: usize = 4;
26const AND_THEN_DENSE_MASK_MIN_INNER_FRACTION: usize = 20;
27
28/// Applies `second` to the rows selected by `first`, both selector-backed.
29pub(super) fn and_then_row_selections(
30    first: &[RowSelector],
31    second: &[RowSelector],
32) -> RowSelection {
33    let mut selectors = vec![];
34    let mut first = first.iter().copied().peekable();
35    let mut second = second.iter().copied().peekable();
36    and_then_iter(&mut selectors, &mut first, &mut second);
37    RowSelection::from_selectors(selectors)
38}
39
40/// Applies the mask `second` to the rows selected by the selector-backed `first`.
41///
42/// The mask is streamed as [`RowSelector`] runs, so it is never materialized.
43pub(super) fn and_then_selectors_with_mask(
44    first: &[RowSelector],
45    second: &BooleanBuffer,
46) -> RowSelection {
47    let mut selectors = vec![];
48    let mut first = first.iter().copied().peekable();
49    let mut second = MaskRunIter::new(second).peekable();
50    and_then_iter(&mut selectors, &mut first, &mut second);
51    RowSelection::from_selectors(selectors)
52}
53
54fn and_then_iter<I, J>(
55    selectors: &mut Vec<RowSelector>,
56    first: &mut Peekable<I>,
57    second: &mut Peekable<J>,
58) where
59    I: Iterator<Item = RowSelector>,
60    J: Iterator<Item = RowSelector>,
61{
62    let mut to_skip = 0;
63    while let Some(b) = second.peek_mut() {
64        let a = first
65            .peek_mut()
66            .expect("selection exceeds the number of selected rows");
67
68        if b.row_count == 0 {
69            second.next().unwrap();
70            continue;
71        }
72
73        if a.row_count == 0 {
74            first.next().unwrap();
75            continue;
76        }
77
78        if a.skip {
79            // Records were skipped when producing second
80            to_skip += a.row_count;
81            first.next().unwrap();
82            continue;
83        }
84
85        let skip = b.skip;
86        let to_process = a.row_count.min(b.row_count);
87
88        a.row_count -= to_process;
89        b.row_count -= to_process;
90
91        match skip {
92            true => to_skip += to_process,
93            false => {
94                if to_skip != 0 {
95                    selectors.push(RowSelector::skip(to_skip));
96                    to_skip = 0;
97                }
98                selectors.push(RowSelector::select(to_process))
99            }
100        }
101    }
102
103    for v in first {
104        if v.row_count != 0 {
105            assert!(
106                v.skip,
107                "selection contains less than the number of selected rows"
108            );
109            to_skip += v.row_count
110        }
111    }
112
113    if to_skip != 0 {
114        selectors.push(RowSelector::skip(to_skip));
115    }
116}
117
118/// Combine two lists of `RowSelection` return the intersection of them
119/// For example:
120/// self:      NNYYYYNNYYNYN
121/// other:     NYNNNNNNY
122///
123/// returned:  NNNNNNNNYYNYN
124pub(super) fn intersect_row_selections(
125    left: &[RowSelector],
126    right: &[RowSelector],
127) -> RowSelection {
128    combine_row_selections(left, right, |l, r| l || r)
129}
130
131/// Combine two lists of `RowSelector` return the union of them
132/// For example:
133/// self:      NNYYYYNNYYNYN
134/// other:     NYNNNNNNY
135///
136/// returned:  NYYYYYNNYYNYN
137///
138/// This can be removed from here once RowSelection::union is in parquet::arrow
139pub(super) fn union_row_selections(left: &[RowSelector], right: &[RowSelector]) -> RowSelection {
140    combine_row_selections(left, right, |l, r| l && r)
141}
142
143/// Combines overlapping selector runs, passing the longer side's tail through unchanged.
144fn combine_row_selections(
145    left: &[RowSelector],
146    right: &[RowSelector],
147    combine_skip: impl Fn(bool, bool) -> bool,
148) -> RowSelection {
149    let mut l_iter = left.iter().copied().peekable();
150    let mut r_iter = right.iter().copied().peekable();
151
152    let iter = std::iter::from_fn(move || {
153        loop {
154            let l = l_iter.peek_mut();
155            let r = r_iter.peek_mut();
156
157            match (l, r) {
158                (Some(a), _) if a.row_count == 0 => {
159                    l_iter.next().unwrap();
160                }
161                (_, Some(b)) if b.row_count == 0 => {
162                    r_iter.next().unwrap();
163                }
164                (Some(l), Some(r)) => {
165                    let skip = combine_skip(l.skip, r.skip);
166                    let row_count = l.row_count.min(r.row_count);
167
168                    if l.row_count < r.row_count {
169                        r.row_count -= row_count;
170                        l_iter.next();
171                    } else {
172                        l.row_count -= row_count;
173                        r_iter.next();
174                    }
175
176                    return Some(RowSelector { row_count, skip });
177                }
178                (Some(_), None) => return l_iter.next(),
179                (None, Some(_)) => return r_iter.next(),
180                (None, None) => return None,
181            }
182        }
183    });
184
185    iter.collect()
186}
187
188/// Combines a mask and selectors without expanding the mask into runs.
189pub(super) fn combine_mixed_selection(
190    selectors: &[RowSelector],
191    mask: &MaskSelection,
192    op: impl FnOnce(&BooleanBuffer, &BooleanBuffer) -> BooleanBuffer,
193) -> RowSelection {
194    // An empty operand leaves the other unchanged.
195    if selectors.is_empty() {
196        return RowSelection::from_mask_selection(mask.clone());
197    }
198    if mask.mask().is_empty() {
199        return RowSelection::from_selectors(selectors.to_vec());
200    }
201    let selectors = boolean_mask_from_selectors(selectors);
202    RowSelection::from_boolean_buffer(op(mask.mask(), &selectors))
203}
204
205/// Bitwise AND of two mask-backed selections. Longer side's tail passes through.
206pub(super) fn intersect_masks(l: &BooleanBuffer, r: &BooleanBuffer) -> BooleanBuffer {
207    if l.len() == r.len() {
208        return combine_equal_length_masks(l, r, |a, b| a & b);
209    }
210    combine_unequal_length_masks(l, r, |a, b| a & b)
211}
212
213/// Bitwise OR of two mask-backed selections. Longer side's tail passes through.
214pub(super) fn union_masks(l: &BooleanBuffer, r: &BooleanBuffer) -> BooleanBuffer {
215    if l.len() == r.len() {
216        return combine_equal_length_masks(l, r, |a, b| a | b);
217    }
218    combine_unequal_length_masks(l, r, |a, b| a | b)
219}
220
221/// Combines two masks of equal length with the bitwise operation `op`.
222///
223/// `BitAnd`/`BitOr` on `&BooleanBuffer` normalise the result to a zero bit offset,
224/// which costs a second allocation and a shifting copy when both operands
225/// share the same non-zero sub-64-bit alignment, causing
226/// `from_bitwise_binary_op` to return a non-zero-offset result. Building the
227/// buffer directly keeps the offset, as the unequal-length path does.
228fn combine_equal_length_masks<F>(l: &BooleanBuffer, r: &BooleanBuffer, op: F) -> BooleanBuffer
229where
230    F: FnMut(u64, u64) -> u64,
231{
232    BooleanBuffer::from_bitwise_binary_op(
233        l.values(),
234        l.offset(),
235        r.values(),
236        r.offset(),
237        l.len(),
238        op,
239    )
240}
241
242/// Combines two masks of unequal length with the bitwise operation `op`,
243/// passing the longer side's tail through unchanged.
244///
245/// The longer mask is copied once into a [`MutableBuffer`] and `op` is then
246/// applied in place over the common prefix. This avoids materialising the
247/// prefix into its own buffer and copying both prefix and tail again through a
248/// [`BooleanBufferBuilder`].
249///
250/// Neither the mask offsets nor the prefix length are assumed to be byte
251/// aligned: the copy keeps the longer mask's offset within its first byte so it
252/// stays a plain byte copy, and that offset is carried over to the result. Only
253/// the longer mask's own byte range is copied, so the result does not retain the
254/// backing allocation it was sliced from.
255fn combine_unequal_length_masks<F>(l: &BooleanBuffer, r: &BooleanBuffer, op: F) -> BooleanBuffer
256where
257    F: FnMut(u64, u64) -> u64,
258{
259    let (longer, shorter) = if l.len() > r.len() { (l, r) } else { (r, l) };
260
261    let sub_byte_offset = longer.offset() % 8;
262    let start_byte = longer.offset() / 8;
263    let end_byte = bit_util::ceil(longer.offset() + longer.len(), 8);
264    let bytes = &longer.values()[start_byte..end_byte];
265    let mut buffer = MutableBuffer::new(bytes.len());
266    buffer.extend_from_slice(bytes);
267
268    bit_util::apply_bitwise_binary_op(
269        buffer.as_slice_mut(),
270        sub_byte_offset,
271        shorter.values(),
272        shorter.offset(),
273        shorter.len(),
274        op,
275    );
276
277    BooleanBuffer::new(buffer.into(), sub_byte_offset, longer.len())
278}
279
280/// Applies `other` to the selected rows of `mask`, preserving the original row domain.
281pub(super) fn and_then_mask(mask: &BooleanBuffer, other: &RowSelection) -> BooleanBuffer {
282    match &other.inner {
283        RowSelectionInner::Mask(other_mask) => and_then_masks(mask, other_mask.mask()),
284        RowSelectionInner::Selectors(selectors) => {
285            and_then_mask_from_selectors(mask, selectors.iter().copied())
286        }
287    }
288}
289
290fn and_then_mask_from_selectors<I>(mask: &BooleanBuffer, other: I) -> BooleanBuffer
291where
292    I: IntoIterator<Item = RowSelector>,
293{
294    let mut builder = BooleanBufferBuilder::new(mask.len());
295    let mut other_iter = other.into_iter();
296    let mut current = other_iter.next();
297    let mut cursor = 0usize;
298
299    // Iterate only over the set positions in `mask`; the gaps of unset bits
300    // are filled in bulk with `append_n` instead of bit-by-bit.
301    for set_idx in mask.set_indices() {
302        if set_idx > cursor {
303            builder.append_n(set_idx - cursor, false);
304        }
305        cursor = set_idx + 1;
306
307        while current.as_ref().is_some_and(|s| s.row_count == 0) {
308            current = other_iter.next();
309        }
310        let selector = current
311            .as_mut()
312            .expect("selection contains less than the number of selected rows");
313        let selected = !selector.skip;
314        selector.row_count -= 1;
315        builder.append(selected);
316    }
317    if cursor < mask.len() {
318        builder.append_n(mask.len() - cursor, false);
319    }
320
321    if current.is_some_and(|s| s.row_count != 0) || other_iter.any(|s| s.row_count != 0) {
322        panic!("selection exceeds the number of selected rows");
323    }
324
325    builder.finish()
326}
327
328fn and_then_masks(mask: &BooleanBuffer, other: &BooleanBuffer) -> BooleanBuffer {
329    let selected_count = mask.count_set_bits();
330    match other.len().cmp(&selected_count) {
331        Ordering::Less => panic!("selection contains less than the number of selected rows"),
332        Ordering::Greater => panic!("selection exceeds the number of selected rows"),
333        Ordering::Equal => {}
334    }
335
336    let other_true_count = other.count_set_bits();
337    if other_true_count == 0 {
338        return BooleanBuffer::new_unset(mask.len());
339    }
340    if other_true_count == selected_count {
341        return mask.clone();
342    }
343
344    if should_use_dense_mask(mask.len(), selected_count, other_true_count) {
345        and_then_dense_masks(mask, other)
346    } else {
347        and_then_sparse_masks(mask, other)
348    }
349}
350
351/// Checks the length and selectivity thresholds for dense expansion.
352#[inline]
353fn should_use_dense_mask(mask_len: usize, selected_count: usize, other_true_count: usize) -> bool {
354    mask_len >= AND_THEN_DENSE_MASK_MIN_LEN
355        && mask_len - selected_count <= mask_len.div_ceil(AND_THEN_DENSE_MASK_MAX_DROPPED_FRACTION)
356        && other_true_count >= selected_count / AND_THEN_DENSE_MASK_MIN_INNER_FRACTION
357}
358
359/// Maps each set bit in `other` to the corresponding set bit in `mask`.
360/// Requires `other.len() == mask.count_set_bits()`.
361fn and_then_sparse_masks(mask: &BooleanBuffer, other: &BooleanBuffer) -> BooleanBuffer {
362    debug_assert_eq!(other.len(), mask.count_set_bits());
363
364    let mut builder = BooleanBufferBuilder::new(mask.len());
365    let mut outer_set_indices = mask.set_indices();
366    let mut next_selected_ordinal = 0usize;
367    let mut cursor = 0usize;
368
369    for selected_ordinal in other.set_indices() {
370        let skip = selected_ordinal - next_selected_ordinal;
371        let set_idx = outer_set_indices
372            .nth(skip)
373            .expect("validated other length matches selected row count");
374        if set_idx > cursor {
375            builder.append_n(set_idx - cursor, false);
376        }
377        builder.append(true);
378        cursor = set_idx + 1;
379        next_selected_ordinal = selected_ordinal + 1;
380    }
381
382    if cursor < mask.len() {
383        builder.append_n(mask.len() - cursor, false);
384    }
385
386    builder.finish()
387}
388
389/// Scatters the next `mask_word.count_ones()` bits of `other` into each mask word.
390/// Requires `other.len() == mask.count_set_bits()`.
391#[inline(never)]
392fn and_then_dense_masks(mask: &BooleanBuffer, other: &BooleanBuffer) -> BooleanBuffer {
393    debug_assert_eq!(other.len(), mask.count_set_bits());
394
395    let mut other_chunks = other.bit_chunks().iter_padded();
396    let mut other_remaining = other.len();
397    let mut pending = 0_u128;
398    let mut pending_len = 0;
399    let mut output = MutableBuffer::with_capacity(mask.len().div_ceil(8));
400
401    for mask_word in mask
402        .bit_chunks()
403        .iter_padded()
404        .take(mask.len().div_ceil(64))
405    {
406        let selected = mask_word.count_ones() as usize;
407        while pending_len < selected {
408            let chunk = other_chunks
409                .next()
410                .expect("validated other length matches selected row count");
411            let chunk_len = other_remaining.min(64);
412            pending |= (chunk as u128) << pending_len;
413            pending_len += chunk_len;
414            other_remaining -= chunk_len;
415        }
416
417        let values = pending as u64;
418        output.extend_from_slice(&bit_util::expand(values, mask_word).to_le_bytes());
419        pending >>= selected;
420        pending_len -= selected;
421    }
422
423    debug_assert_eq!(other_remaining, 0);
424    debug_assert_eq!(pending_len, 0);
425    output.truncate(mask.len().div_ceil(8));
426    BooleanBuffer::new(output.into(), 0, mask.len())
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432    use arrow_array::BooleanArray;
433    use rand::{RngExt, SeedableRng, rng, rngs::StdRng};
434
435    #[test]
436    fn test_and() {
437        let mut a = RowSelection::from(vec![
438            RowSelector::skip(12),
439            RowSelector::select(23),
440            RowSelector::skip(3),
441            RowSelector::select(5),
442        ]);
443
444        let b = RowSelection::from(vec![
445            RowSelector::select(5),
446            RowSelector::skip(4),
447            RowSelector::select(15),
448            RowSelector::skip(4),
449        ]);
450
451        let mut expected = RowSelection::from(vec![
452            RowSelector::skip(12),
453            RowSelector::select(5),
454            RowSelector::skip(4),
455            RowSelector::select(14),
456            RowSelector::skip(3),
457            RowSelector::select(1),
458            RowSelector::skip(4),
459        ]);
460
461        assert_eq!(a.and_then(&b), expected);
462
463        a.split_off(7);
464        expected.split_off(7);
465        assert_eq!(a.and_then(&b), expected);
466
467        let a = RowSelection::from(vec![RowSelector::select(5), RowSelector::skip(3)]);
468
469        let b = RowSelection::from(vec![
470            RowSelector::select(2),
471            RowSelector::skip(1),
472            RowSelector::select(1),
473            RowSelector::skip(1),
474        ]);
475
476        assert_eq!(
477            a.and_then(&b).selectors(),
478            vec![
479                RowSelector::select(2),
480                RowSelector::skip(1),
481                RowSelector::select(1),
482                RowSelector::skip(4)
483            ]
484        );
485    }
486
487    #[test]
488    #[should_panic(expected = "selection exceeds the number of selected rows")]
489    fn test_and_longer() {
490        let a = RowSelection::from(vec![
491            RowSelector::select(3),
492            RowSelector::skip(33),
493            RowSelector::select(3),
494            RowSelector::skip(33),
495        ]);
496        let b = RowSelection::from(vec![RowSelector::select(36)]);
497        a.and_then(&b);
498    }
499
500    #[test]
501    #[should_panic(expected = "selection contains less than the number of selected rows")]
502    fn test_and_shorter() {
503        let a = RowSelection::from(vec![
504            RowSelector::select(3),
505            RowSelector::skip(33),
506            RowSelector::select(3),
507            RowSelector::skip(33),
508        ]);
509        let b = RowSelection::from(vec![RowSelector::select(3)]);
510        a.and_then(&b);
511    }
512
513    #[test]
514    fn test_intersect_row_selection_and_combine() {
515        // a size equal b size
516        let a = vec![
517            RowSelector::select(5),
518            RowSelector::skip(4),
519            RowSelector::select(1),
520        ];
521        let b = vec![
522            RowSelector::select(8),
523            RowSelector::skip(1),
524            RowSelector::select(1),
525        ];
526
527        let res = intersect_row_selections(&a, &b);
528        assert_eq!(
529            res.selectors(),
530            vec![
531                RowSelector::select(5),
532                RowSelector::skip(4),
533                RowSelector::select(1),
534            ],
535        );
536
537        // a size larger than b size
538        let a = vec![
539            RowSelector::select(3),
540            RowSelector::skip(33),
541            RowSelector::select(3),
542            RowSelector::skip(33),
543        ];
544        let b = vec![RowSelector::select(36), RowSelector::skip(36)];
545        let res = intersect_row_selections(&a, &b);
546        assert_eq!(
547            res.selectors(),
548            vec![RowSelector::select(3), RowSelector::skip(69)]
549        );
550
551        // a size less than b size
552        let a = vec![RowSelector::select(3), RowSelector::skip(7)];
553        let b = vec![
554            RowSelector::select(2),
555            RowSelector::skip(2),
556            RowSelector::select(2),
557            RowSelector::skip(2),
558            RowSelector::select(2),
559        ];
560        let res = intersect_row_selections(&a, &b);
561        assert_eq!(
562            res.selectors(),
563            vec![RowSelector::select(2), RowSelector::skip(8)]
564        );
565
566        let a = vec![RowSelector::select(3), RowSelector::skip(7)];
567        let b = vec![
568            RowSelector::select(2),
569            RowSelector::skip(2),
570            RowSelector::select(2),
571            RowSelector::skip(2),
572            RowSelector::select(2),
573        ];
574        let res = intersect_row_selections(&a, &b);
575        assert_eq!(
576            res.selectors(),
577            vec![RowSelector::select(2), RowSelector::skip(8)]
578        );
579    }
580
581    #[test]
582    #[cfg_attr(miri, ignore)] // Takes too long
583    fn test_and_fuzz() {
584        let mut rand = rng();
585        for _ in 0..100 {
586            let a_len = rand.random_range(10..100);
587            let a_bools: Vec<_> = (0..a_len).map(|_| rand.random_bool(0.2)).collect();
588            let a = RowSelection::from_filters(&[BooleanArray::from(a_bools.clone())]);
589
590            let b_len: usize = a_bools.iter().map(|x| *x as usize).sum();
591            let b_bools: Vec<_> = (0..b_len).map(|_| rand.random_bool(0.8)).collect();
592            let b = RowSelection::from_filters(&[BooleanArray::from(b_bools.clone())]);
593
594            let mut expected_bools = vec![false; a_len];
595
596            let mut iter_b = b_bools.iter();
597            for (idx, b) in a_bools.iter().enumerate() {
598                if *b && *iter_b.next().unwrap() {
599                    expected_bools[idx] = true;
600                }
601            }
602
603            let expected = RowSelection::from_filters(&[BooleanArray::from(expected_bools)]);
604
605            assert_eq!(a_len, expected.total_row_count());
606
607            assert_eq!(a.and_then(&b), expected);
608        }
609    }
610
611    #[test]
612    fn test_intersection() {
613        let selection = RowSelection::from(vec![RowSelector::select(1048576)]);
614        let result = selection.intersection(&selection);
615        assert_eq!(result, selection);
616
617        let a = RowSelection::from(vec![
618            RowSelector::skip(10),
619            RowSelector::select(10),
620            RowSelector::skip(10),
621            RowSelector::select(20),
622        ]);
623
624        let b = RowSelection::from(vec![
625            RowSelector::skip(20),
626            RowSelector::select(20),
627            RowSelector::skip(10),
628        ]);
629
630        let result = a.intersection(&b);
631        assert_eq!(
632            result.selectors(),
633            vec![
634                RowSelector::skip(30),
635                RowSelector::select(10),
636                RowSelector::skip(10)
637            ]
638        );
639    }
640
641    #[test]
642    fn test_union() {
643        let selection = RowSelection::from(vec![RowSelector::select(1048576)]);
644        let result = selection.union(&selection);
645        assert_eq!(result, selection);
646
647        // NYNYY
648        let a = RowSelection::from(vec![
649            RowSelector::skip(10),
650            RowSelector::select(10),
651            RowSelector::skip(10),
652            RowSelector::select(20),
653        ]);
654
655        // NNYYNYN
656        let b = RowSelection::from(vec![
657            RowSelector::skip(20),
658            RowSelector::select(20),
659            RowSelector::skip(10),
660            RowSelector::select(10),
661            RowSelector::skip(10),
662        ]);
663
664        let result = a.union(&b);
665
666        // NYYYYYN
667        assert_eq!(
668            result.iter().copied().collect::<Vec<_>>(),
669            vec![
670                RowSelector::skip(10),
671                RowSelector::select(50),
672                RowSelector::skip(10),
673            ]
674        );
675    }
676
677    #[test]
678    fn test_mask_and_then_preserves_backing() {
679        let outer_bits = vec![false, true, true, false, true, false, true];
680        let inner_bits = vec![true, false, true, false];
681        let outer_mask = RowSelection::from_boolean_buffer(BooleanBuffer::from(outer_bits.clone()));
682        let inner = RowSelection::from_filters(&[BooleanArray::from(inner_bits.clone())]);
683
684        let result = outer_mask.and_then(&inner);
685        assert!(result.as_mask().is_some());
686
687        let outer_selectors = RowSelection::from_filters(&[BooleanArray::from(outer_bits)]);
688        let expected = outer_selectors.and_then(&inner);
689        assert_eq!(result, expected);
690
691        let result_mask = result.as_mask().unwrap();
692        let actual_bits: Vec<_> = (0..result_mask.len())
693            .map(|i| result_mask.value(i))
694            .collect();
695        assert_eq!(
696            actual_bits,
697            vec![false, true, false, false, true, false, false]
698        );
699    }
700
701    #[test]
702    fn test_mask_and_then_mask_preserves_backing() {
703        let outer_bits = vec![false, true, true, false, true, false, true, true];
704        let inner_bits = vec![false, true, false, true, false];
705        let outer_mask = RowSelection::from_boolean_buffer(BooleanBuffer::from(outer_bits.clone()));
706        let inner_mask = RowSelection::from_boolean_buffer(BooleanBuffer::from(inner_bits));
707
708        let result = outer_mask.and_then(&inner_mask);
709        assert!(result.as_mask().is_some());
710
711        let outer_selectors = RowSelection::from_filters(&[BooleanArray::from(outer_bits)]);
712        let inner_selectors = RowSelection::from_filters(&[BooleanArray::from(vec![
713            false, true, false, true, false,
714        ])]);
715        assert_eq!(result, outer_selectors.and_then(&inner_selectors));
716
717        let result_mask = result.as_mask().unwrap();
718        let actual_bits: Vec<_> = (0..result_mask.len())
719            .map(|i| result_mask.value(i))
720            .collect();
721        assert_eq!(
722            actual_bits,
723            vec![false, false, true, false, false, false, true, false]
724        );
725    }
726
727    struct AndThenTest {
728        outer: BooleanBuffer,
729        inner: BooleanBuffer,
730        dense_mask_expected: bool,
731    }
732
733    impl AndThenTest {
734        fn run(self) {
735            let Self {
736                outer,
737                inner,
738                dense_mask_expected,
739            } = self;
740            assert_eq!(inner.len(), outer.count_set_bits());
741            assert_eq!(
742                should_use_dense_mask(outer.len(), inner.len(), inner.count_set_bits()),
743                dense_mask_expected
744            );
745
746            let mut inner_idx = 0;
747            let expected = BooleanBuffer::from_iter((0..outer.len()).map(|i| {
748                if !outer.value(i) {
749                    return false;
750                }
751                let value = inner.value(inner_idx);
752                inner_idx += 1;
753                value
754            }));
755
756            if dense_mask_expected {
757                assert_eq!(and_then_dense_masks(&outer, &inner), expected);
758            }
759
760            let outer = RowSelection::from_boolean_buffer(outer);
761            let inner = RowSelection::from_boolean_buffer(inner);
762            let actual = outer.and_then(&inner);
763            assert_eq!(actual.as_mask().unwrap(), &expected);
764        }
765    }
766
767    #[test]
768    fn test_dense_mask_and_then_mask_with_offsets() {
769        for len in [8192, 8193, 10_000] {
770            for outer_stride in [100, 20, 4] {
771                let outer_offset = 3;
772                let outer = BooleanBuffer::from_iter(
773                    (0..len + outer_offset).map(|i| (i + 1) % outer_stride != 0),
774                )
775                .slice(outer_offset, len);
776
777                let inner_offset = 5;
778                let inner_len = outer.count_set_bits();
779                let inner = BooleanBuffer::from_iter(
780                    (0..inner_len + inner_offset).map(|i| (i + 2) % 97 != 0),
781                )
782                .slice(inner_offset, inner_len);
783
784                AndThenTest {
785                    outer,
786                    inner,
787                    dense_mask_expected: true,
788                }
789                .run();
790            }
791        }
792    }
793
794    #[test]
795    fn test_dense_mask_and_then_mask_randomized_word_boundaries() {
796        let mut rng = StdRng::seed_from_u64(0x67e4_a91b_239d_c805);
797
798        for (len, outer_offset, inner_offset) in [(8192, 0, 0), (8192, 3, 5), (8193, 7, 1)] {
799            let outer =
800                BooleanBuffer::from_iter((0..len + outer_offset).map(|_| rng.random_bool(0.8)))
801                    .slice(outer_offset, len);
802            let inner_len = outer.count_set_bits();
803            let inner = BooleanBuffer::from_iter(
804                (0..inner_len + inner_offset).map(|_| rng.random_bool(0.5)),
805            )
806            .slice(inner_offset, inner_len);
807
808            AndThenTest {
809                outer,
810                inner,
811                dense_mask_expected: true,
812            }
813            .run();
814        }
815    }
816
817    #[test]
818    fn test_dense_mask_and_then_policy() {
819        let len = 8192;
820        let selected = len * 3 / 4;
821        let min_inner = selected / AND_THEN_DENSE_MASK_MIN_INNER_FRACTION;
822
823        assert!(!should_use_dense_mask(len - 1, selected, min_inner));
824        assert!(!should_use_dense_mask(len, selected - 1, min_inner));
825        assert!(!should_use_dense_mask(len, selected, min_inner - 1));
826        assert!(should_use_dense_mask(len, selected, min_inner));
827    }
828
829    #[test]
830    fn test_selector_and_then_mask() {
831        let outer =
832            RowSelection::from_filters(&[BooleanArray::from(vec![false, true, true, false, true])]);
833        let inner = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![true, false, true]));
834
835        let result = outer.and_then(&inner);
836        assert!(result.as_mask().is_none());
837        assert_eq!(
838            result,
839            RowSelection::from_filters(&[BooleanArray::from(vec![
840                false, true, false, false, true,
841            ])])
842        );
843    }
844
845    #[test]
846    fn test_mask_and_then_none_selected_returns_all_unset() {
847        let outer = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
848            false, true, true, false, true,
849        ]));
850        let inner =
851            RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![false, false, false]));
852
853        let result = outer.and_then(&inner);
854        let mask = result.as_mask().unwrap();
855        assert_eq!(mask.len(), 5);
856        assert_eq!(mask.count_set_bits(), 0);
857    }
858
859    #[test]
860    fn test_mask_intersection_uses_bitwise() {
861        let a_bits = vec![true, true, false, true, false, true];
862        let b_bits = vec![true, false, true, true, true, false];
863        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(a_bits.clone()));
864        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(b_bits.clone()));
865
866        let r = a.intersection(&b);
867        assert!(r.as_mask().is_some());
868
869        let expected: Vec<bool> = a_bits.iter().zip(&b_bits).map(|(x, y)| *x && *y).collect();
870        let expected_sel = RowSelection::from_filters(&[BooleanArray::from(expected)]);
871        assert_eq!(r, expected_sel);
872    }
873
874    #[test]
875    fn test_mask_union_uses_bitwise() {
876        let a_bits = vec![true, false, false, true, false, false];
877        let b_bits = vec![false, true, false, false, true, false];
878        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(a_bits.clone()));
879        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(b_bits.clone()));
880
881        let r = a.union(&b);
882        assert!(r.as_mask().is_some());
883
884        let expected: Vec<bool> = a_bits.iter().zip(&b_bits).map(|(x, y)| *x || *y).collect();
885        let expected_sel = RowSelection::from_filters(&[BooleanArray::from(expected)]);
886        assert_eq!(r, expected_sel);
887    }
888
889    #[test]
890    fn test_mixed_mask_selector_intersection_and_union() {
891        let mask_bits = vec![true, false, true, false, true, false];
892        let selector_bits = vec![false, true, true, false, false, true];
893        let mask = RowSelection::from_boolean_buffer(BooleanBuffer::from(mask_bits.clone()));
894        let selectors = RowSelection::from_filters(&[BooleanArray::from(selector_bits.clone())]);
895
896        let intersection_bits: Vec<_> = mask_bits
897            .iter()
898            .zip(&selector_bits)
899            .map(|(x, y)| *x && *y)
900            .collect();
901        let expected_intersection =
902            RowSelection::from_filters(&[BooleanArray::from(intersection_bits)]);
903        for result in [mask.intersection(&selectors), selectors.intersection(&mask)] {
904            assert!(result.as_mask().is_some());
905            assert_eq!(result, expected_intersection);
906        }
907
908        let union_bits: Vec<_> = mask_bits
909            .iter()
910            .zip(&selector_bits)
911            .map(|(x, y)| *x || *y)
912            .collect();
913        let expected_union = RowSelection::from_filters(&[BooleanArray::from(union_bits)]);
914        for result in [mask.union(&selectors), selectors.union(&mask)] {
915            assert!(result.as_mask().is_some());
916            assert_eq!(result, expected_union);
917        }
918    }
919
920    #[test]
921    fn test_mask_intersection_uneven_passes_tail_through() {
922        let a_bits = vec![true, true, true, true, true];
923        let b_bits = vec![true, false, true];
924        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(a_bits));
925        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(b_bits));
926
927        let r = a.intersection(&b);
928        let r_mask = r.as_mask().unwrap();
929        assert_eq!(r_mask.len(), 5);
930        let bits: Vec<bool> = (0..5).map(|i| r_mask.value(i)).collect();
931        assert_eq!(bits, vec![true, false, true, true, true]);
932
933        // Swapped operands: the right side is longer and its tail passes through.
934        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![true, false, true]));
935        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
936            true, true, true, false, true,
937        ]));
938        let r = a.intersection(&b);
939        let r_mask = r.as_mask().unwrap();
940        assert_eq!(r_mask.len(), 5);
941        let bits: Vec<bool> = (0..5).map(|i| r_mask.value(i)).collect();
942        assert_eq!(bits, vec![true, false, true, false, true]);
943    }
944
945    #[test]
946    fn test_mask_union_uneven_passes_tail_through() {
947        let a_bits = vec![true, false, true];
948        let b_bits = vec![false, true, false, true, false];
949        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(a_bits));
950        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(b_bits));
951
952        let r = a.union(&b);
953        let r_mask = r.as_mask().unwrap();
954        assert_eq!(r_mask.len(), 5);
955        let bits: Vec<bool> = (0..5).map(|i| r_mask.value(i)).collect();
956        assert_eq!(bits, vec![true, true, true, true, false]);
957
958        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
959            false, true, false, false, true,
960        ]));
961        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![true, false, false]));
962        let r = a.union(&b);
963        let r_mask = r.as_mask().unwrap();
964        let bits: Vec<bool> = (0..5).map(|i| r_mask.value(i)).collect();
965        assert_eq!(bits, vec![true, true, false, false, true]);
966    }
967
968    /// Expected result of combining two masks of possibly differing lengths:
969    /// `op` over the common prefix, then the longer side's tail unchanged.
970    fn expected_combined(l: &[bool], r: &[bool], op: fn(bool, bool) -> bool) -> Vec<bool> {
971        let common = l.len().min(r.len());
972        let longer = if l.len() > r.len() { l } else { r };
973        (0..common)
974            .map(|i| op(l[i], r[i]))
975            .chain(longer[common..].iter().copied())
976            .collect()
977    }
978
979    fn assert_mask_eq(actual: &BooleanBuffer, expected: &[bool], context: &str) {
980        assert_eq!(actual.len(), expected.len(), "{context}: length");
981        let actual: Vec<bool> = actual.iter().collect();
982        assert_eq!(actual, expected, "{context}");
983    }
984
985    #[test]
986    fn test_mixed_mask_selector_algebra_with_offsets_and_uneven_lengths() {
987        for rows in [1, 7, 8, 63, 64, 65, 137] {
988            for (mask_len, selector_len) in [(rows, rows), (rows, 137), (137, rows)] {
989                for offset in [0, 1, 7, 63] {
990                    for mask_bits in [
991                        vec![false; mask_len],
992                        vec![true; mask_len],
993                        (0..mask_len).map(|i| i < mask_len / 2).collect(),
994                        (0..mask_len).map(|i| i % 2 == 0).collect(),
995                    ] {
996                        let buffer = BooleanBuffer::from_iter(
997                            std::iter::repeat_n(false, offset).chain(mask_bits.iter().copied()),
998                        )
999                        .slice(offset, mask_len);
1000                        let mask = RowSelection::from_boolean_buffer(buffer);
1001                        for selector_bits in [
1002                            vec![false; selector_len],
1003                            vec![true; selector_len],
1004                            (0..selector_len).map(|i| i % 2 == 0).collect(),
1005                            (0..selector_len).map(|i| (i / 11) % 2 == 0).collect(),
1006                        ] {
1007                            let selectors = RowSelection::from_filters(&[BooleanArray::from(
1008                                selector_bits.clone(),
1009                            )]);
1010                            let context = format!(
1011                                "mask_len={mask_len} selector_len={selector_len} offset={offset}"
1012                            );
1013                            let intersection =
1014                                expected_combined(&mask_bits, &selector_bits, |a, b| a && b);
1015                            let union =
1016                                expected_combined(&mask_bits, &selector_bits, |a, b| a || b);
1017                            for (left, right) in [(&mask, &selectors), (&selectors, &mask)] {
1018                                assert_mask_eq(
1019                                    left.intersection(right).as_mask().unwrap(),
1020                                    &intersection,
1021                                    &context,
1022                                );
1023                                assert_mask_eq(
1024                                    left.union(right).as_mask().unwrap(),
1025                                    &union,
1026                                    &context,
1027                                );
1028                            }
1029                        }
1030                    }
1031                }
1032            }
1033        }
1034    }
1035
1036    #[test]
1037    fn test_mixed_algebra_empty_operand_preserves_tail() {
1038        let empty = RowSelection::default();
1039        let buffer = BooleanBuffer::from_iter((0..64).map(|i| i % 3 == 0));
1040        let mask = RowSelection::from_boolean_buffer(buffer.slice(5, 40));
1041        let selectors = RowSelection::from(vec![
1042            RowSelector::skip(3),
1043            RowSelector::select(7),
1044            RowSelector::skip(4),
1045        ]);
1046        let empty_mask = RowSelection::from_boolean_buffer(BooleanBuffer::new_unset(0));
1047        for (left, right, expected) in [
1048            (&empty, &mask, &mask),
1049            (&mask, &empty, &mask),
1050            (&empty_mask, &selectors, &selectors),
1051            (&selectors, &empty_mask, &selectors),
1052            (&empty, &empty_mask, &empty_mask),
1053            (&empty_mask, &empty, &empty_mask),
1054        ] {
1055            for result in [left.intersection(right), left.union(right)] {
1056                assert_eq!(&result, expected);
1057                match (result.as_mask(), expected.as_mask()) {
1058                    (Some(actual), Some(expected)) => {
1059                        assert_eq!(actual.inner().as_ptr(), expected.inner().as_ptr());
1060                        assert_eq!(actual.offset(), expected.offset());
1061                    }
1062                    (None, None) => {}
1063                    _ => panic!("empty operand changed the other operand's representation"),
1064                }
1065            }
1066        }
1067    }
1068
1069    #[test]
1070    #[cfg_attr(miri, ignore)] // Takes too long
1071    fn test_mask_algebra_with_offsets() {
1072        // Offsets and lengths that are not byte (or word) aligned on either side,
1073        // so the common prefix can start and end mid byte. Covers both the equal
1074        // and uneven length paths.
1075        let base: Vec<bool> = (0..600).map(|i| i % 7 == 0 || i % 3 == 1).collect();
1076        let other: Vec<bool> = (0..600).map(|i| i % 5 == 2 || i % 11 == 4).collect();
1077        let base = BooleanBuffer::from(base);
1078        let other = BooleanBuffer::from(other);
1079
1080        for l_offset in [0, 1, 5, 8, 13, 64, 67] {
1081            for r_offset in [0, 1, 3, 8, 60, 64, 70] {
1082                for (l_len, r_len) in [
1083                    (0, 9),
1084                    (9, 0),
1085                    (1, 200),
1086                    (200, 1),
1087                    (63, 130),
1088                    (321, 65),
1089                    (0, 0),
1090                    (1, 1),
1091                    (63, 63),
1092                    (64, 64),
1093                    (200, 200),
1094                    (321, 321),
1095                ] {
1096                    let l = base.slice(l_offset, l_len);
1097                    let r = other.slice(r_offset, r_len);
1098                    let l_bits: Vec<bool> = l.iter().collect();
1099                    let r_bits: Vec<bool> = r.iter().collect();
1100                    let context =
1101                        format!("l_offset={l_offset} r_offset={r_offset} lens=({l_len},{r_len})");
1102
1103                    assert_mask_eq(
1104                        &intersect_masks(&l, &r),
1105                        &expected_combined(&l_bits, &r_bits, |a, b| a && b),
1106                        &format!("intersect {context}"),
1107                    );
1108                    assert_mask_eq(
1109                        &union_masks(&l, &r),
1110                        &expected_combined(&l_bits, &r_bits, |a, b| a || b),
1111                        &format!("union {context}"),
1112                    );
1113                }
1114            }
1115        }
1116    }
1117
1118    #[test]
1119    #[cfg_attr(miri, ignore)] // Takes too long
1120    fn test_mask_algebra_does_not_retain_backing_buffer() {
1121        // A short slice of a long mask must not keep the long allocation alive,
1122        // including when the other operand is empty and contributes nothing.
1123        // Comparing capacities rather than lengths, since a shallow slice reports a
1124        // short length while still holding the original allocation through its `Arc`.
1125        let long = BooleanBuffer::from((0..80_000).map(|i| i % 3 == 0).collect::<Vec<bool>>());
1126        assert!(long.inner().capacity() >= 10_000);
1127
1128        for (l, r) in [
1129            (long.slice(5, 40), BooleanBuffer::new_unset(0)),
1130            (long.slice(5, 40), BooleanBuffer::new_set(7)),
1131            (BooleanBuffer::new_set(7), long.slice(5, 40)),
1132        ] {
1133            for combined in [intersect_masks(&l, &r), union_masks(&l, &r)] {
1134                assert!(
1135                    combined.inner().capacity() < long.inner().capacity(),
1136                    "result retained the original backing allocation"
1137                );
1138            }
1139        }
1140    }
1141
1142    #[test]
1143    #[cfg_attr(miri, ignore)] // Takes too long
1144    fn test_mask_algebra_fuzz() {
1145        let mut rng = rng();
1146        for _ in 0..200 {
1147            let l_offset = rng.random_range(0..70);
1148            let r_offset = rng.random_range(0..70);
1149            let l_len = rng.random_range(0..300);
1150            // Bias towards equal lengths so that path is hit often
1151            let r_len = match rng.random_bool(0.25) {
1152                true => l_len,
1153                false => rng.random_range(0..300),
1154            };
1155
1156            let l_bits: Vec<bool> = (0..l_offset + l_len)
1157                .map(|_| rng.random_bool(0.5))
1158                .collect();
1159            let r_bits: Vec<bool> = (0..r_offset + r_len)
1160                .map(|_| rng.random_bool(0.5))
1161                .collect();
1162            let l = BooleanBuffer::from(l_bits).slice(l_offset, l_len);
1163            let r = BooleanBuffer::from(r_bits).slice(r_offset, r_len);
1164            let l_bits: Vec<bool> = l.iter().collect();
1165            let r_bits: Vec<bool> = r.iter().collect();
1166
1167            assert_mask_eq(
1168                &intersect_masks(&l, &r),
1169                &expected_combined(&l_bits, &r_bits, |a, b| a && b),
1170                "intersect",
1171            );
1172            assert_mask_eq(
1173                &union_masks(&l, &r),
1174                &expected_combined(&l_bits, &r_bits, |a, b| a || b),
1175                "union",
1176            );
1177        }
1178    }
1179
1180    #[test]
1181    fn dense_composition_with_zero_words_and_offsets() {
1182        for mask_len in [8192, 8193, 65537] {
1183            for (mask_offset, inner_offset) in [(0, 0), (3, 5), (63, 65), (65, 129)] {
1184                for stride in [4, 100] {
1185                    let mask =
1186                        BooleanBuffer::from_iter((0..mask_offset + mask_len + 73).map(|i| {
1187                            !(mask_offset..mask_offset + mask_len).contains(&i)
1188                                || (i - mask_offset) % stride != 0
1189                        }))
1190                        .slice(mask_offset, mask_len);
1191                    let len = mask.count_set_bits();
1192                    for start in [0, 63, len / 2] {
1193                        for select_last in [false, true] {
1194                            let count = len / 10;
1195                            let inner =
1196                                BooleanBuffer::from_iter((0..inner_offset + len + 73).map(|i| {
1197                                    if !(inner_offset..inner_offset + len).contains(&i) {
1198                                        return true;
1199                                    }
1200                                    let j = i - inner_offset;
1201                                    (start..start + count).contains(&j)
1202                                        || select_last && j == len - 1
1203                                }))
1204                                .slice(inner_offset, len);
1205                            AndThenTest {
1206                                outer: mask.clone(),
1207                                inner,
1208                                dense_mask_expected: true,
1209                            }
1210                            .run();
1211                        }
1212                    }
1213                }
1214            }
1215        }
1216    }
1217}