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#[derive(Debug, Clone, Default)]
45pub struct PushBuffers {
46    /// the virtual "offset" of this buffers (added to any request)
47    offset: u64,
48    /// The total length of the file being decoded
49    file_len: u64,
50    /// The ranges of data that are available for decoding (not adjusted for offset)
51    ranges: Vec<Range<u64>>,
52    /// The buffers of data that can be used to decode the Parquet file
53    buffers: Vec<Bytes>,
54}
55
56impl Display for PushBuffers {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        writeln!(
59            f,
60            "Buffers (offset: {}, file_len: {})",
61            self.offset, self.file_len
62        )?;
63        writeln!(f, "Available Ranges (w/ offset):")?;
64        for range in &self.ranges {
65            writeln!(
66                f,
67                "  {}..{} ({}..{}): {} bytes",
68                range.start,
69                range.end,
70                range.start + self.offset,
71                range.end + self.offset,
72                range.end - range.start
73            )?;
74        }
75
76        Ok(())
77    }
78}
79
80impl PushBuffers {
81    /// Create a new, empty `PushBuffers` for a file of the given length.
82    ///
83    /// Use [`PushBuffers::default`] when the file length is unknown or
84    /// irrelevant (e.g. the push decoder, which tracks ranges by absolute
85    /// offset and never consults `file_len`).
86    pub fn new(file_len: u64) -> Self {
87        Self {
88            offset: 0,
89            file_len,
90            ranges: Vec::new(),
91            buffers: Vec::new(),
92        }
93    }
94
95    /// Push all the ranges and buffers
96    ///
97    /// # Errors
98    /// Returns an error if the number of ranges does not match the number of
99    /// buffers, or if any buffer's length does not match its range (see
100    /// [`Self::push_range`]).
101    pub fn push_ranges(
102        &mut self,
103        ranges: Vec<Range<u64>>,
104        buffers: Vec<Bytes>,
105    ) -> Result<(), ParquetError> {
106        if ranges.len() != buffers.len() {
107            return Err(general_err!(
108                "Number of ranges ({}) must match number of buffers ({})",
109                ranges.len(),
110                buffers.len()
111            ));
112        }
113        for (range, buffer) in ranges.into_iter().zip(buffers) {
114            self.push_range(range, buffer)?;
115        }
116        Ok(())
117    }
118
119    /// Push a new range and its associated buffer
120    ///
121    /// # Errors
122    /// Returns an error if the buffer's length does not match the range's
123    /// length, e.g. when a truncated (short) read is pushed.
124    pub fn push_range(&mut self, range: Range<u64>, buffer: Bytes) -> Result<(), ParquetError> {
125        let expected = range.end.saturating_sub(range.start);
126        if expected != buffer.len() as u64 {
127            return Err(general_err!(
128                "Buffer length ({}) does not match length ({}) of range {}..{}",
129                buffer.len(),
130                expected,
131                range.start,
132                range.end
133            ));
134        }
135        self.ranges.push(range);
136        self.buffers.push(buffer);
137        Ok(())
138    }
139
140    /// Returns true if the Buffers contains data for the given range
141    pub(crate) fn has_range(&self, range: &Range<u64>) -> bool {
142        self.ranges
143            .iter()
144            .any(|r| r.start <= range.start && r.end >= range.end)
145    }
146
147    fn iter(&self) -> impl Iterator<Item = (&Range<u64>, &Bytes)> {
148        self.ranges.iter().zip(self.buffers.iter())
149    }
150
151    /// return the file length of the Parquet file being read
152    pub(crate) fn file_len(&self) -> u64 {
153        self.file_len
154    }
155
156    /// Specify a new offset
157    fn with_offset(mut self, offset: u64) -> Self {
158        self.offset = offset;
159        self
160    }
161
162    /// Return the total of all buffered ranges
163    #[cfg(feature = "arrow")]
164    pub(crate) fn buffered_bytes(&self) -> u64 {
165        self.ranges.iter().map(|r| r.end - r.start).sum()
166    }
167
168    /// Clear any range and corresponding buffer that is exactly in the ranges_to_clear
169    #[cfg(feature = "arrow")]
170    pub(crate) fn clear_ranges(&mut self, ranges_to_clear: &[Range<u64>]) {
171        let mut new_ranges = Vec::new();
172        let mut new_buffers = Vec::new();
173
174        for (range, buffer) in self.iter() {
175            if !ranges_to_clear
176                .iter()
177                .any(|r| r.start == range.start && r.end == range.end)
178            {
179                new_ranges.push(range.clone());
180                new_buffers.push(buffer.clone());
181            }
182        }
183        self.ranges = new_ranges;
184        self.buffers = new_buffers;
185    }
186
187    /// Clear all buffered ranges and their corresponding data
188    pub(crate) fn clear_all_ranges(&mut self) {
189        self.ranges.clear();
190        self.buffers.clear();
191    }
192}
193
194impl Length for PushBuffers {
195    fn len(&self) -> u64 {
196        self.file_len
197    }
198}
199
200/// less efficient implementation of Read for Buffers
201impl std::io::Read for PushBuffers {
202    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
203        // Find the range that contains the start offset
204        let mut found = false;
205        for (range, data) in self.iter() {
206            if range.start <= self.offset && range.end >= self.offset + buf.len() as u64 {
207                // Found the range, figure out the starting offset in the buffer
208                let start_offset = (self.offset - range.start) as usize;
209                let end_offset = start_offset + buf.len();
210                let slice = data.slice(start_offset..end_offset);
211                buf.copy_from_slice(slice.as_ref());
212                found = true;
213                break;
214            }
215        }
216        if found {
217            // If we found the range, we can return the number of bytes read
218            // advance our offset
219            self.offset += buf.len() as u64;
220            Ok(buf.len())
221        } else {
222            Err(std::io::Error::new(
223                std::io::ErrorKind::UnexpectedEof,
224                "No data available in Buffers",
225            ))
226        }
227    }
228}
229
230impl ChunkReader for PushBuffers {
231    type T = Self;
232
233    fn get_read(&self, start: u64) -> Result<Self::T, ParquetError> {
234        Ok(self.clone().with_offset(self.offset + start))
235    }
236
237    fn get_bytes(&self, start: u64, length: usize) -> Result<Bytes, ParquetError> {
238        // find the range that contains the start offset
239        for (range, data) in self.iter() {
240            if range.start <= start && range.end >= start + length as u64 {
241                // Found the range, figure out the starting offset in the buffer
242                let start_offset = (start - range.start) as usize;
243                return Ok(data.slice(start_offset..start_offset + length));
244            }
245        }
246        // Signal that we need more data
247        let requested_end = start + length as u64;
248        Err(ParquetError::NeedMoreDataRange(start..requested_end))
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn push_range_accepts_matching_length() {
258        let mut buffers = PushBuffers::new(100);
259        buffers
260            .push_range(10..14, Bytes::from_static(b"abcd"))
261            .unwrap();
262        assert!(buffers.has_range(&(10..14)));
263    }
264
265    #[test]
266    fn push_range_rejects_short_buffer() {
267        let mut buffers = PushBuffers::new(100);
268        let err = buffers
269            .push_range(10..20, Bytes::from_static(b"abcd"))
270            .unwrap_err();
271        assert_eq!(
272            err.to_string(),
273            "Parquet error: Buffer length (4) does not match length (10) of range 10..20"
274        );
275        assert!(!buffers.has_range(&(10..20)));
276    }
277
278    #[test]
279    fn push_ranges_rejects_mismatched_counts() {
280        let mut buffers = PushBuffers::new(100);
281        let err = buffers
282            .push_ranges(vec![0..4, 4..8], vec![Bytes::from_static(b"abcd")])
283            .unwrap_err();
284        assert_eq!(
285            err.to_string(),
286            "Parquet error: Number of ranges (2) must match number of buffers (1)"
287        );
288    }
289}