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::{MaskRunIter, RowSelection, RowSelectionInner, RowSelector};
26use arrow_buffer::{BooleanBuffer, BooleanBufferBuilder, MutableBuffer, bit_util};
27use std::cmp::Ordering;
28use std::iter::Peekable;
29
30/// Applies `second` to the rows selected by `first`, both selector-backed.
31pub(super) fn and_then_row_selections(
32    first: &[RowSelector],
33    second: &[RowSelector],
34) -> RowSelection {
35    let mut selectors = vec![];
36    let mut first = first.iter().copied().peekable();
37    let mut second = second.iter().copied().peekable();
38    and_then_iter(&mut selectors, &mut first, &mut second);
39    RowSelection::from_selectors(selectors)
40}
41
42/// Applies the mask `second` to the rows selected by the selector-backed `first`.
43///
44/// The mask is streamed as [`RowSelector`] runs, so it is never materialized.
45pub(super) fn and_then_selectors_with_mask(
46    first: &[RowSelector],
47    second: &BooleanBuffer,
48) -> RowSelection {
49    let mut selectors = vec![];
50    let mut first = first.iter().copied().peekable();
51    let mut second = MaskRunIter::new(second).peekable();
52    and_then_iter(&mut selectors, &mut first, &mut second);
53    RowSelection::from_selectors(selectors)
54}
55
56fn and_then_iter<I, J>(
57    selectors: &mut Vec<RowSelector>,
58    first: &mut Peekable<I>,
59    second: &mut Peekable<J>,
60) where
61    I: Iterator<Item = RowSelector>,
62    J: Iterator<Item = RowSelector>,
63{
64    let mut to_skip = 0;
65    while let Some(b) = second.peek_mut() {
66        let a = first
67            .peek_mut()
68            .expect("selection exceeds the number of selected rows");
69
70        if b.row_count == 0 {
71            second.next().unwrap();
72            continue;
73        }
74
75        if a.row_count == 0 {
76            first.next().unwrap();
77            continue;
78        }
79
80        if a.skip {
81            // Records were skipped when producing second
82            to_skip += a.row_count;
83            first.next().unwrap();
84            continue;
85        }
86
87        let skip = b.skip;
88        let to_process = a.row_count.min(b.row_count);
89
90        a.row_count -= to_process;
91        b.row_count -= to_process;
92
93        match skip {
94            true => to_skip += to_process,
95            false => {
96                if to_skip != 0 {
97                    selectors.push(RowSelector::skip(to_skip));
98                    to_skip = 0;
99                }
100                selectors.push(RowSelector::select(to_process))
101            }
102        }
103    }
104
105    for v in first {
106        if v.row_count != 0 {
107            assert!(
108                v.skip,
109                "selection contains less than the number of selected rows"
110            );
111            to_skip += v.row_count
112        }
113    }
114
115    if to_skip != 0 {
116        selectors.push(RowSelector::skip(to_skip));
117    }
118}
119
120/// Combine two lists of `RowSelection` return the intersection of them
121/// For example:
122/// self:      NNYYYYNNYYNYN
123/// other:     NYNNNNNNY
124///
125/// returned:  NNNNNNNNYYNYN
126pub(super) fn intersect_row_selections(
127    left: &[RowSelector],
128    right: &[RowSelector],
129) -> RowSelection {
130    let mut l_iter = left.iter().copied().peekable();
131    let mut r_iter = right.iter().copied().peekable();
132
133    let iter = std::iter::from_fn(move || {
134        loop {
135            let l = l_iter.peek_mut();
136            let r = r_iter.peek_mut();
137
138            match (l, r) {
139                (Some(a), _) if a.row_count == 0 => {
140                    l_iter.next().unwrap();
141                }
142                (_, Some(b)) if b.row_count == 0 => {
143                    r_iter.next().unwrap();
144                }
145                (Some(l), Some(r)) => {
146                    return match (l.skip, r.skip) {
147                        // Keep both ranges
148                        (false, false) => {
149                            if l.row_count < r.row_count {
150                                r.row_count -= l.row_count;
151                                l_iter.next()
152                            } else {
153                                l.row_count -= r.row_count;
154                                r_iter.next()
155                            }
156                        }
157                        // skip at least one
158                        _ => {
159                            if l.row_count < r.row_count {
160                                let skip = l.row_count;
161                                r.row_count -= l.row_count;
162                                l_iter.next();
163                                Some(RowSelector::skip(skip))
164                            } else {
165                                let skip = r.row_count;
166                                l.row_count -= skip;
167                                r_iter.next();
168                                Some(RowSelector::skip(skip))
169                            }
170                        }
171                    };
172                }
173                (Some(_), None) => return l_iter.next(),
174                (None, Some(_)) => return r_iter.next(),
175                (None, None) => return None,
176            }
177        }
178    });
179
180    iter.collect()
181}
182
183/// Combine two lists of `RowSelector` return the union of them
184/// For example:
185/// self:      NNYYYYNNYYNYN
186/// other:     NYNNNNNNY
187///
188/// returned:  NYYYYYNNYYNYN
189///
190/// This can be removed from here once RowSelection::union is in parquet::arrow
191pub(super) fn union_row_selections(left: &[RowSelector], right: &[RowSelector]) -> RowSelection {
192    let mut l_iter = left.iter().copied().peekable();
193    let mut r_iter = right.iter().copied().peekable();
194
195    let iter = std::iter::from_fn(move || {
196        loop {
197            let l = l_iter.peek_mut();
198            let r = r_iter.peek_mut();
199
200            match (l, r) {
201                (Some(a), _) if a.row_count == 0 => {
202                    l_iter.next().unwrap();
203                }
204                (_, Some(b)) if b.row_count == 0 => {
205                    r_iter.next().unwrap();
206                }
207                (Some(l), Some(r)) => {
208                    return match (l.skip, r.skip) {
209                        // Skip both ranges
210                        (true, true) => {
211                            if l.row_count < r.row_count {
212                                let skip = l.row_count;
213                                r.row_count -= l.row_count;
214                                l_iter.next();
215                                Some(RowSelector::skip(skip))
216                            } else {
217                                let skip = r.row_count;
218                                l.row_count -= skip;
219                                r_iter.next();
220                                Some(RowSelector::skip(skip))
221                            }
222                        }
223                        // Keep rows from left
224                        (false, true) => {
225                            if l.row_count < r.row_count {
226                                r.row_count -= l.row_count;
227                                l_iter.next()
228                            } else {
229                                let r_row_count = r.row_count;
230                                l.row_count -= r_row_count;
231                                r_iter.next();
232                                Some(RowSelector::select(r_row_count))
233                            }
234                        }
235                        // Keep rows from right
236                        (true, false) => {
237                            if l.row_count < r.row_count {
238                                let l_row_count = l.row_count;
239                                r.row_count -= l_row_count;
240                                l_iter.next();
241                                Some(RowSelector::select(l_row_count))
242                            } else {
243                                l.row_count -= r.row_count;
244                                r_iter.next()
245                            }
246                        }
247                        // Keep at least one
248                        _ => {
249                            if l.row_count < r.row_count {
250                                r.row_count -= l.row_count;
251                                l_iter.next()
252                            } else {
253                                l.row_count -= r.row_count;
254                                r_iter.next()
255                            }
256                        }
257                    };
258                }
259                (Some(_), None) => return l_iter.next(),
260                (None, Some(_)) => return r_iter.next(),
261                (None, None) => return None,
262            }
263        }
264    });
265
266    iter.collect()
267}
268
269/// Bitwise AND of two mask-backed selections. Longer side's tail passes through.
270pub(super) fn intersect_masks(l: &BooleanBuffer, r: &BooleanBuffer) -> BooleanBuffer {
271    if l.len() == r.len() {
272        return combine_equal_length_masks(l, r, |a, b| a & b);
273    }
274    combine_unequal_length_masks(l, r, |a, b| a & b)
275}
276
277/// Bitwise OR of two mask-backed selections. Longer side's tail passes through.
278pub(super) fn union_masks(l: &BooleanBuffer, r: &BooleanBuffer) -> BooleanBuffer {
279    if l.len() == r.len() {
280        return combine_equal_length_masks(l, r, |a, b| a | b);
281    }
282    combine_unequal_length_masks(l, r, |a, b| a | b)
283}
284
285/// Combines two masks of equal length with the bitwise operation `op`.
286///
287/// `BitAnd`/`BitOr` on `&BooleanBuffer` normalise the result to a zero bit offset,
288/// which costs a second allocation and a shifting copy when both operands
289/// share the same non-zero sub-64-bit alignment, causing
290/// `from_bitwise_binary_op` to return a non-zero-offset result. Building the
291/// buffer directly keeps the offset, as the unequal-length path does.
292fn combine_equal_length_masks<F>(l: &BooleanBuffer, r: &BooleanBuffer, op: F) -> BooleanBuffer
293where
294    F: FnMut(u64, u64) -> u64,
295{
296    BooleanBuffer::from_bitwise_binary_op(
297        l.values(),
298        l.offset(),
299        r.values(),
300        r.offset(),
301        l.len(),
302        op,
303    )
304}
305
306/// Combines two masks of unequal length with the bitwise operation `op`,
307/// passing the longer side's tail through unchanged.
308///
309/// The longer mask is copied once into a [`MutableBuffer`] and `op` is then
310/// applied in place over the common prefix. This avoids materialising the
311/// prefix into its own buffer and copying both prefix and tail again through a
312/// [`BooleanBufferBuilder`].
313///
314/// Neither the mask offsets nor the prefix length are assumed to be byte
315/// aligned: the copy keeps the longer mask's offset within its first byte so it
316/// stays a plain byte copy, and that offset is carried over to the result. Only
317/// the longer mask's own byte range is copied, so the result does not retain the
318/// backing allocation it was sliced from.
319fn combine_unequal_length_masks<F>(l: &BooleanBuffer, r: &BooleanBuffer, op: F) -> BooleanBuffer
320where
321    F: FnMut(u64, u64) -> u64,
322{
323    let (longer, shorter) = if l.len() > r.len() { (l, r) } else { (r, l) };
324
325    let sub_byte_offset = longer.offset() % 8;
326    let start_byte = longer.offset() / 8;
327    let end_byte = bit_util::ceil(longer.offset() + longer.len(), 8);
328    let bytes = &longer.values()[start_byte..end_byte];
329    let mut buffer = MutableBuffer::new(bytes.len());
330    buffer.extend_from_slice(bytes);
331
332    bit_util::apply_bitwise_binary_op(
333        buffer.as_slice_mut(),
334        sub_byte_offset,
335        shorter.values(),
336        shorter.offset(),
337        shorter.len(),
338        op,
339    );
340
341    BooleanBuffer::new(buffer.into(), sub_byte_offset, longer.len())
342}
343
344/// Applies `other` to the selected rows of `mask`, preserving the original row domain.
345pub(super) fn and_then_mask(mask: &BooleanBuffer, other: &RowSelection) -> BooleanBuffer {
346    match &other.inner {
347        RowSelectionInner::Mask(other_mask) => and_then_masks(mask, other_mask.mask()),
348        RowSelectionInner::Selectors(selectors) => {
349            and_then_mask_from_selectors(mask, selectors.iter().copied())
350        }
351    }
352}
353
354fn and_then_mask_from_selectors<I>(mask: &BooleanBuffer, other: I) -> BooleanBuffer
355where
356    I: IntoIterator<Item = RowSelector>,
357{
358    let mut builder = BooleanBufferBuilder::new(mask.len());
359    let mut other_iter = other.into_iter();
360    let mut current = other_iter.next();
361    let mut cursor = 0usize;
362
363    // Iterate only over the set positions in `mask`; the gaps of unset bits
364    // are filled in bulk with `append_n` instead of bit-by-bit.
365    for set_idx in mask.set_indices() {
366        if set_idx > cursor {
367            builder.append_n(set_idx - cursor, false);
368        }
369        cursor = set_idx + 1;
370
371        while current.as_ref().is_some_and(|s| s.row_count == 0) {
372            current = other_iter.next();
373        }
374        let selector = current
375            .as_mut()
376            .expect("selection contains less than the number of selected rows");
377        let selected = !selector.skip;
378        selector.row_count -= 1;
379        builder.append(selected);
380    }
381    if cursor < mask.len() {
382        builder.append_n(mask.len() - cursor, false);
383    }
384
385    if current.is_some_and(|s| s.row_count != 0) || other_iter.any(|s| s.row_count != 0) {
386        panic!("selection exceeds the number of selected rows");
387    }
388
389    builder.finish()
390}
391
392fn and_then_masks(mask: &BooleanBuffer, other: &BooleanBuffer) -> BooleanBuffer {
393    let selected_count = mask.count_set_bits();
394    match other.len().cmp(&selected_count) {
395        Ordering::Less => panic!("selection contains less than the number of selected rows"),
396        Ordering::Greater => panic!("selection exceeds the number of selected rows"),
397        Ordering::Equal => {}
398    }
399
400    let other_true_count = other.count_set_bits();
401    if other_true_count == 0 {
402        return BooleanBuffer::new_unset(mask.len());
403    }
404    if other_true_count == selected_count {
405        return mask.clone();
406    }
407
408    let mut builder = BooleanBufferBuilder::new(mask.len());
409    let mut outer_set_indices = mask.set_indices();
410    let mut next_selected_ordinal = 0usize;
411    let mut cursor = 0usize;
412
413    for selected_ordinal in other.set_indices() {
414        let skip = selected_ordinal - next_selected_ordinal;
415        let set_idx = outer_set_indices
416            .nth(skip)
417            .expect("validated other length matches selected row count");
418        if set_idx > cursor {
419            builder.append_n(set_idx - cursor, false);
420        }
421        builder.append(true);
422        cursor = set_idx + 1;
423        next_selected_ordinal = selected_ordinal + 1;
424    }
425
426    if cursor < mask.len() {
427        builder.append_n(mask.len() - cursor, false);
428    }
429
430    builder.finish()
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436    use arrow_array::BooleanArray;
437    use rand::{RngExt, rng};
438
439    #[test]
440    fn test_and() {
441        let mut a = RowSelection::from(vec![
442            RowSelector::skip(12),
443            RowSelector::select(23),
444            RowSelector::skip(3),
445            RowSelector::select(5),
446        ]);
447
448        let b = RowSelection::from(vec![
449            RowSelector::select(5),
450            RowSelector::skip(4),
451            RowSelector::select(15),
452            RowSelector::skip(4),
453        ]);
454
455        let mut expected = RowSelection::from(vec![
456            RowSelector::skip(12),
457            RowSelector::select(5),
458            RowSelector::skip(4),
459            RowSelector::select(14),
460            RowSelector::skip(3),
461            RowSelector::select(1),
462            RowSelector::skip(4),
463        ]);
464
465        assert_eq!(a.and_then(&b), expected);
466
467        a.split_off(7);
468        expected.split_off(7);
469        assert_eq!(a.and_then(&b), expected);
470
471        let a = RowSelection::from(vec![RowSelector::select(5), RowSelector::skip(3)]);
472
473        let b = RowSelection::from(vec![
474            RowSelector::select(2),
475            RowSelector::skip(1),
476            RowSelector::select(1),
477            RowSelector::skip(1),
478        ]);
479
480        assert_eq!(
481            a.and_then(&b).selectors(),
482            vec![
483                RowSelector::select(2),
484                RowSelector::skip(1),
485                RowSelector::select(1),
486                RowSelector::skip(4)
487            ]
488        );
489    }
490
491    #[test]
492    #[should_panic(expected = "selection exceeds the number of selected rows")]
493    fn test_and_longer() {
494        let a = RowSelection::from(vec![
495            RowSelector::select(3),
496            RowSelector::skip(33),
497            RowSelector::select(3),
498            RowSelector::skip(33),
499        ]);
500        let b = RowSelection::from(vec![RowSelector::select(36)]);
501        a.and_then(&b);
502    }
503
504    #[test]
505    #[should_panic(expected = "selection contains less than the number of selected rows")]
506    fn test_and_shorter() {
507        let a = RowSelection::from(vec![
508            RowSelector::select(3),
509            RowSelector::skip(33),
510            RowSelector::select(3),
511            RowSelector::skip(33),
512        ]);
513        let b = RowSelection::from(vec![RowSelector::select(3)]);
514        a.and_then(&b);
515    }
516
517    #[test]
518    fn test_intersect_row_selection_and_combine() {
519        // a size equal b size
520        let a = vec![
521            RowSelector::select(5),
522            RowSelector::skip(4),
523            RowSelector::select(1),
524        ];
525        let b = vec![
526            RowSelector::select(8),
527            RowSelector::skip(1),
528            RowSelector::select(1),
529        ];
530
531        let res = intersect_row_selections(&a, &b);
532        assert_eq!(
533            res.selectors(),
534            vec![
535                RowSelector::select(5),
536                RowSelector::skip(4),
537                RowSelector::select(1),
538            ],
539        );
540
541        // a size larger than b size
542        let a = vec![
543            RowSelector::select(3),
544            RowSelector::skip(33),
545            RowSelector::select(3),
546            RowSelector::skip(33),
547        ];
548        let b = vec![RowSelector::select(36), RowSelector::skip(36)];
549        let res = intersect_row_selections(&a, &b);
550        assert_eq!(
551            res.selectors(),
552            vec![RowSelector::select(3), RowSelector::skip(69)]
553        );
554
555        // a size less than b size
556        let a = vec![RowSelector::select(3), RowSelector::skip(7)];
557        let b = vec![
558            RowSelector::select(2),
559            RowSelector::skip(2),
560            RowSelector::select(2),
561            RowSelector::skip(2),
562            RowSelector::select(2),
563        ];
564        let res = intersect_row_selections(&a, &b);
565        assert_eq!(
566            res.selectors(),
567            vec![RowSelector::select(2), RowSelector::skip(8)]
568        );
569
570        let a = vec![RowSelector::select(3), RowSelector::skip(7)];
571        let b = vec![
572            RowSelector::select(2),
573            RowSelector::skip(2),
574            RowSelector::select(2),
575            RowSelector::skip(2),
576            RowSelector::select(2),
577        ];
578        let res = intersect_row_selections(&a, &b);
579        assert_eq!(
580            res.selectors(),
581            vec![RowSelector::select(2), RowSelector::skip(8)]
582        );
583    }
584
585    #[test]
586    fn test_and_fuzz() {
587        let mut rand = rng();
588        for _ in 0..100 {
589            let a_len = rand.random_range(10..100);
590            let a_bools: Vec<_> = (0..a_len).map(|_| rand.random_bool(0.2)).collect();
591            let a = RowSelection::from_filters(&[BooleanArray::from(a_bools.clone())]);
592
593            let b_len: usize = a_bools.iter().map(|x| *x as usize).sum();
594            let b_bools: Vec<_> = (0..b_len).map(|_| rand.random_bool(0.8)).collect();
595            let b = RowSelection::from_filters(&[BooleanArray::from(b_bools.clone())]);
596
597            let mut expected_bools = vec![false; a_len];
598
599            let mut iter_b = b_bools.iter();
600            for (idx, b) in a_bools.iter().enumerate() {
601                if *b && *iter_b.next().unwrap() {
602                    expected_bools[idx] = true;
603                }
604            }
605
606            let expected = RowSelection::from_filters(&[BooleanArray::from(expected_bools)]);
607
608            let total_rows: usize = expected.selectors().iter().map(|s| s.row_count).sum();
609            assert_eq!(a_len, total_rows);
610
611            assert_eq!(a.and_then(&b), expected);
612        }
613    }
614
615    #[test]
616    fn test_intersection() {
617        let selection = RowSelection::from(vec![RowSelector::select(1048576)]);
618        let result = selection.intersection(&selection);
619        assert_eq!(result, selection);
620
621        let a = RowSelection::from(vec![
622            RowSelector::skip(10),
623            RowSelector::select(10),
624            RowSelector::skip(10),
625            RowSelector::select(20),
626        ]);
627
628        let b = RowSelection::from(vec![
629            RowSelector::skip(20),
630            RowSelector::select(20),
631            RowSelector::skip(10),
632        ]);
633
634        let result = a.intersection(&b);
635        assert_eq!(
636            result.selectors(),
637            vec![
638                RowSelector::skip(30),
639                RowSelector::select(10),
640                RowSelector::skip(10)
641            ]
642        );
643    }
644
645    #[test]
646    fn test_union() {
647        let selection = RowSelection::from(vec![RowSelector::select(1048576)]);
648        let result = selection.union(&selection);
649        assert_eq!(result, selection);
650
651        // NYNYY
652        let a = RowSelection::from(vec![
653            RowSelector::skip(10),
654            RowSelector::select(10),
655            RowSelector::skip(10),
656            RowSelector::select(20),
657        ]);
658
659        // NNYYNYN
660        let b = RowSelection::from(vec![
661            RowSelector::skip(20),
662            RowSelector::select(20),
663            RowSelector::skip(10),
664            RowSelector::select(10),
665            RowSelector::skip(10),
666        ]);
667
668        let result = a.union(&b);
669
670        // NYYYYYN
671        assert_eq!(
672            result.iter().copied().collect::<Vec<_>>(),
673            vec![
674                RowSelector::skip(10),
675                RowSelector::select(50),
676                RowSelector::skip(10),
677            ]
678        );
679    }
680
681    #[test]
682    fn test_mask_and_then_preserves_backing() {
683        let outer_bits = vec![false, true, true, false, true, false, true];
684        let inner_bits = vec![true, false, true, false];
685        let outer_mask = RowSelection::from_boolean_buffer(BooleanBuffer::from(outer_bits.clone()));
686        let inner = RowSelection::from_filters(&[BooleanArray::from(inner_bits.clone())]);
687
688        let result = outer_mask.and_then(&inner);
689        assert!(result.as_mask().is_some());
690
691        let outer_selectors = RowSelection::from_filters(&[BooleanArray::from(outer_bits)]);
692        let expected = outer_selectors.and_then(&inner);
693        assert_eq!(result, expected);
694
695        let result_mask = result.as_mask().unwrap();
696        let actual_bits: Vec<_> = (0..result_mask.len())
697            .map(|i| result_mask.value(i))
698            .collect();
699        assert_eq!(
700            actual_bits,
701            vec![false, true, false, false, true, false, false]
702        );
703    }
704
705    #[test]
706    fn test_mask_and_then_mask_preserves_backing() {
707        let outer_bits = vec![false, true, true, false, true, false, true, true];
708        let inner_bits = vec![false, true, false, true, false];
709        let outer_mask = RowSelection::from_boolean_buffer(BooleanBuffer::from(outer_bits.clone()));
710        let inner_mask = RowSelection::from_boolean_buffer(BooleanBuffer::from(inner_bits));
711
712        let result = outer_mask.and_then(&inner_mask);
713        assert!(result.as_mask().is_some());
714
715        let outer_selectors = RowSelection::from_filters(&[BooleanArray::from(outer_bits)]);
716        let inner_selectors = RowSelection::from_filters(&[BooleanArray::from(vec![
717            false, true, false, true, false,
718        ])]);
719        assert_eq!(result, outer_selectors.and_then(&inner_selectors));
720
721        let result_mask = result.as_mask().unwrap();
722        let actual_bits: Vec<_> = (0..result_mask.len())
723            .map(|i| result_mask.value(i))
724            .collect();
725        assert_eq!(
726            actual_bits,
727            vec![false, false, true, false, false, false, true, false]
728        );
729    }
730
731    #[test]
732    fn test_selector_and_then_mask() {
733        let outer =
734            RowSelection::from_filters(&[BooleanArray::from(vec![false, true, true, false, true])]);
735        let inner = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![true, false, true]));
736
737        let result = outer.and_then(&inner);
738        assert!(result.as_mask().is_none());
739        assert_eq!(
740            result,
741            RowSelection::from_filters(&[BooleanArray::from(vec![
742                false, true, false, false, true,
743            ])])
744        );
745    }
746
747    #[test]
748    fn test_mask_and_then_none_selected_returns_all_unset() {
749        let outer = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
750            false, true, true, false, true,
751        ]));
752        let inner =
753            RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![false, false, false]));
754
755        let result = outer.and_then(&inner);
756        let mask = result.as_mask().unwrap();
757        assert_eq!(mask.len(), 5);
758        assert_eq!(mask.count_set_bits(), 0);
759    }
760
761    #[test]
762    fn test_mask_intersection_uses_bitwise() {
763        let a_bits = vec![true, true, false, true, false, true];
764        let b_bits = vec![true, false, true, true, true, false];
765        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(a_bits.clone()));
766        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(b_bits.clone()));
767
768        let r = a.intersection(&b);
769        assert!(r.as_mask().is_some());
770
771        let expected: Vec<bool> = a_bits.iter().zip(&b_bits).map(|(x, y)| *x && *y).collect();
772        let expected_sel = RowSelection::from_filters(&[BooleanArray::from(expected)]);
773        assert_eq!(r, expected_sel);
774    }
775
776    #[test]
777    fn test_mask_union_uses_bitwise() {
778        let a_bits = vec![true, false, false, true, false, false];
779        let b_bits = vec![false, true, false, false, true, false];
780        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(a_bits.clone()));
781        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(b_bits.clone()));
782
783        let r = a.union(&b);
784        assert!(r.as_mask().is_some());
785
786        let expected: Vec<bool> = a_bits.iter().zip(&b_bits).map(|(x, y)| *x || *y).collect();
787        let expected_sel = RowSelection::from_filters(&[BooleanArray::from(expected)]);
788        assert_eq!(r, expected_sel);
789    }
790
791    #[test]
792    fn test_mixed_mask_selector_intersection_and_union() {
793        let mask_bits = vec![true, false, true, false, true, false];
794        let selector_bits = vec![false, true, true, false, false, true];
795        let mask = RowSelection::from_boolean_buffer(BooleanBuffer::from(mask_bits.clone()));
796        let selectors = RowSelection::from_filters(&[BooleanArray::from(selector_bits.clone())]);
797
798        let intersection_bits: Vec<_> = mask_bits
799            .iter()
800            .zip(&selector_bits)
801            .map(|(x, y)| *x && *y)
802            .collect();
803        let expected_intersection =
804            RowSelection::from_filters(&[BooleanArray::from(intersection_bits)]);
805        assert_eq!(mask.intersection(&selectors), expected_intersection);
806        assert_eq!(selectors.intersection(&mask), expected_intersection);
807
808        let union_bits: Vec<_> = mask_bits
809            .iter()
810            .zip(&selector_bits)
811            .map(|(x, y)| *x || *y)
812            .collect();
813        let expected_union = RowSelection::from_filters(&[BooleanArray::from(union_bits)]);
814        assert_eq!(mask.union(&selectors), expected_union);
815        assert_eq!(selectors.union(&mask), expected_union);
816    }
817
818    #[test]
819    fn test_mask_intersection_uneven_passes_tail_through() {
820        let a_bits = vec![true, true, true, true, true];
821        let b_bits = vec![true, false, true];
822        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(a_bits));
823        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(b_bits));
824
825        let r = a.intersection(&b);
826        let r_mask = r.as_mask().unwrap();
827        assert_eq!(r_mask.len(), 5);
828        let bits: Vec<bool> = (0..5).map(|i| r_mask.value(i)).collect();
829        assert_eq!(bits, vec![true, false, true, true, true]);
830
831        // Swapped operands: the right side is longer and its tail passes through.
832        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![true, false, true]));
833        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
834            true, true, true, false, true,
835        ]));
836        let r = a.intersection(&b);
837        let r_mask = r.as_mask().unwrap();
838        assert_eq!(r_mask.len(), 5);
839        let bits: Vec<bool> = (0..5).map(|i| r_mask.value(i)).collect();
840        assert_eq!(bits, vec![true, false, true, false, true]);
841    }
842
843    #[test]
844    fn test_mask_union_uneven_passes_tail_through() {
845        let a_bits = vec![true, false, true];
846        let b_bits = vec![false, true, false, true, false];
847        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(a_bits));
848        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(b_bits));
849
850        let r = a.union(&b);
851        let r_mask = r.as_mask().unwrap();
852        assert_eq!(r_mask.len(), 5);
853        let bits: Vec<bool> = (0..5).map(|i| r_mask.value(i)).collect();
854        assert_eq!(bits, vec![true, true, true, true, false]);
855
856        let a = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
857            false, true, false, false, true,
858        ]));
859        let b = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![true, false, false]));
860        let r = a.union(&b);
861        let r_mask = r.as_mask().unwrap();
862        let bits: Vec<bool> = (0..5).map(|i| r_mask.value(i)).collect();
863        assert_eq!(bits, vec![true, true, false, false, true]);
864    }
865
866    /// Expected result of combining two masks of possibly differing lengths:
867    /// `op` over the common prefix, then the longer side's tail unchanged.
868    fn expected_combined(l: &[bool], r: &[bool], op: fn(bool, bool) -> bool) -> Vec<bool> {
869        let common = l.len().min(r.len());
870        let longer = if l.len() > r.len() { l } else { r };
871        (0..common)
872            .map(|i| op(l[i], r[i]))
873            .chain(longer[common..].iter().copied())
874            .collect()
875    }
876
877    fn assert_mask_eq(actual: &BooleanBuffer, expected: &[bool], context: &str) {
878        assert_eq!(actual.len(), expected.len(), "{context}: length");
879        let actual: Vec<bool> = actual.iter().collect();
880        assert_eq!(actual, expected, "{context}");
881    }
882
883    #[test]
884    fn test_mask_algebra_with_offsets() {
885        // Offsets and lengths that are not byte (or word) aligned on either side,
886        // so the common prefix can start and end mid byte. Covers both the equal
887        // and uneven length paths.
888        let base: Vec<bool> = (0..600).map(|i| i % 7 == 0 || i % 3 == 1).collect();
889        let other: Vec<bool> = (0..600).map(|i| i % 5 == 2 || i % 11 == 4).collect();
890        let base = BooleanBuffer::from(base);
891        let other = BooleanBuffer::from(other);
892
893        for l_offset in [0, 1, 5, 8, 13, 64, 67] {
894            for r_offset in [0, 1, 3, 8, 60, 64, 70] {
895                for (l_len, r_len) in [
896                    (0, 9),
897                    (9, 0),
898                    (1, 200),
899                    (200, 1),
900                    (63, 130),
901                    (321, 65),
902                    (0, 0),
903                    (1, 1),
904                    (63, 63),
905                    (64, 64),
906                    (200, 200),
907                    (321, 321),
908                ] {
909                    let l = base.slice(l_offset, l_len);
910                    let r = other.slice(r_offset, r_len);
911                    let l_bits: Vec<bool> = l.iter().collect();
912                    let r_bits: Vec<bool> = r.iter().collect();
913                    let context =
914                        format!("l_offset={l_offset} r_offset={r_offset} lens=({l_len},{r_len})");
915
916                    assert_mask_eq(
917                        &intersect_masks(&l, &r),
918                        &expected_combined(&l_bits, &r_bits, |a, b| a && b),
919                        &format!("intersect {context}"),
920                    );
921                    assert_mask_eq(
922                        &union_masks(&l, &r),
923                        &expected_combined(&l_bits, &r_bits, |a, b| a || b),
924                        &format!("union {context}"),
925                    );
926                }
927            }
928        }
929    }
930
931    #[test]
932    fn test_mask_algebra_does_not_retain_backing_buffer() {
933        // A short slice of a long mask must not keep the long allocation alive,
934        // including when the other operand is empty and contributes nothing.
935        // Comparing capacities rather than lengths, since a shallow slice reports a
936        // short length while still holding the original allocation through its `Arc`.
937        let long = BooleanBuffer::from((0..80_000).map(|i| i % 3 == 0).collect::<Vec<bool>>());
938        assert!(long.inner().capacity() >= 10_000);
939
940        for (l, r) in [
941            (long.slice(5, 40), BooleanBuffer::new_unset(0)),
942            (long.slice(5, 40), BooleanBuffer::new_set(7)),
943            (BooleanBuffer::new_set(7), long.slice(5, 40)),
944        ] {
945            for combined in [intersect_masks(&l, &r), union_masks(&l, &r)] {
946                assert!(
947                    combined.inner().capacity() < long.inner().capacity(),
948                    "result retained the original backing allocation"
949                );
950            }
951        }
952    }
953
954    #[test]
955    fn test_mask_algebra_fuzz() {
956        let mut rng = rng();
957        for _ in 0..200 {
958            let l_offset = rng.random_range(0..70);
959            let r_offset = rng.random_range(0..70);
960            let l_len = rng.random_range(0..300);
961            // Bias towards equal lengths so that path is hit often
962            let r_len = match rng.random_bool(0.25) {
963                true => l_len,
964                false => rng.random_range(0..300),
965            };
966
967            let l_bits: Vec<bool> = (0..l_offset + l_len)
968                .map(|_| rng.random_bool(0.5))
969                .collect();
970            let r_bits: Vec<bool> = (0..r_offset + r_len)
971                .map(|_| rng.random_bool(0.5))
972                .collect();
973            let l = BooleanBuffer::from(l_bits).slice(l_offset, l_len);
974            let r = BooleanBuffer::from(r_bits).slice(r_offset, r_len);
975            let l_bits: Vec<bool> = l.iter().collect();
976            let r_bits: Vec<bool> = r.iter().collect();
977
978            assert_mask_eq(
979                &intersect_masks(&l, &r),
980                &expected_combined(&l_bits, &r_bits, |a, b| a && b),
981                "intersect",
982            );
983            assert_mask_eq(
984                &union_masks(&l, &r),
985                &expected_combined(&l_bits, &r_bits, |a, b| a || b),
986                "union",
987            );
988        }
989    }
990}