1use crate::arrow::ProjectionMask;
19use crate::arrow::array_reader::RowGroups;
20use crate::arrow::arrow_reader::RowSelection;
21use crate::column::page::{PageIterator, PageReader};
22use crate::errors::ParquetError;
23use crate::file::metadata::page_index::RowGroupPageIndex;
24use crate::file::metadata::{ParquetMetaData, RowGroupMetaData};
25use crate::file::page_index::offset_index::PageLocation;
26use crate::file::reader::{ChunkReader, Length, SerializedPageReader};
27use bytes::{Buf, Bytes};
28use std::ops::Range;
29use std::sync::Arc;
30
31#[derive(Debug)]
33pub(crate) struct InMemoryRowGroup<'a> {
34 pub(crate) page_index: Option<RowGroupPageIndex>,
35 pub(crate) column_chunks: Vec<Option<Arc<ColumnChunkData>>>,
37 pub(crate) row_count: usize,
38 pub(crate) row_group_idx: usize,
39 pub(crate) metadata: &'a ParquetMetaData,
40}
41
42#[derive(Debug)]
44pub(crate) struct FetchRanges {
45 pub(crate) ranges: Vec<Range<u64>>,
47 pub(crate) page_start_offsets: Option<Vec<Option<Vec<u64>>>>,
50}
51
52impl InMemoryRowGroup<'_> {
53 pub(crate) fn fetch_ranges(
63 &self,
64 projection: &ProjectionMask,
65 selection: Option<&RowSelection>,
66 batch_size: usize,
67 cache_mask: Option<&ProjectionMask>,
68 ) -> FetchRanges {
69 let metadata = self.metadata.row_group(self.row_group_idx);
70 let page_index = self.page_index.as_ref().filter(|_| selection.is_some());
73 let expanded_selection = selection
74 .filter(|_| page_index.is_some() && cache_mask.is_some())
75 .map(|selection| selection.expand_to_batch_boundaries(batch_size, self.row_count));
76
77 let mut page_start_offsets: Option<Vec<Option<Vec<u64>>>> = page_index.map(|_| vec![]);
79 let mut ranges = vec![];
80 let columns = columns_to_fetch(projection, self.column_chunks.len(), |idx| {
81 self.column_chunks[idx].is_some()
82 });
83 for idx in columns {
84 let locations = page_index
85 .and_then(|page_index| page_index.offset_index(idx))
86 .map(|offset_index| offset_index.page_locations().as_slice());
87 let column_selection =
88 column_selection(selection, expanded_selection.as_ref(), cache_mask, idx);
89 let (start, len) = metadata.column(idx).byte_range();
90 let fetch = ColumnFetch::new(start..start + len, locations, column_selection);
91 let first = ranges.len();
92 ranges.extend(fetch.ranges());
93 if let Some(page_start_offsets) = page_start_offsets.as_mut() {
94 page_start_offsets.push(match fetch {
95 ColumnFetch::Chunk { .. } => None,
97 ColumnFetch::Pages { .. } => {
98 Some(ranges[first..].iter().map(|range| range.start).collect())
99 }
100 });
101 }
102 }
103 FetchRanges {
104 ranges,
105 page_start_offsets,
106 }
107 }
108
109 pub(crate) fn fill_column_chunks<I>(
114 &mut self,
115 projection: &ProjectionMask,
116 page_start_offsets: Option<Vec<Option<Vec<u64>>>>,
117 chunk_data: I,
118 ) where
119 I: IntoIterator<Item = Bytes>,
120 {
121 let mut chunk_data = chunk_data.into_iter();
122 let metadata = self.metadata.row_group(self.row_group_idx);
123 if let Some(page_start_offsets) = page_start_offsets {
124 let mut page_start_offsets = page_start_offsets.into_iter();
127
128 for (idx, chunk) in self.column_chunks.iter_mut().enumerate() {
129 if chunk.is_some() || !projection.leaf_included(idx) {
130 continue;
131 }
132
133 match page_start_offsets.next() {
134 Some(None) => {
136 if let Some(data) = chunk_data.next() {
137 *chunk = Some(Arc::new(ColumnChunkData::Dense {
138 offset: metadata.column(idx).byte_range().0 as usize,
139 data,
140 }));
141 }
142 }
143 Some(Some(offsets)) => {
144 let mut chunks = Vec::with_capacity(offsets.len());
145 for _ in 0..offsets.len() {
146 chunks.push(chunk_data.next().unwrap());
147 }
148
149 *chunk = Some(Arc::new(ColumnChunkData::Sparse {
150 length: metadata.column(idx).byte_range().1 as usize,
151 data: offsets
152 .into_iter()
153 .map(|x| x as usize)
154 .zip(chunks)
155 .collect(),
156 }))
157 }
158 None => {}
159 }
160 }
161 } else {
162 for (idx, chunk) in self.column_chunks.iter_mut().enumerate() {
163 if chunk.is_some() || !projection.leaf_included(idx) {
164 continue;
165 }
166
167 if let Some(data) = chunk_data.next() {
168 *chunk = Some(Arc::new(ColumnChunkData::Dense {
169 offset: metadata.column(idx).byte_range().0 as usize,
170 data,
171 }));
172 }
173 }
174 }
175 }
176}
177
178#[inline]
183pub(crate) fn columns_to_fetch<'a>(
184 projection: &'a ProjectionMask,
185 num_columns: usize,
186 is_read: impl Fn(usize) -> bool + 'a,
187) -> impl Iterator<Item = usize> + 'a {
188 (0..num_columns).filter(move |&idx| projection.leaf_included(idx) && !is_read(idx))
189}
190
191#[inline]
195pub(crate) fn column_selection<'a>(
196 selection: Option<&'a RowSelection>,
197 expanded_selection: Option<&'a RowSelection>,
198 cache_mask: Option<&ProjectionMask>,
199 idx: usize,
200) -> Option<&'a RowSelection> {
201 match expanded_selection {
202 Some(expanded) if cache_mask.is_some_and(|mask| mask.leaf_included(idx)) => Some(expanded),
203 _ => selection,
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
212pub(crate) enum ColumnFetch<'a> {
213 Chunk {
216 range: Range<u64>,
218 },
219 Pages {
222 dictionary: Option<Range<u64>>,
224 locations: &'a [PageLocation],
226 pages: Vec<usize>,
228 },
229}
230
231impl<'a> ColumnFetch<'a> {
232 #[inline]
236 pub(crate) fn new(
237 chunk: Range<u64>,
238 locations: Option<&'a [PageLocation]>,
239 selection: Option<&RowSelection>,
240 ) -> Self {
241 let (Some(selection), Some(locations)) = (selection, locations) else {
242 return Self::Chunk { range: chunk };
243 };
244 let fetched = selection.scan_ranges(locations);
247 let mut fetched = fetched.iter().peekable();
248 let pages = locations
249 .iter()
250 .enumerate()
251 .filter(|(_, location)| {
252 fetched
253 .next_if(|range| range.start == location.offset as u64)
254 .is_some()
255 })
256 .map(|(idx, _)| idx)
257 .collect();
258 Self::Pages {
259 dictionary: dictionary_range(chunk.start, locations),
260 locations,
261 pages,
262 }
263 }
264
265 pub(crate) fn ranges(&self) -> impl Iterator<Item = Range<u64>> + '_ {
267 let (chunk, dictionary, pages) = match self {
268 Self::Chunk { range, .. } => (Some(range.clone()), None, None),
269 Self::Pages {
270 dictionary,
271 locations,
272 pages,
273 } => (None, dictionary.clone(), Some((*locations, pages))),
274 };
275 let data_pages = pages
276 .into_iter()
277 .flat_map(|(locations, pages)| pages.iter().map(|&page| page_range(&locations[page])));
278 chunk.into_iter().chain(dictionary).chain(data_pages)
279 }
280}
281
282#[inline]
285pub(crate) fn dictionary_range(chunk_start: u64, locations: &[PageLocation]) -> Option<Range<u64>> {
286 match locations.first() {
287 Some(first) if first.offset as u64 != chunk_start => Some(chunk_start..first.offset as u64),
288 _ => None,
289 }
290}
291
292#[inline]
294pub(crate) fn page_range(location: &PageLocation) -> Range<u64> {
295 let start = location.offset as u64;
296 start..start + location.compressed_page_size as u64
297}
298
299impl RowGroups for InMemoryRowGroup<'_> {
300 fn num_rows(&self) -> usize {
301 self.row_count
302 }
303
304 fn column_chunks(&self, i: usize) -> crate::errors::Result<Box<dyn PageIterator>> {
306 match &self.column_chunks[i] {
307 None => Err(ParquetError::General(format!(
308 "Invalid column index {i}, column was not fetched"
309 ))),
310 Some(data) => {
311 let page_locations = self
312 .page_index
313 .as_ref()
314 .and_then(|pi| pi.page_locations(i).cloned());
315 let column_chunk_metadata = self.metadata.row_group(self.row_group_idx).column(i);
316 let page_reader = SerializedPageReader::new(
317 data.clone(),
318 column_chunk_metadata,
319 self.row_count,
320 page_locations,
321 )?;
322 let page_reader = page_reader.add_crypto_context(
323 self.row_group_idx,
324 i,
325 self.metadata,
326 column_chunk_metadata,
327 )?;
328
329 let page_reader: Box<dyn PageReader> = Box::new(page_reader);
330
331 Ok(Box::new(ColumnChunkIterator {
332 reader: Some(Ok(page_reader)),
333 }))
334 }
335 }
336 }
337
338 fn row_groups(&self) -> Box<dyn Iterator<Item = &RowGroupMetaData> + '_> {
339 Box::new(std::iter::once(self.metadata.row_group(self.row_group_idx)))
340 }
341
342 fn metadata(&self) -> &ParquetMetaData {
343 self.metadata
344 }
345}
346
347#[derive(Clone, Debug)]
351pub(crate) enum ColumnChunkData {
352 Sparse {
356 length: usize,
358 data: Vec<(usize, Bytes)>,
363 },
364 Dense { offset: usize, data: Bytes },
366}
367
368impl ColumnChunkData {
369 fn get(&self, start: u64) -> crate::errors::Result<Bytes> {
371 match &self {
372 ColumnChunkData::Sparse { data, .. } => data
373 .binary_search_by_key(&start, |(offset, _)| *offset as u64)
374 .map(|idx| data[idx].1.clone())
375 .map_err(|_| {
376 ParquetError::General(format!(
377 "Invalid offset in sparse column chunk data: {start}, no matching page found.\
378 If you are using a `SelectionStrategyPolicy::Mask`, ensure that the OffsetIndex is provided when \
379 creating the InMemoryRowGroup."
380 ))
381 }),
382 ColumnChunkData::Dense { offset, data } => {
383 let start = start as usize - *offset;
384 Ok(data.slice(start..))
385 }
386 }
387 }
388}
389
390impl Length for ColumnChunkData {
391 fn len(&self) -> u64 {
393 match &self {
394 ColumnChunkData::Sparse { length, .. } => *length as u64,
395 ColumnChunkData::Dense { data, .. } => data.len() as u64,
396 }
397 }
398}
399
400impl ChunkReader for ColumnChunkData {
401 type T = bytes::buf::Reader<Bytes>;
402
403 fn get_read(&self, start: u64) -> crate::errors::Result<Self::T> {
404 Ok(self.get(start)?.reader())
405 }
406
407 fn get_bytes(&self, start: u64, length: usize) -> crate::errors::Result<Bytes> {
408 let data = self.get(start)?;
409 if data.len() < length {
410 return Err(general_err!(
411 "column chunk data at offset {start} has {} bytes, expected {length}",
412 data.len()
413 ));
414 }
415 Ok(data.slice(..length))
416 }
417}
418
419struct ColumnChunkIterator {
421 reader: Option<crate::errors::Result<Box<dyn PageReader>>>,
422}
423
424impl Iterator for ColumnChunkIterator {
425 type Item = crate::errors::Result<Box<dyn PageReader>>;
426
427 fn next(&mut self) -> Option<Self::Item> {
428 self.reader.take()
429 }
430}
431
432impl PageIterator for ColumnChunkIterator {}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn get_bytes_errors_on_short_data() {
440 let dense = ColumnChunkData::Dense {
441 offset: 100,
442 data: Bytes::from_static(b"0123456789"),
443 };
444 assert_eq!(
445 dense.get_bytes(105, 5).unwrap(),
446 Bytes::from_static(b"56789")
447 );
448 let err = dense.get_bytes(105, 6).unwrap_err().to_string();
449 assert!(err.contains("has 5 bytes, expected 6"), "{err}");
450 }
451}