parquet/arrow/arrow_reader/selection/
cursor.rs1use super::boolean::boolean_mask_from_selectors;
26use super::{RowSelection, RowSelector};
27use crate::errors::ParquetError;
28use arrow_array::BooleanArray;
29use arrow_buffer::BooleanBuffer;
30use std::collections::VecDeque;
31use std::ops::Range;
32use std::sync::Arc;
33
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum RowSelectionPolicy {
37 Selectors,
39 Mask,
41 Auto {
43 threshold: usize,
45 },
46}
47
48impl Default for RowSelectionPolicy {
49 fn default() -> Self {
50 Self::Auto { threshold: 32 }
51 }
52}
53
54#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub(crate) enum RowSelectionStrategy {
60 Selectors,
62 Mask,
64}
65
66#[derive(Debug)]
72pub enum RowSelectionCursor {
73 All,
75 Mask(MaskCursor),
77 Selectors(SelectorsCursor),
79}
80
81impl RowSelectionCursor {
82 pub(crate) fn new_mask_from_selectors(
84 selectors: Vec<RowSelector>,
85 loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
86 ) -> Self {
87 debug_assert!(
88 selectors
89 .last()
90 .map(|selector| !selector.skip)
91 .unwrap_or(true),
92 "Mask selectors must not end with a skip"
93 );
94 Self::Mask(MaskCursor {
95 mask: boolean_mask_from_selectors(&selectors),
96 position: 0,
97 loaded_row_ranges,
98 })
99 }
100
101 pub(crate) fn new_mask_from_buffer(
103 mask: BooleanBuffer,
104 loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
105 ) -> Self {
106 debug_assert!(
107 mask.is_empty() || mask.value(mask.len() - 1),
108 "Mask selections must not end with a skip"
109 );
110 Self::Mask(MaskCursor {
111 mask,
112 position: 0,
113 loaded_row_ranges,
114 })
115 }
116
117 pub(crate) fn new_selectors(selectors: Vec<RowSelector>) -> Self {
119 Self::Selectors(SelectorsCursor {
120 selectors: selectors.into(),
121 position: 0,
122 })
123 }
124
125 pub(crate) fn new_all() -> Self {
127 Self::All
128 }
129}
130
131#[derive(Debug)]
136pub struct SelectorsCursor {
137 selectors: VecDeque<RowSelector>,
138 position: usize,
140}
141
142impl SelectorsCursor {
143 pub fn is_empty(&self) -> bool {
145 self.selectors.is_empty()
146 }
147
148 pub(crate) fn next_selector(&mut self) -> RowSelector {
150 let selector = self.selectors.pop_front().unwrap();
151 self.position += selector.row_count;
152 selector
153 }
154
155 pub(crate) fn return_selector(&mut self, selector: RowSelector) {
157 self.position = self.position.saturating_sub(selector.row_count);
158 self.selectors.push_front(selector);
159 }
160}
161
162#[derive(Debug)]
186pub struct MaskCursor {
187 mask: BooleanBuffer,
188 position: usize,
190 loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
192}
193
194impl MaskCursor {
195 pub fn is_empty(&self) -> bool {
197 self.position >= self.mask.len()
198 }
199
200 pub fn next_mask_chunk(&mut self, batch_size: usize) -> Option<MaskChunk> {
202 if self.is_empty() {
203 return None;
204 }
205
206 Some(self.next_mask_chunk_non_empty(batch_size))
207 }
208
209 fn next_mask_chunk_non_empty(&mut self, batch_size: usize) -> MaskChunk {
211 debug_assert!(!self.is_empty());
212
213 let (initial_skip, chunk_rows, selected_rows, mask_start, end_position) = {
214 let mask = &self.mask;
215 let start_position = self.position;
216 let mut cursor = start_position;
217 let mut initial_skip = 0;
218
219 while cursor < mask.len() && !mask.value(cursor) {
220 initial_skip += 1;
221 cursor += 1;
222 }
223 debug_assert!(
224 cursor < mask.len(),
225 "ReadPlan must remove trailing skips from Mask selections"
226 );
227
228 let mask_start = cursor;
229 let mut chunk_rows = 0;
230 let mut selected_rows = 0;
231
232 while cursor < mask.len() && selected_rows < batch_size {
236 chunk_rows += 1;
237 if mask.value(cursor) {
238 selected_rows += 1;
239 }
240 cursor += 1;
241 }
242
243 (initial_skip, chunk_rows, selected_rows, mask_start, cursor)
244 };
245
246 self.position = end_position;
247
248 MaskChunk {
249 initial_skip,
250 chunk_rows,
251 selected_rows,
252 mask_start,
253 }
254 }
255
256 pub(crate) fn next_chunk(&mut self, batch_size: usize) -> Result<MaskChunk, ParquetError> {
262 debug_assert!(batch_size > 0);
263 debug_assert!(!self.is_empty());
264
265 if self.loaded_row_ranges.is_none() {
266 return Ok(self.next_mask_chunk_non_empty(batch_size));
267 }
268
269 let start_position = self.position;
270 let mut cursor = start_position;
271 while cursor < self.mask.len() && !self.mask.value(cursor) {
272 cursor += 1;
273 }
274
275 debug_assert!(
276 cursor < self.mask.len(),
277 "ReadPlan must remove trailing skips from Mask selections"
278 );
279
280 let loaded_range_end = self
281 .loaded_row_ranges
282 .as_ref()
283 .and_then(|ranges| ranges.end_containing(cursor))
284 .ok_or_else(|| {
285 ParquetError::General(format!(
286 "Internal Error: selected row {cursor} has no loaded page range"
287 ))
288 })?;
289
290 let mask_start = cursor;
291 let mut selected_rows = 0;
292 while cursor < loaded_range_end && cursor < self.mask.len() && selected_rows < batch_size {
293 if self.mask.value(cursor) {
294 selected_rows += 1;
295 }
296 cursor += 1;
297 }
298
299 self.position = cursor;
300 Ok(MaskChunk {
301 initial_skip: mask_start - start_position,
302 chunk_rows: cursor - mask_start,
303 selected_rows,
304 mask_start,
305 })
306 }
307
308 pub fn mask_values_for(&self, chunk: &MaskChunk) -> Result<BooleanArray, ParquetError> {
310 if chunk.mask_start.saturating_add(chunk.chunk_rows) > self.mask.len() {
311 return Err(ParquetError::General(
312 "Internal Error: MaskChunk exceeds mask length".to_string(),
313 ));
314 }
315 Ok(BooleanArray::from(
316 self.mask.slice(chunk.mask_start, chunk.chunk_rows),
317 ))
318 }
319}
320
321#[derive(Debug)]
323pub struct MaskChunk {
324 pub initial_skip: usize,
326 pub chunk_rows: usize,
328 pub selected_rows: usize,
330 pub mask_start: usize,
332}
333
334#[derive(Clone, Debug)]
336pub(crate) struct LoadedRowRanges(Vec<Range<usize>>);
337
338impl LoadedRowRanges {
339 pub(crate) fn from_selection(selection: RowSelection) -> Self {
340 let selectors: Vec<RowSelector> = selection.into();
341 let mut position = 0;
342 let ranges = selectors
343 .into_iter()
344 .filter_map(|selector| {
345 let start = position;
346 position += selector.row_count;
347 (!selector.skip).then_some(start..position)
348 })
349 .collect();
350 Self(ranges)
351 }
352
353 fn end_containing(&self, row: usize) -> Option<usize> {
354 let idx = self.0.partition_point(|range| range.end <= row);
355 self.0
356 .get(idx)
357 .filter(|range| range.start <= row)
358 .map(|range| range.end)
359 }
360
361 #[cfg(test)]
362 pub(crate) fn ranges(&self) -> &[Range<usize>] {
363 &self.0
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 #[test]
372 fn test_loaded_mask_chunk_stops_at_trimmed_mask_end() {
373 let loaded = LoadedRowRanges::from_selection(RowSelection::from_consecutive_ranges(
374 std::iter::once(0..5),
375 10,
376 ));
377 let RowSelectionCursor::Mask(mut cursor) = RowSelectionCursor::new_mask_from_selectors(
378 vec![RowSelector::select(1)],
379 Some(loaded.into()),
380 ) else {
381 unreachable!()
382 };
383
384 let chunk = cursor.next_chunk(10).unwrap();
385 assert_eq!(chunk.chunk_rows, 1);
386 assert!(cursor.is_empty());
387 }
388
389 #[test]
390 fn test_next_mask_chunk_until_cursor_is_empty() {
391 let RowSelectionCursor::Mask(mut cursor) = RowSelectionCursor::new_mask_from_selectors(
392 vec![
393 RowSelector::skip(2),
394 RowSelector::select(2),
395 RowSelector::skip(1),
396 RowSelector::select(1),
397 ],
398 None,
399 ) else {
400 unreachable!()
401 };
402
403 let first = cursor.next_mask_chunk(2).unwrap();
404 assert_eq!(first.initial_skip, 2);
405 assert_eq!(first.chunk_rows, 2);
406 assert_eq!(first.selected_rows, 2);
407
408 let second = cursor.next_mask_chunk(2).unwrap();
409 assert_eq!(second.initial_skip, 1);
410 assert_eq!(second.chunk_rows, 1);
411 assert_eq!(second.selected_rows, 1);
412
413 assert!(cursor.next_mask_chunk(2).is_none());
414 }
415}