Skip to main content

parquet/util/
push_buffers.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
18use crate::errors::ParquetError;
19use crate::file::reader::{ChunkReader, Length};
20use bytes::Bytes;
21use std::fmt::Display;
22use std::ops::Range;
23
24/// Holds multiple non-contiguous, caller-provided buffers of file data.
25///
26/// This is the in-memory buffer used by the push-based Parquet decoders
27/// (`ParquetPushDecoder` and `ParquetMetaDataPushDecoder`). It can be
28/// constructed up front and handed to a builder so the decoder reuses bytes
29/// that have already been fetched.
30///
31/// Features:
32/// 1. Zero copy
33/// 2. non contiguous ranges of bytes
34///
35/// # Non Coalescing
36///
37/// This buffer does not coalesce  (merging adjacent ranges of bytes into a
38/// single range). Coalescing at this level would require copying the data but
39/// the caller may already have the needed data in a single buffer which would
40/// require no copying.
41///
42/// Thus, the implementation defers to the caller to coalesce subsequent requests
43/// if desired.
44///
45/// # Ordering
46///
47/// The buffers are sorted by the start of their range. Thus, lookups use a
48/// binary search, not a scan of all buffers. Pushed ranges can overlap.
49#[derive(Debug, Clone, Default)]
50pub struct PushBuffers {
51    /// the virtual "offset" of this buffers (added to any request)
52    offset: u64,
53    /// The total length of the file being decoded
54    file_len: u64,
55    /// The ranges of data that are available for decoding (not adjusted for
56    /// offset), sorted by `start`
57    ranges: Vec<Range<u64>>,
58    /// The buffers of data that can be used to decode the Parquet file, in the
59    /// same order as `ranges`
60    buffers: Vec<Bytes>,
61    /// The length of the longest range in `ranges`.
62    ///
63    /// This keeps lookups fast in the common case: buffers that do not
64    /// overlap and have similar lengths. Pushed ranges can overlap, so a
65    /// lookup cannot stop at the nearest buffer. `max_len` tells the lookup
66    /// when no earlier buffer can reach the requested range, so it checks one
67    /// or two buffers, not all of them. See [`Self::find`].
68    max_len: u64,
69    /// The sum of the lengths of `ranges`, kept up to date so that
70    /// [`Self::buffered_bytes`] does not scan all ranges.
71    buffered_bytes: u64,
72}
73
74impl Display for PushBuffers {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        writeln!(
77            f,
78            "Buffers (offset: {}, file_len: {})",
79            self.offset, self.file_len
80        )?;
81        writeln!(f, "Available Ranges (w/ offset):")?;
82        for range in &self.ranges {
83            writeln!(
84                f,
85                "  {}..{} ({}..{}): {} bytes",
86                range.start,
87                range.end,
88                range.start + self.offset,
89                range.end + self.offset,
90                range.end - range.start
91            )?;
92        }
93
94        Ok(())
95    }
96}
97
98impl PushBuffers {
99    /// Create a new, empty `PushBuffers` for a file of the given length.
100    ///
101    /// Use [`PushBuffers::default`] when the file length is unknown or
102    /// irrelevant (e.g. the push decoder, which tracks ranges by absolute
103    /// offset and never consults `file_len`).
104    pub fn new(file_len: u64) -> Self {
105        Self {
106            offset: 0,
107            file_len,
108            ranges: Vec::new(),
109            buffers: Vec::new(),
110            max_len: 0,
111            buffered_bytes: 0,
112        }
113    }
114
115    /// Push all the ranges and buffers
116    ///
117    /// # Errors
118    /// Returns an error if the number of ranges does not match the number of
119    /// buffers, or if any buffer's length does not match its range (see
120    /// [`Self::push_range`]).
121    pub fn push_ranges(
122        &mut self,
123        ranges: Vec<Range<u64>>,
124        buffers: Vec<Bytes>,
125    ) -> Result<(), ParquetError> {
126        if ranges.len() != buffers.len() {
127            return Err(general_err!(
128                "Number of ranges ({}) must match number of buffers ({})",
129                ranges.len(),
130                buffers.len()
131            ));
132        }
133        for (range, buffer) in ranges.into_iter().zip(buffers) {
134            self.push_range(range, buffer)?;
135        }
136        Ok(())
137    }
138
139    /// Push a new range and its associated buffer
140    ///
141    /// # Errors
142    /// Returns an error if the buffer's length does not match the range's
143    /// length, e.g. when a truncated (short) read is pushed.
144    pub fn push_range(&mut self, range: Range<u64>, buffer: Bytes) -> Result<(), ParquetError> {
145        let expected = range.end.saturating_sub(range.start);
146        if expected != buffer.len() as u64 {
147            return Err(general_err!(
148                "Buffer length ({}) does not match length ({}) of range {}..{}",
149                buffer.len(),
150                expected,
151                range.start,
152                range.end
153            ));
154        }
155        // Insert after all buffers that start at or before `range.start`.
156        // Thus, ranges pushed in file order go at the end.
157        let idx = self.ranges.partition_point(|r| r.start <= range.start);
158        self.max_len = self.max_len.max(expected);
159        self.buffered_bytes += expected;
160        self.ranges.insert(idx, range);
161        self.buffers.insert(idx, buffer);
162        Ok(())
163    }
164
165    /// Returns true if the Buffers contains data for the given range
166    pub(crate) fn has_range(&self, range: &Range<u64>) -> bool {
167        self.find(range.start, range.end).is_some()
168    }
169
170    /// Returns the index of a buffer that contains all bytes of `start..end`,
171    /// if any.
172    fn find(&self, start: u64, end: u64) -> Option<usize> {
173        // Common case: the buffers do not overlap. Then only the last buffer
174        // that starts at or before `start` can contain `start..end`:
175        //
176        //   buffers:  0..25    ├─────────┤
177        //             25..50             ├─────────┤
178        //             50..75                       ├─────────┤
179        //             75..100                                ├─────────┤
180        //   find:     55..70                         ├─────┤  only 50..75 can contain it
181        //
182        // But pushed buffers can overlap. Then a buffer that starts much
183        // earlier can be the one that contains `start..end`:
184        //
185        //   buffers:  0..100   ├───────────────────────────────────────┤
186        //             50..60                       ├───┤
187        //             55..58                         ├┤
188        //   find:     55..90                         ├─────────────┤  only 0..100 contains it
189        //
190        // Thus, scan back from the last buffer that starts at or before
191        // `start`. Without a limit, a lookup that finds nothing scans all
192        // earlier buffers. `max_len` gives the limit: a buffer that starts
193        // more than `max_len` bytes before `end` ends before `end`, and so do
194        // all buffers before it. Stop there.
195        //
196        // In the common case the scan stops after one or two buffers. It is
197        // long only if a caller pushes one large buffer and then many small
198        // buffers after its start.
199        let candidates = self.ranges.partition_point(|r| r.start <= start);
200        self.ranges[..candidates]
201            .iter()
202            .enumerate()
203            .rev()
204            .take_while(|(_, r)| r.start.saturating_add(self.max_len) >= end)
205            .find(|(_, r)| r.end >= end)
206            .map(|(idx, _)| idx)
207    }
208
209    /// return the file length of the Parquet file being read
210    pub(crate) fn file_len(&self) -> u64 {
211        self.file_len
212    }
213
214    /// Specify a new offset
215    fn with_offset(mut self, offset: u64) -> Self {
216        self.offset = offset;
217        self
218    }
219
220    /// Return the total length of all buffered ranges.
221    ///
222    /// This counts the bytes that the decoder can still read, not the
223    /// allocated memory. [`Self::release_ranges`] keeps the remaining parts of
224    /// a buffer as slices of the pushed [`Bytes`], and the allocation of a
225    /// pushed [`Bytes`] is freed only after all of its parts are released.
226    #[cfg(feature = "arrow")]
227    pub(crate) fn buffered_bytes(&self) -> u64 {
228        self.buffered_bytes
229    }
230
231    /// Clear any range and corresponding buffer that is exactly in the ranges_to_clear
232    #[cfg(feature = "arrow")]
233    pub(crate) fn clear_ranges(&mut self, ranges_to_clear: &[Range<u64>]) {
234        // Use `(start, end)` tuples because `Range` is not `Ord`
235        let mut clear: Vec<(u64, u64)> = ranges_to_clear.iter().map(|r| (r.start, r.end)).collect();
236        if clear.is_empty() {
237            return;
238        }
239        clear.sort_unstable();
240        let mut ranges = Vec::with_capacity(self.ranges.len());
241        let mut buffers = Vec::with_capacity(self.buffers.len());
242        for (range, buffer) in self.ranges.drain(..).zip(self.buffers.drain(..)) {
243            if clear.binary_search(&(range.start, range.end)).is_err() {
244                ranges.push(range);
245                buffers.push(buffer);
246            }
247        }
248        self.ranges = ranges;
249        self.buffers = buffers;
250        self.buffered_bytes = self
251            .ranges
252            .iter()
253            .map(|r| r.end.saturating_sub(r.start))
254            .sum();
255        self.update_max_len();
256    }
257
258    /// Set `max_len` to the maximum length of the remaining ranges.
259    ///
260    /// A `max_len` that is too large is still correct, lookups only scan
261    /// further. This update is for performance: a large buffer that was
262    /// removed must not slow down later lookups.
263    #[cfg(feature = "arrow")]
264    fn update_max_len(&mut self) {
265        self.max_len = self
266            .ranges
267            .iter()
268            .map(|r| r.end.saturating_sub(r.start))
269            .max()
270            .unwrap_or(0);
271    }
272
273    /// Remove all buffered bytes in `ranges`.
274    ///
275    /// A buffer that overlaps a range is trimmed or split, and the parts
276    /// outside the range are kept. The kept parts are zero-copy slices, which
277    /// record the bytes that the decoder can still read. Thus, the allocator
278    /// frees the memory of a pushed [`Bytes`] only after all of its parts are
279    /// removed.
280    ///
281    /// The buffers stay sorted by start, which lookups require (see
282    /// [`Self::find`]). If no buffer overlaps `ranges`, the buffers do not
283    /// change.
284    #[cfg(feature = "arrow")]
285    pub(crate) fn release_ranges(&mut self, ranges: &[Range<u64>]) {
286        let release = merge_ranges(ranges);
287        if release.is_empty() {
288            return;
289        }
290        // Most calls release ranges that are no longer buffered. Return
291        // before reallocating the buffers.
292        let overlaps = |range: &Range<u64>| {
293            !range.is_empty() && {
294                let first = release.partition_point(|r| r.end <= range.start);
295                release.get(first).is_some_and(|r| r.start < range.end)
296            }
297        };
298        if !self.ranges.iter().any(overlaps) {
299            return;
300        }
301        // Trim the buffers that overlap a released range in place, and do not
302        // clone or move the others. The parts after the first part of a split
303        // buffer go to `split`. A buffer that is released entirely becomes
304        // empty, and is removed below.
305        let mut split = vec![];
306        let mut emptied = false;
307        for (range, buffer) in self.ranges.iter_mut().zip(self.buffers.iter_mut()) {
308            if range.is_empty() {
309                emptied = true;
310                continue;
311            }
312            if !overlaps(range) {
313                continue;
314            }
315            let whole = range.clone();
316            let offset = |pos: u64| (pos - whole.start) as usize;
317            // The parts of `whole` between the released ranges.
318            let first = release.partition_point(|r| r.end <= whole.start);
319            let mut kept = 0;
320            let mut first_part = None;
321            let mut keep = |part: Range<u64>| {
322                kept += part.end - part.start;
323                if first_part.is_none() {
324                    first_part = Some(part);
325                } else {
326                    let data = buffer.slice(offset(part.start)..offset(part.end));
327                    split.push((part, data));
328                }
329            };
330            let mut start = whole.start;
331            for r in release[first..].iter().take_while(|r| r.start < whole.end) {
332                if start < r.start {
333                    keep(start..r.start);
334                }
335                start = r.end;
336            }
337            if start < whole.end {
338                keep(start..whole.end);
339            }
340            self.buffered_bytes -= (whole.end - whole.start) - kept;
341            match first_part {
342                Some(part) => {
343                    buffer.truncate(offset(part.end));
344                    let _ = buffer.split_to(offset(part.start));
345                    *range = part;
346                }
347                None => {
348                    *buffer = Bytes::new();
349                    *range = whole.start..whole.start;
350                    emptied = true;
351                }
352            }
353        }
354        if emptied {
355            // Remove the released buffers. As before, this also removes empty
356            // buffers that were pushed. `retain` visits each element exactly
357            // once in the original order, so both calls keep the same indices.
358            let mut keep = self.ranges.iter().map(|range| !range.is_empty());
359            self.buffers.retain(|_| keep.next().unwrap());
360            self.ranges.retain(|range| !range.is_empty());
361        }
362        for (range, buffer) in split {
363            self.ranges.push(range);
364            self.buffers.push(buffer);
365        }
366        // Split parts are appended, and if buffers overlap, the tail of a
367        // trimmed buffer can start after the start of the next buffer. The
368        // sort is stable.
369        if !self.ranges.is_sorted_by_key(|range| range.start) {
370            let mut parts: Vec<_> = std::mem::take(&mut self.ranges)
371                .into_iter()
372                .zip(std::mem::take(&mut self.buffers))
373                .collect();
374            parts.sort_by_key(|(range, _)| range.start);
375            (self.ranges, self.buffers) = parts.into_iter().unzip();
376        }
377        self.update_max_len();
378    }
379
380    /// Remove all buffered bytes outside `keep`.
381    ///
382    /// This calls [`Self::release_ranges`] with the complement of `keep`, so
383    /// the same rules apply to the kept parts.
384    #[cfg(feature = "arrow")]
385    pub(crate) fn retain_ranges(&mut self, keep: &[Range<u64>]) {
386        let mut release = vec![];
387        let mut start = 0;
388        for range in merge_ranges(keep) {
389            release.push(start..range.start);
390            start = range.end;
391        }
392        release.push(start..u64::MAX);
393        self.release_ranges(&release);
394    }
395
396    /// Clear all buffered ranges and their corresponding data
397    pub(crate) fn clear_all_ranges(&mut self) {
398        self.ranges.clear();
399        self.buffers.clear();
400        self.max_len = 0;
401        self.buffered_bytes = 0;
402    }
403
404    /// Panics if `ranges`, `buffers` and `max_len` do not agree, or if the
405    /// buffers are not sorted.
406    #[cfg(test)]
407    #[track_caller]
408    fn assert_invariants(&self) {
409        assert_eq!(self.ranges.len(), self.buffers.len());
410        assert!(
411            self.ranges.is_sorted_by_key(|r| r.start),
412            "not sorted: {:?}",
413            self.ranges
414        );
415        for (range, buffer) in self.ranges.iter().zip(&self.buffers) {
416            assert_eq!(range.end.saturating_sub(range.start), buffer.len() as u64);
417        }
418        let max_len = self
419            .ranges
420            .iter()
421            .map(|r| r.end.saturating_sub(r.start))
422            .max();
423        assert_eq!(self.max_len, max_len.unwrap_or(0));
424    }
425}
426
427/// Sort `ranges` by start, remove empty ranges, and merge ranges that
428/// overlap or are adjacent.
429#[cfg(feature = "arrow")]
430fn merge_ranges(ranges: &[Range<u64>]) -> Vec<Range<u64>> {
431    let mut sorted: Vec<Range<u64>> = ranges.iter().filter(|r| !r.is_empty()).cloned().collect();
432    sorted.sort_unstable_by_key(|r| r.start);
433    let mut merged: Vec<Range<u64>> = Vec::with_capacity(sorted.len());
434    for range in sorted {
435        match merged.last_mut() {
436            Some(last) if range.start <= last.end => last.end = last.end.max(range.end),
437            _ => merged.push(range),
438        }
439    }
440    merged
441}
442
443impl Length for PushBuffers {
444    fn len(&self) -> u64 {
445        self.file_len
446    }
447}
448
449/// less efficient implementation of Read for Buffers
450impl std::io::Read for PushBuffers {
451    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
452        // Find the range that contains the start offset
453        let found = self.find(self.offset, self.offset + buf.len() as u64);
454        if let Some(idx) = found {
455            // Found the range, figure out the starting offset in the buffer
456            let start_offset = (self.offset - self.ranges[idx].start) as usize;
457            let end_offset = start_offset + buf.len();
458            buf.copy_from_slice(&self.buffers[idx][start_offset..end_offset]);
459            // If we found the range, we can return the number of bytes read
460            // advance our offset
461            self.offset += buf.len() as u64;
462            Ok(buf.len())
463        } else {
464            Err(std::io::Error::new(
465                std::io::ErrorKind::UnexpectedEof,
466                "No data available in Buffers",
467            ))
468        }
469    }
470}
471
472impl ChunkReader for PushBuffers {
473    type T = Self;
474
475    fn get_read(&self, start: u64) -> Result<Self::T, ParquetError> {
476        Ok(self.clone().with_offset(self.offset + start))
477    }
478
479    fn get_bytes(&self, start: u64, length: usize) -> Result<Bytes, ParquetError> {
480        // find the range that contains the start offset
481        if let Some(idx) = self.find(start, start + length as u64) {
482            // Found the range, figure out the starting offset in the buffer
483            let start_offset = (start - self.ranges[idx].start) as usize;
484            return Ok(self.buffers[idx].slice(start_offset..start_offset + length));
485        }
486        // Signal that we need more data
487        let requested_end = start + length as u64;
488        Err(ParquetError::NeedMoreDataRange(start..requested_end))
489    }
490}
491
492#[cfg(test)]
493mod tests {
494    use super::*;
495
496    /// Release one range.
497    #[cfg(feature = "arrow")]
498    fn release(buffers: &mut PushBuffers, range: Range<u64>) {
499        buffers.release_ranges(std::slice::from_ref(&range));
500        buffers.assert_invariants();
501    }
502
503    /// The buffered ranges and their data.
504    #[cfg(feature = "arrow")]
505    fn buffered_parts(buffers: &PushBuffers) -> Vec<(Range<u64>, Bytes)> {
506        let ranges = buffers.ranges.iter().cloned();
507        ranges.zip(buffers.buffers.iter().cloned()).collect()
508    }
509
510    #[test]
511    #[cfg(feature = "arrow")]
512    fn release_ranges_without_overlap_keeps_buffers() {
513        let mut buffers = PushBuffers::new(100);
514        buffers
515            .push_range(20..24, Bytes::from_static(b"abcd"))
516            .unwrap();
517        buffers
518            .push_range(0..10, Bytes::from_static(b"0123456789"))
519            .unwrap();
520
521        release(&mut buffers, 90..95);
522        release(&mut buffers, 10..20);
523        assert_eq!(buffers.ranges, vec![0..10, 20..24]);
524        assert_eq!(buffers.buffered_bytes(), 14);
525
526        release(&mut buffers, 22..24);
527        assert_eq!(buffers.ranges, vec![0..10, 20..22]);
528        assert_eq!(buffers.buffered_bytes(), 12);
529
530        buffers.clear_all_ranges();
531        assert_eq!(buffers.buffered_bytes(), 0);
532    }
533
534    #[test]
535    #[cfg(feature = "arrow")]
536    fn release_ranges_trims_and_splits_buffers() {
537        let mut buffers = PushBuffers::new(100);
538        buffers
539            .push_range(0..10, Bytes::from_static(b"0123456789"))
540            .unwrap();
541        buffers
542            .push_range(20..24, Bytes::from_static(b"abcd"))
543            .unwrap();
544
545        // Split the first buffer, and do not change the second buffer.
546        release(&mut buffers, 3..5);
547        assert_eq!(buffers.buffered_bytes(), 12);
548        assert!(buffers.has_range(&(0..3)));
549        assert!(buffers.has_range(&(5..10)));
550        assert!(!buffers.has_range(&(3..4)));
551        assert_eq!(
552            buffers.get_bytes(5, 5).unwrap(),
553            Bytes::from_static(b"56789")
554        );
555        assert!(buffers.has_range(&(20..24)));
556
557        // One range that overlaps two buffers trims both of them.
558        release(&mut buffers, 8..22);
559        assert_eq!(buffers.buffered_bytes(), 3 + 3 + 2);
560        assert_eq!(buffers.get_bytes(5, 3).unwrap(), Bytes::from_static(b"567"));
561        assert_eq!(buffers.get_bytes(22, 2).unwrap(), Bytes::from_static(b"cd"));
562
563        // Ranges in any order, adjacent or empty, and bytes that are not
564        // buffered.
565        buffers.release_ranges(&[50..60, 1..2, 0..1, 7..7]);
566        assert_eq!(buffers.buffered_bytes(), 1 + 3 + 2);
567        assert_eq!(buffers.get_bytes(2, 1).unwrap(), Bytes::from_static(b"2"));
568
569        release(&mut buffers, 0..100);
570        assert_eq!(buffers.buffered_bytes(), 0);
571    }
572
573    #[test]
574    #[cfg(feature = "arrow")]
575    fn release_ranges_removes_exact_and_overlapping_buffers() {
576        let mut buffers = PushBuffers::new(100);
577        for range in [10..20, 0..30, 10..15, 40..50] {
578            let data = Bytes::from(vec![0u8; (range.end - range.start) as usize]);
579            buffers.push_range(range, data).unwrap();
580        }
581        buffers.release_ranges(&[40..50, 10..15]);
582        buffers.assert_invariants();
583        assert_eq!(buffers.buffered_bytes(), 10 + 5 + 15);
584        assert!(buffers.has_range(&(0..10)));
585        assert!(buffers.has_range(&(15..30)));
586        assert!(!buffers.has_range(&(10..15)));
587        assert!(!buffers.has_range(&(40..50)));
588    }
589
590    #[test]
591    #[cfg(feature = "arrow")]
592    fn retain_ranges_keeps_only_the_given_bytes() {
593        let mut buffers = PushBuffers::new(100);
594        buffers
595            .push_range(0..10, Bytes::from_static(b"0123456789"))
596            .unwrap();
597        buffers
598            .push_range(20..24, Bytes::from_static(b"abcd"))
599            .unwrap();
600        buffers
601            .push_range(30..32, Bytes::from_static(b"xy"))
602            .unwrap();
603        // Ranges in any order, overlapping, and outside the buffers.
604        buffers.retain_ranges(&[22..40, 2..4, 3..5, 8..9]);
605        assert_eq!(buffers.buffered_bytes(), 3 + 1 + 2 + 2);
606        assert_eq!(buffers.get_bytes(2, 3).unwrap(), Bytes::from_static(b"234"));
607        assert_eq!(buffers.get_bytes(8, 1).unwrap(), Bytes::from_static(b"8"));
608        assert!(!buffers.has_range(&(5..6)));
609        assert_eq!(buffers.get_bytes(22, 2).unwrap(), Bytes::from_static(b"cd"));
610        assert_eq!(buffers.get_bytes(30, 2).unwrap(), Bytes::from_static(b"xy"));
611        buffers.retain_ranges(&[]);
612        assert_eq!(buffers.buffered_bytes(), 0);
613    }
614
615    /// Lookups stay correct when the kept parts of overlapping buffers
616    /// interleave.
617    #[test]
618    #[cfg(feature = "arrow")]
619    fn retain_ranges_with_overlapping_buffers() {
620        let mut buffers = PushBuffers::new(100);
621        for range in [0..100, 3..70] {
622            let data = Bytes::from(vec![0u8; (range.end - range.start) as usize]);
623            buffers.push_range(range, data).unwrap();
624        }
625        buffers.retain_ranges(&[0..5, 50..60]);
626        buffers.assert_invariants();
627        assert_eq!(buffers.buffered_bytes(), 5 + 2 + 10 + 10);
628        assert!(buffers.has_range(&(0..5)));
629        assert!(buffers.has_range(&(50..60)));
630        assert!(!buffers.has_range(&(5..50)));
631    }
632
633    #[test]
634    fn push_range_accepts_matching_length() {
635        let mut buffers = PushBuffers::new(100);
636        buffers
637            .push_range(10..14, Bytes::from_static(b"abcd"))
638            .unwrap();
639        assert!(buffers.has_range(&(10..14)));
640    }
641
642    #[test]
643    fn push_range_rejects_short_buffer() {
644        let mut buffers = PushBuffers::new(100);
645        let err = buffers
646            .push_range(10..20, Bytes::from_static(b"abcd"))
647            .unwrap_err();
648        assert_eq!(
649            err.to_string(),
650            "Parquet error: Buffer length (4) does not match length (10) of range 10..20"
651        );
652        assert!(!buffers.has_range(&(10..20)));
653    }
654
655    /// The bytes of a fake file: byte `i` is `i % 251`, so any slice of it is
656    /// easy to build and to check.
657    fn file_bytes(range: Range<u64>) -> Bytes {
658        range.map(|i| (i % 251) as u8).collect::<Vec<u8>>().into()
659    }
660
661    fn push(buffers: &mut PushBuffers, range: Range<u64>) {
662        buffers
663            .push_range(range.clone(), file_bytes(range))
664            .unwrap();
665    }
666
667    /// Checks the invariants, and that each buffer still has the bytes of its
668    /// range.
669    #[track_caller]
670    fn assert_valid(buffers: &PushBuffers) {
671        buffers.assert_invariants();
672        for (range, buffer) in buffers.ranges.iter().zip(&buffers.buffers) {
673            assert_eq!(*buffer, file_bytes(range.clone()));
674        }
675    }
676
677    #[test]
678    fn overlapping_pushes_find_the_containing_buffer() {
679        let mut buffers = PushBuffers::new(1000);
680        // Small buffers inside a large one start closer to most offsets.
681        // The pushes are not in file order: `assert_valid` checks the sort.
682        for range in [50..60, 0..100, 55..58, 10..20] {
683            push(&mut buffers, range);
684        }
685        assert_valid(&buffers);
686        // 55..90 starts in 50..60 and 55..58, but only 0..100 contains it.
687        assert_eq!(buffers.get_bytes(55, 35).unwrap(), file_bytes(55..90));
688        assert!(buffers.has_range(&(0..100)));
689        assert!(buffers.has_range(&(99..100)));
690        assert!(!buffers.has_range(&(99..101)));
691        assert!(matches!(
692            buffers.get_bytes(90, 20),
693            Err(ParquetError::NeedMoreDataRange(r)) if r == (90..110)
694        ));
695
696        // `Read` finds the same buffer.
697        let mut reader = buffers.get_read(56).unwrap();
698        let mut out = [0u8; 30];
699        std::io::Read::read_exact(&mut reader, &mut out).unwrap();
700        assert_eq!(&out[..], &file_bytes(56..86)[..]);
701    }
702
703    #[test]
704    #[cfg(feature = "arrow")]
705    fn clear_ranges_drops_exact_matches_only() {
706        let mut buffers = PushBuffers::new(1000);
707        for range in [10..20, 0..30, 10..15, 40..50] {
708            push(&mut buffers, range);
709        }
710        buffers.clear_ranges(&[40..50, 10..15, 5..30]);
711        assert_valid(&buffers);
712        assert_eq!(buffers.ranges, vec![0..30, 10..20]);
713    }
714
715    /// Random pushes, clears and lookups, compared with a list that is
716    /// scanned in full for each lookup.
717    #[test]
718    #[cfg(feature = "arrow")]
719    fn fuzz_matches_a_linear_scan() {
720        use rand::rngs::StdRng;
721        use rand::{RngExt, SeedableRng};
722
723        fn random_range(rng: &mut StdRng) -> Range<u64> {
724            let start = rng.random_range(0..500);
725            let len = [0, 1, 5, 20, 100, 300][rng.random_range(0..6)];
726            start..start + len
727        }
728
729        for seed in 0..200 {
730            let mut rng = StdRng::seed_from_u64(seed);
731            let mut buffers = PushBuffers::new(1000);
732            let mut model: Vec<Range<u64>> = vec![];
733            for _ in 0..60 {
734                match rng.random_range(0..10) {
735                    0..4 => {
736                        let range = random_range(&mut rng);
737                        push(&mut buffers, range.clone());
738                        model.push(range);
739                    }
740                    4..6 => {
741                        // Clear some pushed ranges and some other ranges.
742                        let mut clear: Vec<_> = (0..rng.random_range(1..4))
743                            .map(|_| random_range(&mut rng))
744                            .collect();
745                        if !model.is_empty() {
746                            clear.push(model[rng.random_range(0..model.len())].clone());
747                        }
748                        buffers.clear_ranges(&clear);
749                        model.retain(|r| !clear.contains(r));
750                    }
751                    _ => {
752                        let range = random_range(&mut rng);
753                        let expected = model
754                            .iter()
755                            .any(|r| r.start <= range.start && r.end >= range.end);
756                        assert_eq!(buffers.has_range(&range), expected, "seed {seed} {range:?}");
757                        if expected {
758                            let len = (range.end - range.start) as usize;
759                            assert_eq!(
760                                buffers.get_bytes(range.start, len).unwrap(),
761                                file_bytes(range.clone())
762                            );
763                        }
764                    }
765                }
766                assert_valid(&buffers);
767                let mut actual = buffers.ranges.clone();
768                actual.sort_by_key(|r| (r.start, r.end));
769                model.sort_by_key(|r| (r.start, r.end));
770                assert_eq!(actual, model, "seed {seed}");
771            }
772        }
773    }
774
775    #[test]
776    fn push_ranges_rejects_mismatched_counts() {
777        let mut buffers = PushBuffers::new(100);
778        let err = buffers
779            .push_ranges(vec![0..4, 4..8], vec![Bytes::from_static(b"abcd")])
780            .unwrap_err();
781        assert_eq!(
782            err.to_string(),
783            "Parquet error: Number of ranges (2) must match number of buffers (1)"
784        );
785    }
786
787    /// The result of `release_ranges`, computed by rebuilding all buffers.
788    #[cfg(feature = "arrow")]
789    fn release_by_rebuild(
790        buffers: &PushBuffers,
791        release: &[Range<u64>],
792    ) -> Vec<(Range<u64>, Bytes)> {
793        let release = merge_ranges(release);
794        let mut parts = vec![];
795        for (range, buffer) in &buffered_parts(buffers) {
796            let mut start = range.start;
797            let mut keep = |part: Range<u64>| {
798                let offset = |pos: u64| (pos - range.start) as usize;
799                parts.push((
800                    part.clone(),
801                    buffer.slice(offset(part.start)..offset(part.end)),
802                ));
803            };
804            for r in release
805                .iter()
806                .filter(|r| r.start < range.end && r.end > range.start)
807            {
808                if start < r.start {
809                    keep(start..r.start);
810                }
811                start = start.max(r.end);
812            }
813            if start < range.end {
814                keep(start..range.end);
815            }
816        }
817        parts.sort_by_key(|(range, _)| range.start);
818        parts
819    }
820
821    #[test]
822    #[cfg(feature = "arrow")]
823    #[expect(clippy::reversed_empty_ranges)]
824    fn release_ranges_splits_and_removes_empty_buffers() {
825        let mut buffers = PushBuffers::new(100);
826        buffers
827            .push_range(0..20, Bytes::from_static(b"abcdefghijklmnopqrst"))
828            .unwrap();
829        // An empty and an inverted range, which `push_range` accepts.
830        buffers.push_range(30..30, Bytes::new()).unwrap();
831        buffers.push_range(10..5, Bytes::new()).unwrap();
832
833        // Split one buffer into three parts.
834        buffers.release_ranges(&[3..5, 8..10]);
835        assert_eq!(buffers.ranges, vec![0..3, 5..8, 10..20]);
836        assert_eq!(buffers.buffered_bytes(), 16);
837        assert_eq!(buffers.get_bytes(5, 3).unwrap(), Bytes::from_static(b"fgh"));
838
839        // Release a whole buffer.
840        release(&mut buffers, 5..8);
841        assert_eq!(buffers.ranges, vec![0..3, 10..20]);
842        assert_eq!(buffers.buffered_bytes(), 13);
843    }
844
845    #[test]
846    #[cfg(feature = "arrow")]
847    fn fuzz_release_ranges_matches_rebuild() {
848        use rand::{RngExt, SeedableRng, rngs::StdRng};
849        let data: Vec<u8> = (0..=255).collect();
850        let mut rng = StdRng::seed_from_u64(42);
851        for _ in 0..2000 {
852            let mut buffers = PushBuffers::new(256);
853            for _ in 0..rng.random_range(0..6) {
854                let start = rng.random_range(0..100u64);
855                if rng.random_bool(0.1) {
856                    // An inverted range, which `push_range` accepts.
857                    let end = start.saturating_sub(rng.random_range(1..5u64));
858                    buffers.push_range(start..end, Bytes::new()).unwrap();
859                    continue;
860                }
861                let end = start + rng.random_range(0..20u64);
862                let bytes = Bytes::copy_from_slice(&data[start as usize..end as usize]);
863                buffers.push_range(start..end, bytes).unwrap();
864            }
865            for _ in 0..3 {
866                let release: Vec<_> = (0..rng.random_range(0..4))
867                    .map(|_| {
868                        let start = rng.random_range(0..110u64);
869                        start..start + rng.random_range(0..15u64)
870                    })
871                    .collect();
872                let overlaps = buffers.ranges.iter().any(|range| {
873                    !range.is_empty()
874                        && release
875                            .iter()
876                            .any(|r| !r.is_empty() && r.start < range.end && r.end > range.start)
877                });
878                let before = buffered_parts(&buffers);
879                let expected = release_by_rebuild(&buffers, &release);
880
881                buffers.release_ranges(&release);
882                buffers.assert_invariants();
883                let actual = buffered_parts(&buffers);
884                if overlaps {
885                    // The order of buffers with the same start is not
886                    // specified.
887                    let by_range = |mut parts: Vec<(Range<u64>, Bytes)>| {
888                        parts.sort_by_key(|(range, _)| (range.start, range.end));
889                        parts
890                    };
891                    assert_eq!(
892                        by_range(actual.clone()),
893                        by_range(expected),
894                        "release {release:?} of {before:?}"
895                    );
896                } else {
897                    assert_eq!(actual, before, "release {release:?} of {before:?}");
898                }
899                for (range, buffer) in actual.iter().filter(|(range, _)| !range.is_empty()) {
900                    assert_eq!(
901                        &data[range.start as usize..range.end as usize],
902                        buffer.as_ref()
903                    );
904                }
905                let total: u64 = actual
906                    .iter()
907                    .map(|(r, _)| r.end.saturating_sub(r.start))
908                    .sum();
909                assert_eq!(buffers.buffered_bytes(), total);
910            }
911        }
912    }
913}