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