1use super::{RowSelection, RowSelector};
13use crate::errors::ParquetError;
14use arrow_array::BooleanArray;
15use arrow_buffer::BooleanBuffer;
16use arrow_buffer::bit_chunk_iterator::BitChunks;
17use std::collections::VecDeque;
18use std::ops::Range;
19use std::sync::Arc;
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum RowSelectionPolicy {
24 Selectors,
26 Mask,
28 Auto {
30 threshold: usize,
32 },
33}
34
35impl Default for RowSelectionPolicy {
36 fn default() -> Self {
37 Self::Auto { threshold: 32 }
38 }
39}
40
41impl RowSelectionPolicy {
42 pub(crate) fn resolve(self, selection: Option<&RowSelection>) -> RowSelectionStrategy {
44 match self {
45 Self::Selectors => RowSelectionStrategy::Selectors,
46 Self::Mask => RowSelectionStrategy::Mask,
47 Self::Auto { threshold } => selection
48 .map(|selection| selection.auto_selection_strategy(threshold))
49 .unwrap_or(RowSelectionStrategy::Selectors),
50 }
51 }
52
53 pub(crate) fn apply(self, selection: RowSelection) -> RowSelection {
56 match (
57 self.resolve(Some(&selection)),
58 selection.as_mask().is_some(),
59 ) {
60 (RowSelectionStrategy::Mask, true) | (RowSelectionStrategy::Selectors, false) => {
61 selection
62 }
63 (RowSelectionStrategy::Mask, false) => selection.force_mask(),
64 (RowSelectionStrategy::Selectors, true) => {
65 RowSelection::from(Vec::<RowSelector>::from(selection))
66 }
67 }
68 }
69}
70
71#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76pub(crate) enum RowSelectionStrategy {
77 Selectors,
79 Mask,
81}
82
83impl RowSelectionStrategy {
84 pub(crate) fn build_cursor(
87 self,
88 selection: RowSelection,
89 loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
90 ) -> RowSelectionCursor {
91 match self {
92 Self::Mask => RowSelectionCursor::new_mask_from_buffer(
93 selection.into_boolean_buffer(),
94 loaded_row_ranges,
95 ),
96 Self::Selectors => RowSelectionCursor::new_selectors(selection.into()),
97 }
98 }
99}
100
101#[derive(Debug)]
107pub enum RowSelectionCursor {
108 All,
110 Mask(MaskCursor),
112 Selectors(SelectorsCursor),
114}
115
116impl RowSelectionCursor {
117 pub(crate) fn new_mask_from_buffer(
119 mask: BooleanBuffer,
120 loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
121 ) -> Self {
122 debug_assert!(
123 mask.is_empty() || mask.value(mask.len() - 1),
124 "Mask selections must not end with a skip"
125 );
126 Self::Mask(MaskCursor {
127 mask,
128 position: 0,
129 loaded_row_ranges,
130 })
131 }
132
133 pub(crate) fn new_selectors(selectors: Vec<RowSelector>) -> Self {
135 Self::Selectors(SelectorsCursor {
136 selectors: selectors.into(),
137 position: 0,
138 })
139 }
140
141 pub(crate) fn new_all() -> Self {
143 Self::All
144 }
145}
146
147#[derive(Debug)]
152pub struct SelectorsCursor {
153 selectors: VecDeque<RowSelector>,
154 position: usize,
156}
157
158impl SelectorsCursor {
159 pub fn is_empty(&self) -> bool {
161 self.selectors.is_empty()
162 }
163
164 pub(crate) fn next_selector(&mut self) -> RowSelector {
166 let selector = self.selectors.pop_front().unwrap();
167 self.position += selector.row_count;
168 selector
169 }
170
171 pub(crate) fn return_selector(&mut self, selector: RowSelector) {
173 self.position = self.position.saturating_sub(selector.row_count);
174 self.selectors.push_front(selector);
175 }
176}
177
178#[derive(Debug)]
203pub struct MaskCursor {
204 mask: BooleanBuffer,
205 position: usize,
207 loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
209}
210
211impl MaskCursor {
212 pub fn is_empty(&self) -> bool {
214 self.position >= self.mask.len()
215 }
216
217 pub fn next_mask_chunk(&mut self, batch_size: usize) -> Option<MaskChunk> {
219 if self.is_empty() {
220 return None;
221 }
222
223 Some(self.next_mask_chunk_non_empty(batch_size))
224 }
225
226 fn next_mask_chunk_non_empty(&mut self, batch_size: usize) -> MaskChunk {
228 debug_assert!(!self.is_empty());
229 let mask_start = self.next_selected_row().unwrap_or(self.mask.len());
230 debug_assert!(
231 mask_start < self.mask.len(),
232 "ReadPlan must remove trailing skips from Mask selections"
233 );
234 self.scan_chunk(mask_start, self.mask.len(), batch_size)
235 }
236
237 fn next_selected_row(&self) -> Option<usize> {
239 let chunks = BitChunks::new(
240 self.mask.values(),
241 self.mask.offset() + self.position,
242 self.mask.len() - self.position,
243 );
244 chunks.iter_padded().enumerate().find_map(|(idx, word)| {
245 (word != 0).then(|| self.position + idx * 64 + word.trailing_zeros() as usize)
246 })
247 }
248
249 #[inline(never)]
254 fn scan_chunk(&mut self, mask_start: usize, end: usize, batch_size: usize) -> MaskChunk {
255 let chunks = BitChunks::new(
256 self.mask.values(),
257 self.mask.offset() + mask_start,
258 end - mask_start,
259 );
260 let mut selected_rows = 0;
261 let mut chunk_end = mask_start;
262
263 if batch_size > 0 {
264 for (idx, mut word) in chunks.iter_padded().enumerate() {
265 let count = word.count_ones() as usize;
266 if count == 0 {
267 continue;
268 }
269
270 let remaining = batch_size - selected_rows;
271 let last_bit = if count <= remaining {
272 selected_rows += count;
273 63 - word.leading_zeros() as usize
274 } else {
275 for _ in 1..remaining {
277 word &= word - 1;
278 }
279 selected_rows = batch_size;
280 word.trailing_zeros() as usize
281 };
282 chunk_end = mask_start + idx * 64 + last_bit + 1;
283 if selected_rows == batch_size {
284 break;
285 }
286 }
287 }
288
289 let initial_skip = mask_start - self.position;
290 self.position = chunk_end;
293 MaskChunk {
294 initial_skip,
295 chunk_rows: chunk_end - mask_start,
296 selected_rows,
297 mask_start,
298 }
299 }
300
301 pub(crate) fn next_chunk(&mut self, batch_size: usize) -> Result<MaskChunk, ParquetError> {
310 debug_assert!(batch_size > 0);
311 debug_assert!(!self.is_empty());
312
313 if self.loaded_row_ranges.is_none() {
314 return Ok(self.next_mask_chunk_non_empty(batch_size));
315 }
316
317 let mask_start = self.next_selected_row().ok_or_else(|| {
318 ParquetError::General(
319 "Internal Error: Mask cursor reached the end without finding a selected row; \
320 ReadPlan must remove trailing skips"
321 .to_string(),
322 )
323 })?;
324
325 let loaded_range_end = self
326 .loaded_row_ranges
327 .as_ref()
328 .and_then(|ranges| ranges.end_containing(mask_start))
329 .ok_or_else(|| {
330 ParquetError::General(format!(
331 "Internal Error: selected row {mask_start} has no loaded page range"
332 ))
333 })?;
334
335 Ok(self.scan_chunk(
336 mask_start,
337 loaded_range_end.min(self.mask.len()),
338 batch_size,
339 ))
340 }
341
342 pub fn mask_values_for(&self, chunk: &MaskChunk) -> Result<BooleanArray, ParquetError> {
344 if chunk.mask_start.saturating_add(chunk.chunk_rows) > self.mask.len() {
345 return Err(ParquetError::General(
346 "Internal Error: MaskChunk exceeds mask length".to_string(),
347 ));
348 }
349 Ok(BooleanArray::from(
350 self.mask.slice(chunk.mask_start, chunk.chunk_rows),
351 ))
352 }
353}
354
355#[derive(Debug)]
357pub struct MaskChunk {
358 pub initial_skip: usize,
360 pub chunk_rows: usize,
362 pub selected_rows: usize,
364 pub mask_start: usize,
366}
367
368#[derive(Clone, Debug)]
370pub(crate) struct LoadedRowRanges(Vec<Range<usize>>);
371
372impl LoadedRowRanges {
373 pub(crate) fn from_selection(selection: RowSelection) -> Self {
374 let selectors: Vec<RowSelector> = selection.into();
375 let mut position = 0;
376 let ranges = selectors
377 .into_iter()
378 .filter_map(|selector| {
379 let start = position;
380 position += selector.row_count;
381 (!selector.skip).then_some(start..position)
382 })
383 .collect();
384 Self(ranges)
385 }
386
387 fn end_containing(&self, row: usize) -> Option<usize> {
388 let idx = self.0.partition_point(|range| range.end <= row);
389 self.0
390 .get(idx)
391 .filter(|range| range.start <= row)
392 .map(|range| range.end)
393 }
394
395 #[cfg(test)]
396 pub(crate) fn ranges(&self) -> &[Range<usize>] {
397 &self.0
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 fn assert_mask_chunks(mask: BooleanBuffer, loaded: Option<LoadedRowRanges>, batch_size: usize) {
409 let selected: Vec<_> = (0..mask.len()).filter(|&row| mask.value(row)).collect();
410 let loaded = loaded.map(Arc::new);
411 let selection = RowSelection::from_boolean_buffer(mask).trim();
412 let RowSelectionCursor::Mask(mut cursor) =
413 RowSelectionStrategy::Mask.build_cursor(selection, loaded.clone())
414 else {
415 unreachable!()
416 };
417 let mut next = 0;
418 let mut position = 0;
419 while next < selected.len() {
420 let first = selected[next];
421 let end = loaded
422 .as_ref()
423 .map_or(usize::MAX, |ranges| ranges.end_containing(first).unwrap());
424 let count = selected[next..]
425 .iter()
426 .take(batch_size)
427 .take_while(|&&row| row < end)
428 .count();
429 let last = selected[next + count - 1];
430 let chunk = cursor.next_chunk(batch_size).unwrap();
431 assert_eq!(chunk.initial_skip, first - position);
432 assert_eq!(chunk.mask_start, first);
433 assert_eq!(chunk.chunk_rows, last + 1 - first);
434 assert_eq!(chunk.selected_rows, count);
435 assert_eq!(cursor.position, last + 1);
436 let values = cursor.mask_values_for(&chunk).unwrap();
437 assert_eq!(values.true_count(), count);
438 assert!(values.value(0));
439 assert!(values.value(values.len() - 1));
440 next += count;
441 position = last + 1;
442 }
443 assert!(cursor.is_empty());
444 assert!(cursor.next_mask_chunk(batch_size).is_none());
445 }
446
447 #[test]
448 fn test_mask_chunks_sparse_aligned_words() {
449 let mask = BooleanBuffer::from_iter((0..256).map(|row| row == 0 || row == 255));
452 assert_mask_chunks(mask, None, 1);
453 }
454
455 #[test]
456 fn test_mask_chunks_word_boundaries_and_offsets() {
457 use rand::{RngExt, SeedableRng, rngs::StdRng};
458
459 for len in [0, 1, 7, 8, 63, 64, 65, 127, 128, 129, 511, 2049] {
460 for offset in [0, 1, 3, 7, 8, 31, 63, 64, 65] {
461 for pattern in 0..7 {
462 let mut rng = StdRng::seed_from_u64(42);
463 let mask = BooleanBuffer::from_iter((0..offset + len + 64).map(|row| {
464 if !(offset..offset + len).contains(&row) {
465 return true;
468 }
469 let row = row - offset;
470 match pattern {
471 0 => false,
472 1 => true,
473 2 => row % 2 == 0,
474 3 => row % 129 == 128,
475 4 => rng.random_bool(0.1),
476 5 => rng.random_bool(0.5),
477 _ => rng.random_bool(0.9),
478 }
479 }))
480 .slice(offset, len);
481 for batch_size in [1, 2, 3, 63, 64, 65, 1024] {
482 assert_mask_chunks(mask.clone(), None, batch_size);
483 }
484 }
485 }
486 }
487 }
488
489 #[test]
490 fn test_mask_chunks_loaded_ranges_word_boundaries() {
491 for page_rows in [1, 31, 63, 64, 65, 127, 257] {
492 let len = page_rows * 7 + 13;
493 let ranges: Vec<_> = (0..len)
494 .step_by(page_rows)
495 .enumerate()
496 .filter(|(page, _)| page % 3 != 1)
497 .map(|(_, start)| start..(start + page_rows).min(len))
498 .collect();
499 let loaded = LoadedRowRanges::from_selection(RowSelection::from_consecutive_ranges(
500 ranges.iter().cloned(),
501 len,
502 ));
503 for offset in [0, 1, 3, 7, 31, 63, 64, 65] {
504 for pattern in 0..4 {
505 let mask = BooleanBuffer::from_iter((0..offset + len + 64).map(|row| {
506 if !(offset..offset + len).contains(&row) {
507 return true;
508 }
509 let row = row - offset;
510 ranges.iter().any(|range| range.contains(&row))
511 && match pattern {
512 0 => true,
513 1 => row % 2 == 0,
514 2 => row % page_rows == 0,
516 _ => {
519 row % page_rows == page_rows - 1
520 || row % 64 == 0
521 || row % 64 == 63
522 }
523 }
524 }))
525 .slice(offset, len);
526 for batch_size in [1, 2, 3, 63, 64, 65, 1024] {
527 assert_mask_chunks(mask.clone(), Some(loaded.clone()), batch_size);
528 }
529 }
530 }
531 }
532 }
533
534 #[test]
535 fn test_mask_chunk_does_not_read_selected_rows_outside_loaded_range() {
536 let mut cursor = MaskCursor {
537 mask: BooleanBuffer::new_set(130),
538 position: 0,
539 loaded_row_ranges: Some(Arc::new(LoadedRowRanges(vec![0..65, 66..130]))),
540 };
541 let chunk = cursor.next_chunk(1024).unwrap();
542 assert_eq!(chunk.chunk_rows, 65);
543 assert_eq!(chunk.selected_rows, 65);
544 assert_eq!(cursor.position, 65);
545 let err = cursor.next_chunk(1024).unwrap_err();
546 assert!(
547 err.to_string()
548 .contains("selected row 65 has no loaded page range")
549 );
550 assert_eq!(cursor.position, 65);
551 }
552
553 #[test]
554 fn selection_respects_policy() {
555 let mask = BooleanBuffer::from(vec![true, true, false, false, true, true, false, false]);
556 for (policy, expect_mask) in [
557 (RowSelectionPolicy::Mask, true),
558 (RowSelectionPolicy::Selectors, false),
559 (RowSelectionPolicy::Auto { threshold: 2 }, false),
560 (RowSelectionPolicy::Auto { threshold: 3 }, true),
561 ] {
562 for selection in [
563 RowSelection::from_boolean_buffer(mask.clone()),
564 RowSelection::from(vec![
565 RowSelector::select(2),
566 RowSelector::skip(2),
567 RowSelector::select(2),
568 RowSelector::skip(2),
569 ]),
570 ] {
571 let selection = policy.apply(selection);
572 assert_eq!(selection.as_mask().is_some(), expect_mask, "{policy:?}");
573 assert_eq!(selection.into_boolean_buffer(), mask);
574 }
575 }
576 }
577
578 #[test]
579 fn adaptive_selection_uses_selectors_for_long_runs() {
580 let mask = BooleanBuffer::from_iter((0..1_024).map(|idx| (256..768).contains(&idx)));
581 let selection =
582 RowSelectionPolicy::default().apply(RowSelection::from_boolean_buffer(mask));
583 assert!(selection.as_mask().is_none());
584 }
585
586 #[test]
587 fn adaptive_selection_keeps_fragmented_masks() {
588 let mask = BooleanBuffer::from_iter((0..1_024).map(|idx| idx % 2 == 0));
589 let selection =
590 RowSelectionPolicy::default().apply(RowSelection::from_boolean_buffer(mask));
591 assert!(selection.as_mask().is_some());
592 }
593
594 #[test]
595 fn test_loaded_mask_chunk_stops_at_trimmed_mask_end() {
596 let loaded = LoadedRowRanges::from_selection(RowSelection::from_consecutive_ranges(
597 std::iter::once(0..5),
598 10,
599 ));
600 let RowSelectionCursor::Mask(mut cursor) = RowSelectionStrategy::Mask.build_cursor(
601 RowSelection::from(vec![RowSelector::select(1)]),
602 Some(loaded.into()),
603 ) else {
604 unreachable!()
605 };
606
607 let chunk = cursor.next_chunk(10).unwrap();
608 assert_eq!(chunk.chunk_rows, 1);
609 assert!(cursor.is_empty());
610 }
611
612 #[test]
613 fn test_next_mask_chunk_until_cursor_is_empty() {
614 let RowSelectionCursor::Mask(mut cursor) = RowSelectionStrategy::Mask.build_cursor(
615 RowSelection::from(vec![
616 RowSelector::skip(2),
617 RowSelector::select(2),
618 RowSelector::skip(1),
619 RowSelector::select(1),
620 ]),
621 None,
622 ) else {
623 unreachable!()
624 };
625
626 let first = cursor.next_mask_chunk(2).unwrap();
627 assert_eq!(first.initial_skip, 2);
628 assert_eq!(first.chunk_rows, 2);
629 assert_eq!(first.selected_rows, 2);
630
631 let second = cursor.next_mask_chunk(2).unwrap();
632 assert_eq!(second.initial_skip, 1);
633 assert_eq!(second.chunk_rows, 1);
634 assert_eq!(second.selected_rows, 1);
635
636 assert!(cursor.next_mask_chunk(2).is_none());
637 }
638}