Skip to main content

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