Skip to main content

parquet/arrow/arrow_reader/selection/
algebra.rs

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