use super::boolean::boolean_mask_from_selectors;
use super::{RowSelection, RowSelector};
use crate::errors::ParquetError;
use arrow_array::BooleanArray;
use arrow_buffer::BooleanBuffer;
use std::collections::VecDeque;
use std::ops::Range;
use std::sync::Arc;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RowSelectionPolicy {
Selectors,
Mask,
Auto {
threshold: usize,
},
}
impl Default for RowSelectionPolicy {
fn default() -> Self {
Self::Auto { threshold: 32 }
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum RowSelectionStrategy {
Selectors,
Mask,
}
#[derive(Debug)]
pub enum RowSelectionCursor {
All,
Mask(MaskCursor),
Selectors(SelectorsCursor),
}
impl RowSelectionCursor {
pub(crate) fn new_mask_from_selectors(
selectors: Vec<RowSelector>,
loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
) -> Self {
debug_assert!(
selectors
.last()
.map(|selector| !selector.skip)
.unwrap_or(true),
"Mask selectors must not end with a skip"
);
Self::Mask(MaskCursor {
mask: boolean_mask_from_selectors(&selectors),
position: 0,
loaded_row_ranges,
})
}
pub(crate) fn new_mask_from_buffer(
mask: BooleanBuffer,
loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
) -> Self {
debug_assert!(
mask.is_empty() || mask.value(mask.len() - 1),
"Mask selections must not end with a skip"
);
Self::Mask(MaskCursor {
mask,
position: 0,
loaded_row_ranges,
})
}
pub(crate) fn new_selectors(selectors: Vec<RowSelector>) -> Self {
Self::Selectors(SelectorsCursor {
selectors: selectors.into(),
position: 0,
})
}
pub(crate) fn new_all() -> Self {
Self::All
}
}
#[derive(Debug)]
pub struct SelectorsCursor {
selectors: VecDeque<RowSelector>,
position: usize,
}
impl SelectorsCursor {
pub fn is_empty(&self) -> bool {
self.selectors.is_empty()
}
pub(crate) fn selectors_mut(&mut self) -> &mut VecDeque<RowSelector> {
&mut self.selectors
}
pub(crate) fn next_selector(&mut self) -> RowSelector {
let selector = self.selectors.pop_front().unwrap();
self.position += selector.row_count;
selector
}
pub(crate) fn return_selector(&mut self, selector: RowSelector) {
self.position = self.position.saturating_sub(selector.row_count);
self.selectors.push_front(selector);
}
}
#[derive(Debug)]
pub struct MaskCursor {
mask: BooleanBuffer,
position: usize,
loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
}
impl MaskCursor {
pub fn is_empty(&self) -> bool {
self.position >= self.mask.len()
}
pub fn next_mask_chunk(&mut self, batch_size: usize) -> Option<MaskChunk> {
if self.is_empty() {
return None;
}
Some(self.next_mask_chunk_non_empty(batch_size))
}
fn next_mask_chunk_non_empty(&mut self, batch_size: usize) -> MaskChunk {
debug_assert!(!self.is_empty());
let (initial_skip, chunk_rows, selected_rows, mask_start, end_position) = {
let mask = &self.mask;
let start_position = self.position;
let mut cursor = start_position;
let mut initial_skip = 0;
while cursor < mask.len() && !mask.value(cursor) {
initial_skip += 1;
cursor += 1;
}
debug_assert!(
cursor < mask.len(),
"ReadPlan must remove trailing skips from Mask selections"
);
let mask_start = cursor;
let mut chunk_rows = 0;
let mut selected_rows = 0;
while cursor < mask.len() && selected_rows < batch_size {
chunk_rows += 1;
if mask.value(cursor) {
selected_rows += 1;
}
cursor += 1;
}
(initial_skip, chunk_rows, selected_rows, mask_start, cursor)
};
self.position = end_position;
MaskChunk {
initial_skip,
chunk_rows,
selected_rows,
mask_start,
}
}
pub(crate) fn next_chunk(&mut self, batch_size: usize) -> Result<MaskChunk, ParquetError> {
debug_assert!(batch_size > 0);
debug_assert!(!self.is_empty());
if self.loaded_row_ranges.is_none() {
return Ok(self.next_mask_chunk_non_empty(batch_size));
}
let start_position = self.position;
let mut cursor = start_position;
while cursor < self.mask.len() && !self.mask.value(cursor) {
cursor += 1;
}
debug_assert!(
cursor < self.mask.len(),
"ReadPlan must remove trailing skips from Mask selections"
);
let loaded_range_end = self
.loaded_row_ranges
.as_ref()
.and_then(|ranges| ranges.end_containing(cursor))
.ok_or_else(|| {
ParquetError::General(format!(
"Internal Error: selected row {cursor} has no loaded page range"
))
})?;
let mask_start = cursor;
let mut selected_rows = 0;
while cursor < loaded_range_end && cursor < self.mask.len() && selected_rows < batch_size {
if self.mask.value(cursor) {
selected_rows += 1;
}
cursor += 1;
}
self.position = cursor;
Ok(MaskChunk {
initial_skip: mask_start - start_position,
chunk_rows: cursor - mask_start,
selected_rows,
mask_start,
})
}
pub fn mask_values_for(&self, chunk: &MaskChunk) -> Result<BooleanArray, ParquetError> {
if chunk.mask_start.saturating_add(chunk.chunk_rows) > self.mask.len() {
return Err(ParquetError::General(
"Internal Error: MaskChunk exceeds mask length".to_string(),
));
}
Ok(BooleanArray::from(
self.mask.slice(chunk.mask_start, chunk.chunk_rows),
))
}
}
#[derive(Debug)]
pub struct MaskChunk {
pub initial_skip: usize,
pub chunk_rows: usize,
pub selected_rows: usize,
pub mask_start: usize,
}
#[derive(Clone, Debug)]
pub(crate) struct LoadedRowRanges(Vec<Range<usize>>);
impl LoadedRowRanges {
pub(crate) fn from_selection(selection: RowSelection) -> Self {
let selectors: Vec<RowSelector> = selection.into();
let mut position = 0;
let ranges = selectors
.into_iter()
.filter_map(|selector| {
let start = position;
position += selector.row_count;
(!selector.skip).then_some(start..position)
})
.collect();
Self(ranges)
}
fn end_containing(&self, row: usize) -> Option<usize> {
let idx = self.0.partition_point(|range| range.end <= row);
self.0
.get(idx)
.filter(|range| range.start <= row)
.map(|range| range.end)
}
#[cfg(test)]
pub(crate) fn ranges(&self) -> &[Range<usize>] {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_loaded_mask_chunk_stops_at_trimmed_mask_end() {
let loaded = LoadedRowRanges::from_selection(RowSelection::from_consecutive_ranges(
std::iter::once(0..5),
10,
));
let RowSelectionCursor::Mask(mut cursor) = RowSelectionCursor::new_mask_from_selectors(
vec![RowSelector::select(1)],
Some(loaded.into()),
) else {
unreachable!()
};
let chunk = cursor.next_chunk(10).unwrap();
assert_eq!(chunk.chunk_rows, 1);
assert!(cursor.is_empty());
}
#[test]
fn test_next_mask_chunk_until_cursor_is_empty() {
let RowSelectionCursor::Mask(mut cursor) = RowSelectionCursor::new_mask_from_selectors(
vec![
RowSelector::skip(2),
RowSelector::select(2),
RowSelector::skip(1),
RowSelector::select(1),
],
None,
) else {
unreachable!()
};
let first = cursor.next_mask_chunk(2).unwrap();
assert_eq!(first.initial_skip, 2);
assert_eq!(first.chunk_rows, 2);
assert_eq!(first.selected_rows, 2);
let second = cursor.next_mask_chunk(2).unwrap();
assert_eq!(second.initial_skip, 1);
assert_eq!(second.chunk_rows, 1);
assert_eq!(second.selected_rows, 1);
assert!(cursor.next_mask_chunk(2).is_none());
}
}