Skip to main content

parquet/arrow/arrow_reader/selection/
ranges.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5//! Mapping row selections onto ranges: the byte ranges
6//! of the data pages that must be fetched ([`RowSelection::scan_ranges`]) and
7//! the expansion of a selection to batch boundaries.
8//!
9//! Page selection checks bitmap-backed selections directly, while batch
10//! expansion streams their runs using [`MaskRunIter`].
11//!
12//! [`MaskRunIter`]: crate::arrow::arrow_reader::MaskRunIter
13
14use super::{RowSelection, RowSelector};
15use crate::file::page_index::offset_index::PageLocation;
16use arrow_buffer::BooleanBuffer;
17use arrow_buffer::bit_chunk_iterator::BitChunks;
18use std::ops::Range;
19
20/// Checks each page for a selected bit without expanding the mask into runs.
21pub(super) fn scan_ranges_from_mask(
22    mask: &BooleanBuffer,
23    page_locations: &[PageLocation],
24) -> Vec<Range<u64>> {
25    let mut ranges = Vec::new();
26    let mut row_start = 0;
27    for (idx, page) in page_locations.iter().enumerate() {
28        if row_start >= mask.len() {
29            break;
30        }
31        let row_end = page_locations
32            .get(idx + 1)
33            .map_or(mask.len(), |next| next.first_row_index as usize)
34            .min(mask.len());
35
36        let chunks = BitChunks::new(
37            mask.values(),
38            mask.offset() + row_start,
39            row_end - row_start,
40        );
41        if chunks.iter_padded().any(|word| word != 0) {
42            let start = page.offset as u64;
43            ranges.push(start..start + page.compressed_page_size as u64);
44        }
45        row_start = row_end;
46    }
47    ranges
48}
49
50/// Byte ranges of the data pages containing at least one selected row.
51#[inline]
52pub(super) fn scan_ranges_from_selectors<I>(
53    selectors: I,
54    page_locations: &[PageLocation],
55) -> Vec<Range<u64>>
56where
57    I: IntoIterator<Item = RowSelector>,
58{
59    let mut ranges: Vec<Range<u64>> = vec![];
60    let mut row_offset = 0;
61
62    let mut pages = page_locations.iter().peekable();
63    let mut selectors = selectors.into_iter();
64    let mut current_selector = selectors.next();
65    let mut current_page = pages.next();
66
67    let mut current_page_included = false;
68
69    while let Some((selector, page)) = current_selector.as_mut().zip(current_page) {
70        if !(selector.skip || current_page_included) {
71            let start = page.offset as u64;
72            let end = start + page.compressed_page_size as u64;
73            ranges.push(start..end);
74            current_page_included = true;
75        }
76
77        if let Some(next_page) = pages.peek() {
78            if row_offset + selector.row_count > next_page.first_row_index as usize {
79                let remaining_in_page = next_page.first_row_index as usize - row_offset;
80                selector.row_count -= remaining_in_page;
81                row_offset += remaining_in_page;
82                current_page = pages.next();
83                current_page_included = false;
84            } else {
85                if row_offset + selector.row_count == next_page.first_row_index as usize {
86                    current_page = pages.next();
87                    current_page_included = false;
88                }
89                row_offset += selector.row_count;
90                current_selector = selectors.next();
91            }
92        } else {
93            current_selector = selectors.next()
94        }
95    }
96
97    ranges
98}
99
100/// Grows each selected run to the batch boundaries containing it, merging
101/// overlapping or adjacent ranges.
102#[inline]
103pub(super) fn expand_to_batch_boundaries_from_selectors<I>(
104    selectors: I,
105    batch_size: usize,
106    total_rows: usize,
107) -> RowSelection
108where
109    I: IntoIterator<Item = RowSelector>,
110{
111    let mut ranges: Vec<Range<usize>> = Vec::new();
112    let mut row_offset = 0;
113
114    // Selectors advance in row order, so expanded ranges can be merged as we go.
115    for selector in selectors {
116        let row_start = row_offset;
117        row_offset += selector.row_count;
118        if selector.skip {
119            continue;
120        }
121
122        // Round the start down and the end up to cover complete batches.
123        let batch_start = (row_start / batch_size) * batch_size;
124        let batch_end = row_offset.next_multiple_of(batch_size).min(total_rows);
125
126        // Ends are non-decreasing because selectors advance in row order.
127        match ranges.last_mut() {
128            // The ranges overlap or touch, so extend the last range.
129            Some(last) if batch_start <= last.end => last.end = batch_end,
130            // There is a gap, or this is the first selected range.
131            _ => ranges.push(batch_start..batch_end),
132        }
133    }
134
135    RowSelection::from_consecutive_ranges(ranges.into_iter(), total_rows)
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    fn page_locations(starts: impl IntoIterator<Item = usize>) -> Vec<PageLocation> {
143        starts
144            .into_iter()
145            .enumerate()
146            .map(|(idx, row)| PageLocation {
147                offset: (idx * 23 + 5) as i64,
148                compressed_page_size: (idx % 13 + 1) as i32,
149                first_row_index: row as i64,
150            })
151            .collect()
152    }
153
154    fn assert_mask_scan_ranges(
155        mask: &BooleanBuffer,
156        offset: usize,
157        pages: &[PageLocation],
158        total_rows: usize,
159    ) {
160        // Build the reference from individual bits, independent of word iterators.
161        let selectors = RowSelection::from_consecutive_ranges(
162            mask.iter()
163                .enumerate()
164                .filter_map(|(row, selected)| selected.then_some(row..row + 1)),
165            mask.len(),
166        );
167        // Set padding bits to ensure they cannot cause extra pages to load.
168        let padded = BooleanBuffer::from_iter(
169            std::iter::repeat_n(true, offset)
170                .chain(mask.iter())
171                .chain(std::iter::repeat_n(true, 64)),
172        );
173        let selection = RowSelection::from_boolean_buffer(padded.slice(offset, mask.len()));
174        assert_eq!(
175            selection.scan_ranges(pages),
176            selectors.scan_ranges(pages),
177            "mask={mask:?}, offset={offset}, pages={pages:?}"
178        );
179        assert_eq!(
180            selection.row_ranges_for_selected_pages(pages, total_rows),
181            selectors.row_ranges_for_selected_pages(pages, total_rows)
182        );
183        assert!(selection.scan_ranges(&[]).is_empty());
184    }
185
186    #[test]
187    fn test_mask_scan_ranges_matches_selectors() {
188        use rand::{RngExt, SeedableRng, rngs::StdRng};
189
190        for len in [0, 1, 7, 8, 63, 64, 65, 127, 128, 129, 511, 2049] {
191            for page_rows in [1, 7, 31, 63, 64, 65, 127, 257] {
192                // Include pages beyond the selection and a final partial page.
193                let total_rows = len + 37;
194                let pages = page_locations((0..total_rows).step_by(page_rows));
195                for pattern in 0..7 {
196                    let mut rng = StdRng::seed_from_u64(42);
197                    let mask = BooleanBuffer::from_iter((0..len).map(|row| match pattern {
198                        0 => false,
199                        1 => true,
200                        2 => row % 2 == 0,
201                        3 => row % page_rows == page_rows - 1,
202                        4 => rng.random_bool(0.01),
203                        5 => rng.random_bool(0.5),
204                        _ => rng.random_bool(0.99),
205                    }));
206                    for offset in [0, 1, 3, 7, 31, 63, 64, 65] {
207                        assert_mask_scan_ranges(&mask, offset, &pages, total_rows);
208                    }
209                }
210            }
211        }
212    }
213
214    #[test]
215    fn test_mask_scan_ranges_single_bits_and_padding() {
216        let pages = page_locations([0, 1, 130, 257, 386, 512, 549]);
217        for len in [130, 257, 511, 512] {
218            let selected_rows = std::iter::once(None).chain(
219                [
220                    0, 1, 7, 8, 55, 56, 63, 64, 65, 127, 128, 129, 130, 255, 256, 257, 510, 511,
221                ]
222                .into_iter()
223                .filter(|&row| row < len)
224                .map(Some),
225            );
226            for selected_row in selected_rows {
227                let mask = BooleanBuffer::from_iter((0..len).map(|row| selected_row == Some(row)));
228                for offset in [0, 1, 7, 8, 31, 63, 64, 65] {
229                    assert_mask_scan_ranges(&mask, offset, &pages, 600);
230                }
231            }
232        }
233    }
234
235    #[test]
236    fn test_scan_ranges() {
237        let index = vec![
238            PageLocation {
239                offset: 0,
240                compressed_page_size: 10,
241                first_row_index: 0,
242            },
243            PageLocation {
244                offset: 10,
245                compressed_page_size: 10,
246                first_row_index: 10,
247            },
248            PageLocation {
249                offset: 20,
250                compressed_page_size: 10,
251                first_row_index: 20,
252            },
253            PageLocation {
254                offset: 30,
255                compressed_page_size: 10,
256                first_row_index: 30,
257            },
258            PageLocation {
259                offset: 40,
260                compressed_page_size: 10,
261                first_row_index: 40,
262            },
263            PageLocation {
264                offset: 50,
265                compressed_page_size: 10,
266                first_row_index: 50,
267            },
268            PageLocation {
269                offset: 60,
270                compressed_page_size: 10,
271                first_row_index: 60,
272            },
273        ];
274
275        let selection = RowSelection::from(vec![
276            // Skip first page
277            RowSelector::skip(10),
278            // Multiple selects in same page
279            RowSelector::select(3),
280            RowSelector::skip(3),
281            RowSelector::select(4),
282            // Select to page boundary
283            RowSelector::skip(5),
284            RowSelector::select(5),
285            // Skip full page past page boundary
286            RowSelector::skip(12),
287            // Select across page boundaries
288            RowSelector::select(12),
289            // Skip final page
290            RowSelector::skip(12),
291        ]);
292
293        let ranges = selection.scan_ranges(&index);
294
295        // assert_eq!(mask, vec![false, true, true, false, true, true, false]);
296        assert_eq!(ranges, vec![10..20, 20..30, 40..50, 50..60]);
297        assert_eq!(
298            selection.row_ranges_for_selected_pages(&index, 70),
299            vec![10..20, 20..30, 40..50, 50..60]
300        );
301
302        let selection = RowSelection::from(vec![
303            // Skip first page
304            RowSelector::skip(10),
305            // Multiple selects in same page
306            RowSelector::select(3),
307            RowSelector::skip(3),
308            RowSelector::select(4),
309            // Select to page boundary
310            RowSelector::skip(5),
311            RowSelector::select(5),
312            // Skip full page past page boundary
313            RowSelector::skip(12),
314            // Select across page boundaries
315            RowSelector::select(12),
316            RowSelector::skip(1),
317            // Select across page boundaries including final page
318            RowSelector::select(8),
319        ]);
320
321        let ranges = selection.scan_ranges(&index);
322
323        // assert_eq!(mask, vec![false, true, true, false, true, true, true]);
324        assert_eq!(ranges, vec![10..20, 20..30, 40..50, 50..60, 60..70]);
325
326        let selection = RowSelection::from(vec![
327            // Skip first page
328            RowSelector::skip(10),
329            // Multiple selects in same page
330            RowSelector::select(3),
331            RowSelector::skip(3),
332            RowSelector::select(4),
333            // Select to page boundary
334            RowSelector::skip(5),
335            RowSelector::select(5),
336            // Skip full page past page boundary
337            RowSelector::skip(12),
338            // Select to final page boundary
339            RowSelector::select(12),
340            RowSelector::skip(1),
341            // Skip across final page boundary
342            RowSelector::skip(8),
343            // Select from final page
344            RowSelector::select(4),
345        ]);
346
347        let ranges = selection.scan_ranges(&index);
348
349        // assert_eq!(mask, vec![false, true, true, false, true, true, true]);
350        assert_eq!(ranges, vec![10..20, 20..30, 40..50, 50..60, 60..70]);
351
352        let selection = RowSelection::from(vec![
353            // Skip first page
354            RowSelector::skip(10),
355            // Multiple selects in same page
356            RowSelector::select(3),
357            RowSelector::skip(3),
358            RowSelector::select(4),
359            // Select to remaining in page and first row of next page
360            RowSelector::skip(5),
361            RowSelector::select(6),
362            // Skip remaining
363            RowSelector::skip(50),
364        ]);
365
366        let ranges = selection.scan_ranges(&index);
367
368        // assert_eq!(mask, vec![false, true, true, false, true, true, true]);
369        assert_eq!(ranges, vec![10..20, 20..30, 30..40]);
370    }
371}