1#[cfg(feature = "arrow")]
19use crate::arrow::ProjectionMask;
20#[cfg(feature = "encryption")]
21use crate::encryption::decrypt::FileDecryptionProperties;
22use crate::errors::{ParquetError, Result};
23use crate::file::FOOTER_SIZE;
24#[cfg(feature = "arrow")]
25use crate::file::metadata::dictionary::decode_dictionary_page;
26use crate::file::metadata::parser::decode_metadata;
27use crate::file::metadata::thrift::parquet_schema_from_bytes;
28use crate::file::metadata::{
29 FooterTail, ParquetMetaData, ParquetMetaDataOptions, ParquetMetaDataPushDecoder,
30};
31use crate::file::reader::ChunkReader;
32use crate::schema::types::SchemaDescriptor;
33#[cfg(feature = "arrow")]
34use arrow_array::ArrayRef;
35use bytes::Bytes;
36use std::collections::BTreeSet;
37use std::iter::FusedIterator;
38use std::slice::Iter;
39use std::sync::Arc;
40use std::{io::Read, ops::Range};
41
42use crate::DecodeResult;
43#[cfg(all(feature = "async", feature = "arrow"))]
44use crate::arrow::async_reader::{MetadataFetch, MetadataSuffixFetch};
45
46#[derive(Default, Debug)]
79pub struct ParquetMetaDataReader {
80 metadata: Option<ParquetMetaData>,
81 column_index: PageIndexPolicy,
82 offset_index: PageIndexPolicy,
83 column_index_mask: ColumnChunkMask,
84 offset_index_mask: ColumnChunkMask,
85 prefetch_hint: Option<usize>,
86 metadata_options: Option<Arc<ParquetMetaDataOptions>>,
87 metadata_size: Option<usize>,
90 #[cfg(feature = "encryption")]
91 file_decryption_properties: Option<Arc<FileDecryptionProperties>>,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
100pub enum PageIndexPolicy {
101 #[default]
103 Skip,
104 Optional,
106 Required,
113}
114
115impl From<bool> for PageIndexPolicy {
116 fn from(value: bool) -> Self {
117 match value {
118 true => Self::Required,
119 false => Self::Skip,
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
159pub struct ColumnChunkMask {
160 row_groups: Option<Arc<[u32]>>,
166 columns: Option<Arc<[u32]>>,
167}
168
169impl ColumnChunkMask {
170 pub fn all() -> Self {
172 Self::default()
173 }
174
175 pub fn none() -> Self {
177 Self {
178 row_groups: Some(Arc::from([])),
179 columns: Some(Arc::from([])),
180 }
181 }
182
183 pub fn columns(columns: impl IntoIterator<Item = usize>) -> Self {
187 Self {
188 row_groups: None,
189 columns: Self::iter_to_set(columns),
190 }
191 }
192
193 pub fn row_groups(row_groups: impl IntoIterator<Item = usize>) -> Self {
197 Self {
198 row_groups: Self::iter_to_set(row_groups),
199 columns: None,
200 }
201 }
202
203 pub fn row_groups_and_columns(
207 row_groups: impl IntoIterator<Item = usize>,
208 columns: impl IntoIterator<Item = usize>,
209 ) -> Self {
210 Self {
211 row_groups: Self::iter_to_set(row_groups),
212 columns: Self::iter_to_set(columns),
213 }
214 }
215
216 pub fn includes_row_group(&self, idx: usize, num_row_groups: usize) -> bool {
218 idx < num_row_groups && Self::includes_index(self.row_groups.as_ref(), idx)
219 }
220
221 pub fn includes_column(&self, idx: usize, num_columns: usize) -> bool {
223 idx < num_columns && Self::includes_index(self.columns.as_ref(), idx)
224 }
225
226 fn includes_index(keep: Option<&Arc<[u32]>>, idx: usize) -> bool {
227 let Ok(idx) = i32::try_from(idx) else {
229 return false;
230 };
231 let idx = idx as u32;
232 keep.is_none_or(|keep| keep.binary_search(&idx).is_ok())
233 }
234
235 pub fn is_all(&self) -> bool {
237 self.row_groups.is_none() && self.columns.is_none()
238 }
239
240 pub fn is_none(&self) -> bool {
246 self.row_groups.as_ref().is_some_and(|i| i.is_empty())
247 || self.columns.as_ref().is_some_and(|i| i.is_empty())
248 }
249
250 #[cfg(feature = "arrow")]
252 pub fn from_projection(projection: &ProjectionMask, schema: &SchemaDescriptor) -> Self {
253 Self::columns((0..schema.num_columns()).filter(|&i| projection.leaf_included(i)))
254 }
255
256 pub fn row_group_indices(&self, num_row_groups: usize) -> impl Iterator<Item = usize> + '_ {
259 Self::axis_indices(self.row_groups.as_deref(), num_row_groups)
260 }
261
262 pub fn column_indices(&self, num_columns: usize) -> impl Iterator<Item = usize> + '_ {
265 Self::axis_indices(self.columns.as_deref(), num_columns)
266 }
267
268 fn axis_indices(axis: Option<&[u32]>, len: usize) -> AxisIndices<'_> {
269 match axis {
270 None => AxisIndices::All(0..len),
271 Some(indices) => {
272 let end = indices.partition_point(|&i| (i as usize) < len);
273 AxisIndices::Selected(indices[..end].iter())
274 }
275 }
276 }
277
278 fn iter_to_set(indices: impl IntoIterator<Item = usize>) -> Option<Arc<[u32]>> {
281 Some(
282 indices
283 .into_iter()
284 .filter_map(|i| i32::try_from(i).ok()) .map(|i| i as u32) .collect::<BTreeSet<_>>()
287 .into_iter()
288 .collect::<Vec<_>>()
289 .into(),
290 )
291 }
292}
293
294enum AxisIndices<'a> {
296 All(Range<usize>),
297 Selected(Iter<'a, u32>),
298}
299
300impl Iterator for AxisIndices<'_> {
301 type Item = usize;
302
303 fn next(&mut self) -> Option<Self::Item> {
304 match self {
305 Self::All(indices) => indices.next(),
306 Self::Selected(indices) => indices.next().map(|&i| i as usize),
307 }
308 }
309
310 fn size_hint(&self) -> (usize, Option<usize>) {
311 match self {
312 Self::All(indices) => indices.size_hint(),
313 Self::Selected(indices) => indices.size_hint(),
314 }
315 }
316}
317
318impl ExactSizeIterator for AxisIndices<'_> {}
319impl FusedIterator for AxisIndices<'_> {}
320
321impl ParquetMetaDataReader {
322 pub fn new() -> Self {
324 Default::default()
325 }
326
327 pub fn new_with_metadata(metadata: ParquetMetaData) -> Self {
330 Self {
331 metadata: Some(metadata),
332 ..Default::default()
333 }
334 }
335
336 pub fn with_page_index_policy(self, policy: PageIndexPolicy) -> Self {
338 self.with_column_index_policy(policy)
339 .with_offset_index_policy(policy)
340 }
341
342 pub fn with_column_index_policy(mut self, policy: PageIndexPolicy) -> Self {
344 self.column_index = policy;
345 self
346 }
347
348 pub fn with_offset_index_policy(mut self, policy: PageIndexPolicy) -> Self {
350 self.offset_index = policy;
351 self
352 }
353
354 pub fn with_page_index_mask(self, mask: ColumnChunkMask) -> Self {
362 self.with_column_index_mask(mask.clone())
363 .with_offset_index_mask(mask)
364 }
365
366 pub fn with_column_index_mask(mut self, mask: ColumnChunkMask) -> Self {
371 self.column_index_mask = mask;
372 self
373 }
374
375 pub fn with_offset_index_mask(mut self, mask: ColumnChunkMask) -> Self {
380 self.offset_index_mask = mask;
381 self
382 }
383
384 pub fn with_metadata_options(mut self, options: Option<ParquetMetaDataOptions>) -> Self {
386 self.metadata_options = options.map(Arc::new);
387 self
388 }
389
390 pub fn with_prefetch_hint(mut self, prefetch: Option<usize>) -> Self {
402 self.prefetch_hint = prefetch;
403 self
404 }
405
406 #[cfg(feature = "encryption")]
410 pub fn with_decryption_properties(
411 mut self,
412 properties: Option<std::sync::Arc<FileDecryptionProperties>>,
413 ) -> Self {
414 self.file_decryption_properties = properties;
415 self
416 }
417
418 pub fn has_metadata(&self) -> bool {
420 self.metadata.is_some()
421 }
422
423 pub fn finish(&mut self) -> Result<ParquetMetaData> {
425 self.metadata
426 .take()
427 .ok_or_else(|| general_err!("could not parse parquet metadata"))
428 }
429
430 pub fn parse_and_finish<R: ChunkReader>(mut self, reader: &R) -> Result<ParquetMetaData> {
449 self.try_parse(reader)?;
450 self.finish()
451 }
452
453 pub fn try_parse<R: ChunkReader>(&mut self, reader: &R) -> Result<()> {
459 self.try_parse_sized(reader, reader.len())
460 }
461
462 pub fn try_parse_sized<R: ChunkReader>(&mut self, reader: &R, file_size: u64) -> Result<()> {
535 self.metadata = match self.parse_metadata(reader) {
536 Ok(metadata) => Some(metadata),
537 Err(ParquetError::NeedMoreData(needed)) => {
538 return if file_size == reader.len() || needed as u64 > file_size {
541 Err(eof_err!(
542 "Parquet file too small. Size is {} but need {}",
543 file_size,
544 needed
545 ))
546 } else {
547 Err(ParquetError::NeedMoreData(needed))
549 };
550 }
551 Err(e) => return Err(e),
552 };
553
554 if self.column_index == PageIndexPolicy::Skip && self.offset_index == PageIndexPolicy::Skip
556 {
557 return Ok(());
558 }
559
560 self.read_page_indexes_sized(reader, file_size)
561 }
562
563 pub fn read_page_indexes<R: ChunkReader>(&mut self, reader: &R) -> Result<()> {
573 self.read_page_indexes_sized(reader, reader.len())
574 }
575
576 pub fn read_page_indexes_sized<R: ChunkReader>(
585 &mut self,
586 reader: &R,
587 file_size: u64,
588 ) -> Result<()> {
589 let Some(metadata) = self.metadata.take() else {
590 return Err(general_err!(
591 "Tried to read page indexes without ParquetMetaData metadata"
592 ));
593 };
594
595 let push_decoder = ParquetMetaDataPushDecoder::try_new_with_metadata(file_size, metadata)?
596 .with_offset_index_policy(self.offset_index)
597 .with_column_index_policy(self.column_index)
598 .with_offset_index_mask(self.offset_index_mask.clone())
599 .with_column_index_mask(self.column_index_mask.clone())
600 .with_metadata_options(self.metadata_options.clone());
601 let mut push_decoder = self.prepare_push_decoder(push_decoder);
602
603 let range = match needs_index_data(&mut push_decoder)? {
605 NeedsIndexData::No(metadata) => {
606 self.metadata = Some(metadata);
607 return Ok(());
608 }
609 NeedsIndexData::Yes(range) => range,
610 };
611
612 let file_range = file_size.saturating_sub(reader.len())..file_size;
615 if !(file_range.contains(&range.start) && file_range.contains(&range.end)) {
616 return if range.end > file_size {
618 Err(eof_err!(
619 "Parquet file too small. Range {range:?} is beyond file bounds {file_size}",
620 ))
621 } else {
622 Err(ParquetError::NeedMoreData(
624 (file_size - range.start).try_into()?,
625 ))
626 };
627 }
628
629 if let Some(metadata_size) = self.metadata_size {
632 let metadata_range = file_size.saturating_sub(metadata_size as u64)..file_size;
633 if range.end > metadata_range.start {
634 return Err(eof_err!(
635 "Parquet file too small. Page index range {range:?} overlaps with file metadata {metadata_range:?}",
636 ));
637 }
638 }
639
640 let bytes_needed = usize::try_from(range.end - range.start)?;
642 let bytes = reader.get_bytes(range.start - file_range.start, bytes_needed)?;
643
644 push_decoder.push_range(range, bytes)?;
645 let metadata = parse_index_data(&mut push_decoder)?;
646 self.metadata = Some(metadata);
647
648 Ok(())
649 }
650
651 #[cfg(all(feature = "async", feature = "arrow"))]
658 pub async fn load_and_finish<F: MetadataFetch>(
659 mut self,
660 fetch: F,
661 file_size: u64,
662 ) -> Result<ParquetMetaData> {
663 self.try_load(fetch, file_size).await?;
664 self.finish()
665 }
666
667 #[cfg(all(feature = "async", feature = "arrow"))]
674 pub async fn load_via_suffix_and_finish<F: MetadataSuffixFetch>(
675 mut self,
676 fetch: F,
677 ) -> Result<ParquetMetaData> {
678 self.try_load_via_suffix(fetch).await?;
679 self.finish()
680 }
681 #[cfg(all(feature = "async", feature = "arrow"))]
687 pub async fn try_load<F: MetadataFetch>(&mut self, mut fetch: F, file_size: u64) -> Result<()> {
688 let (metadata, remainder) = self.load_metadata(&mut fetch, file_size).await?;
689
690 self.metadata = Some(metadata);
691
692 if self.column_index == PageIndexPolicy::Skip && self.offset_index == PageIndexPolicy::Skip
694 {
695 return Ok(());
696 }
697
698 self.load_page_index_with_remainder(fetch, remainder).await
699 }
700
701 #[cfg(all(feature = "async", feature = "arrow"))]
707 pub async fn try_load_via_suffix<F: MetadataSuffixFetch>(
708 &mut self,
709 mut fetch: F,
710 ) -> Result<()> {
711 let metadata = self.load_metadata_via_suffix(&mut fetch).await?;
712
713 self.metadata = Some(metadata);
714
715 if self.column_index == PageIndexPolicy::Skip && self.offset_index == PageIndexPolicy::Skip
717 {
718 return Ok(());
719 }
720
721 self.load_page_index_with_remainder(fetch, None).await
722 }
723
724 #[cfg(all(feature = "async", feature = "arrow"))]
730 pub async fn load_page_index<F: MetadataFetch>(&mut self, fetch: F) -> Result<()> {
731 self.load_page_index_with_remainder(fetch, None).await
732 }
733
734 #[cfg(feature = "arrow")]
752 pub fn read_column_dictionary<R: ChunkReader>(
753 reader: &R,
754 metadata: &ParquetMetaData,
755 row_group_idx: usize,
756 column_idx: usize,
757 ) -> Result<Option<ArrayRef>> {
758 let Some(range) = dictionary_page_byte_range(metadata, row_group_idx, column_idx)? else {
759 return Ok(None);
760 };
761 let length = usize::try_from(range.end - range.start)?;
762 let buffer = reader.get_bytes(range.start, length)?;
763 decode_dictionary_page(buffer, metadata, row_group_idx, column_idx).map(Some)
764 }
765
766 #[cfg(all(feature = "async", feature = "arrow"))]
768 pub async fn read_column_dictionary_async<F: MetadataFetch>(
769 mut fetch: F,
770 metadata: &ParquetMetaData,
771 row_group_idx: usize,
772 column_idx: usize,
773 ) -> Result<Option<ArrayRef>> {
774 let Some(range) = dictionary_page_byte_range(metadata, row_group_idx, column_idx)? else {
775 return Ok(None);
776 };
777 let buffer = fetch.fetch(range).await?;
778 decode_dictionary_page(buffer, metadata, row_group_idx, column_idx).map(Some)
779 }
780
781 #[cfg(all(feature = "async", feature = "arrow"))]
782 async fn load_page_index_with_remainder<F: MetadataFetch>(
783 &mut self,
784 mut fetch: F,
785 remainder: Option<(usize, Bytes)>,
786 ) -> Result<()> {
787 let Some(metadata) = self.metadata.take() else {
788 return Err(general_err!("Footer metadata is not present"));
789 };
790
791 let file_size = u64::MAX;
794 let push_decoder = ParquetMetaDataPushDecoder::try_new_with_metadata(file_size, metadata)?
795 .with_offset_index_policy(self.offset_index)
796 .with_column_index_policy(self.column_index)
797 .with_offset_index_mask(self.offset_index_mask.clone())
798 .with_column_index_mask(self.column_index_mask.clone())
799 .with_metadata_options(self.metadata_options.clone());
800 let mut push_decoder = self.prepare_push_decoder(push_decoder);
801
802 let range = match needs_index_data(&mut push_decoder)? {
804 NeedsIndexData::No(metadata) => {
805 self.metadata = Some(metadata);
806 return Ok(());
807 }
808 NeedsIndexData::Yes(range) => range,
809 };
810
811 let bytes = match &remainder {
812 Some((remainder_start, remainder)) if *remainder_start as u64 <= range.start => {
813 let remainder_start = *remainder_start as u64;
814 let offset = usize::try_from(range.start - remainder_start)?;
815 let end = usize::try_from(range.end - remainder_start)?;
816 if end > remainder.len() {
817 return Err(general_err!(
818 "Corrupted parquet file: index data range ({:?}) exceeds remainder length ({})",
819 range,
820 remainder.len()
821 ));
822 }
823 remainder.slice(offset..end)
824 }
825 _ => fetch.fetch(range.clone()).await?,
827 };
828
829 if bytes.len() as u64 != range.end - range.start {
831 return Err(general_err!(
832 "Corrupted parquet file: index data length mismatch, expected {}, got {}",
833 range.end - range.start,
834 bytes.len()
835 ));
836 }
837 push_decoder.push_range(range.clone(), bytes)?;
838 let metadata = parse_index_data(&mut push_decoder)?;
839 self.metadata = Some(metadata);
840 Ok(())
841 }
842
843 fn parse_metadata<R: ChunkReader>(&mut self, chunk_reader: &R) -> Result<ParquetMetaData> {
846 let file_size = chunk_reader.len();
848 if file_size < (FOOTER_SIZE as u64) {
849 return Err(ParquetError::NeedMoreData(FOOTER_SIZE));
850 }
851
852 let mut footer = [0_u8; FOOTER_SIZE];
853 chunk_reader
854 .get_read(file_size - FOOTER_SIZE as u64)?
855 .read_exact(&mut footer)?;
856
857 let footer = FooterTail::try_new(&footer)?;
858 let metadata_len = footer.metadata_length();
859 let footer_metadata_len = FOOTER_SIZE + metadata_len;
860 self.metadata_size = Some(footer_metadata_len);
861
862 if footer_metadata_len as u64 > file_size {
863 return Err(ParquetError::NeedMoreData(footer_metadata_len));
864 }
865
866 let start = file_size - footer_metadata_len as u64;
867 let bytes = chunk_reader.get_bytes(start, metadata_len)?;
868 self.decode_footer_metadata(bytes, file_size, footer)
869 }
870
871 pub fn metadata_size(&self) -> Option<usize> {
874 self.metadata_size
875 }
876
877 #[cfg(all(feature = "async", feature = "arrow"))]
881 fn get_prefetch_size(&self) -> usize {
882 if let Some(prefetch) = self.prefetch_hint
883 && prefetch > FOOTER_SIZE
884 {
885 return prefetch;
886 }
887 FOOTER_SIZE
888 }
889
890 #[cfg(all(feature = "async", feature = "arrow"))]
891 async fn load_metadata<F: MetadataFetch>(
892 &self,
893 fetch: &mut F,
894 file_size: u64,
895 ) -> Result<(ParquetMetaData, Option<(usize, Bytes)>)> {
896 let prefetch = self.get_prefetch_size() as u64;
897
898 if file_size < FOOTER_SIZE as u64 {
899 return Err(eof_err!("file size of {} is less than footer", file_size));
900 }
901
902 let footer_start = file_size.saturating_sub(prefetch);
906
907 let suffix = fetch.fetch(footer_start..file_size).await?;
908 let suffix_len = suffix.len();
909 let fetch_len = (file_size - footer_start)
910 .try_into()
911 .expect("footer size should never be larger than u32");
912 if suffix_len < fetch_len {
913 return Err(eof_err!(
914 "metadata requires {} bytes, but could only read {}",
915 fetch_len,
916 suffix_len
917 ));
918 }
919
920 let mut footer = [0; FOOTER_SIZE];
921 footer.copy_from_slice(&suffix[suffix_len - FOOTER_SIZE..suffix_len]);
922
923 let footer = FooterTail::try_new(&footer)?;
924 let length = footer.metadata_length();
925
926 if file_size < (length + FOOTER_SIZE) as u64 {
927 return Err(eof_err!(
928 "file size of {} is less than footer + metadata {}",
929 file_size,
930 length + FOOTER_SIZE
931 ));
932 }
933
934 if length > suffix_len - FOOTER_SIZE {
936 let metadata_start = file_size - (length + FOOTER_SIZE) as u64;
937 let meta = fetch
938 .fetch(metadata_start..(file_size - FOOTER_SIZE as u64))
939 .await?;
940 Ok((self.decode_footer_metadata(meta, file_size, footer)?, None))
941 } else {
942 let metadata_start = (file_size - (length + FOOTER_SIZE) as u64 - footer_start)
943 .try_into()
944 .expect("metadata length should never be larger than u32");
945 let slice = suffix.slice(metadata_start..suffix_len - FOOTER_SIZE);
946 Ok((
947 self.decode_footer_metadata(slice, file_size, footer)?,
948 Some((footer_start as usize, suffix.slice(..metadata_start))),
949 ))
950 }
951 }
952
953 #[cfg(all(feature = "async", feature = "arrow"))]
954 async fn load_metadata_via_suffix<F: MetadataSuffixFetch>(
958 &self,
959 fetch: &mut F,
960 ) -> Result<ParquetMetaData> {
961 let prefetch = self.get_prefetch_size();
962
963 let suffix = fetch.fetch_suffix(prefetch).await?;
964 let suffix_len = suffix.len();
965
966 if suffix_len < FOOTER_SIZE {
967 return Err(eof_err!(
968 "footer metadata requires {} bytes, but could only read {}",
969 FOOTER_SIZE,
970 suffix_len
971 ));
972 }
973
974 let mut footer = [0; FOOTER_SIZE];
975 footer.copy_from_slice(&suffix[suffix_len - FOOTER_SIZE..suffix_len]);
976
977 let footer = FooterTail::try_new(&footer)?;
978 let length = footer.metadata_length();
979 let file_size = (length + FOOTER_SIZE) as u64;
982
983 let metadata_offset = length + FOOTER_SIZE;
985 if length > suffix_len - FOOTER_SIZE {
986 let meta = fetch.fetch_suffix(metadata_offset).await?;
987
988 if meta.len() < metadata_offset {
989 return Err(eof_err!(
990 "metadata requires {} bytes, but could only read {}",
991 metadata_offset,
992 meta.len()
993 ));
994 }
995
996 let meta = meta.slice(0..length);
998 Ok(self.decode_footer_metadata(meta, file_size, footer)?)
999 } else {
1000 let metadata_start = suffix_len - metadata_offset;
1001 let slice = suffix.slice(metadata_start..suffix_len - FOOTER_SIZE);
1002 Ok(self.decode_footer_metadata(slice, file_size, footer)?)
1003 }
1004 }
1005
1006 pub(crate) fn decode_footer_metadata(
1020 &self,
1021 buf: Bytes,
1022 file_size: u64,
1023 footer_tail: FooterTail,
1024 ) -> Result<ParquetMetaData> {
1025 let ending_offset = file_size.checked_sub(FOOTER_SIZE as u64).ok_or_else(|| {
1030 general_err!(
1031 "file size {file_size} is smaller than footer size {}",
1032 FOOTER_SIZE
1033 )
1034 })?;
1035
1036 let starting_offset = ending_offset.checked_sub(buf.len() as u64).ok_or_else(|| {
1037 general_err!(
1038 "file size {file_size} is smaller than buffer size {} + footer size {}",
1039 buf.len(),
1040 FOOTER_SIZE
1041 )
1042 })?;
1043
1044 let range = starting_offset..ending_offset;
1045
1046 let push_decoder =
1047 ParquetMetaDataPushDecoder::try_new_with_footer_tail(file_size, footer_tail)?
1048 .with_page_index_policy(PageIndexPolicy::Skip)
1050 .with_metadata_options(self.metadata_options.clone());
1051
1052 let mut push_decoder = self.prepare_push_decoder(push_decoder);
1053 push_decoder.push_range(range, buf)?;
1054 match push_decoder.try_decode()? {
1055 DecodeResult::Data(metadata) => Ok(metadata),
1056 DecodeResult::Finished => Err(general_err!(
1057 "could not parse parquet metadata -- previously finished"
1058 )),
1059 DecodeResult::NeedsData(ranges) => Err(general_err!(
1060 "could not parse parquet metadata, needs ranges {:?}",
1061 ranges
1062 )),
1063 }
1064 }
1065
1066 #[cfg(feature = "encryption")]
1068 fn prepare_push_decoder(
1069 &self,
1070 push_decoder: ParquetMetaDataPushDecoder,
1071 ) -> ParquetMetaDataPushDecoder {
1072 push_decoder.with_file_decryption_properties(
1073 self.file_decryption_properties
1074 .as_ref()
1075 .map(std::sync::Arc::clone),
1076 )
1077 }
1078 #[cfg(not(feature = "encryption"))]
1079 fn prepare_push_decoder(
1080 &self,
1081 push_decoder: ParquetMetaDataPushDecoder,
1082 ) -> ParquetMetaDataPushDecoder {
1083 push_decoder
1084 }
1085
1086 pub fn decode_metadata(buf: &[u8]) -> Result<ParquetMetaData> {
1094 decode_metadata(buf, None)
1095 }
1096
1097 pub fn decode_metadata_with_options(
1102 buf: &[u8],
1103 options: Option<&ParquetMetaDataOptions>,
1104 ) -> Result<ParquetMetaData> {
1105 decode_metadata(buf, options)
1106 }
1107
1108 pub fn decode_schema(buf: &[u8]) -> Result<Arc<SchemaDescriptor>> {
1111 Ok(Arc::new(parquet_schema_from_bytes(buf)?))
1112 }
1113}
1114
1115enum NeedsIndexData {
1118 No(ParquetMetaData),
1120 Yes(Range<u64>),
1122}
1123
1124fn needs_index_data(push_decoder: &mut ParquetMetaDataPushDecoder) -> Result<NeedsIndexData> {
1127 match push_decoder.try_decode()? {
1128 DecodeResult::NeedsData(ranges) => {
1129 let range = ranges
1130 .into_iter()
1131 .reduce(|a, b| a.start.min(b.start)..a.end.max(b.end))
1132 .ok_or_else(|| general_err!("Internal error: no ranges provided"))?;
1133 Ok(NeedsIndexData::Yes(range))
1134 }
1135 DecodeResult::Data(metadata) => Ok(NeedsIndexData::No(metadata)),
1136 DecodeResult::Finished => Err(general_err!("Internal error: decoder was finished")),
1137 }
1138}
1139
1140fn parse_index_data(push_decoder: &mut ParquetMetaDataPushDecoder) -> Result<ParquetMetaData> {
1143 match push_decoder.try_decode()? {
1144 DecodeResult::NeedsData(_) => Err(general_err!(
1145 "Internal error: decoder still needs data after reading required range"
1146 )),
1147 DecodeResult::Data(metadata) => Ok(metadata),
1148 DecodeResult::Finished => Err(general_err!("Internal error: decoder was finished")),
1149 }
1150}
1151
1152#[cfg(feature = "arrow")]
1157fn dictionary_page_byte_range(
1158 metadata: &ParquetMetaData,
1159 row_group_idx: usize,
1160 column_idx: usize,
1161) -> Result<Option<Range<u64>>> {
1162 let column_metadata = metadata.row_group(row_group_idx).column(column_idx);
1163 let column_descriptor = column_metadata.column_descr();
1164
1165 if column_descriptor.physical_type() != crate::basic::Type::BYTE_ARRAY {
1166 return Ok(None);
1167 }
1168 let Some(start) = column_metadata.dictionary_page_offset() else {
1169 return Ok(None);
1170 };
1171 let start: u64 = start
1172 .try_into()
1173 .map_err(|_| ParquetError::General("Dictionary page offset is invalid".to_string()))?;
1174 let end: u64 = column_metadata
1175 .data_page_offset()
1176 .try_into()
1177 .map_err(|_| ParquetError::General("Data page offset is invalid".to_string()))?;
1178 if end < start {
1179 return Err(ParquetError::General(
1180 "Data page offset precedes dictionary page offset".to_string(),
1181 ));
1182 }
1183
1184 Ok(Some(start..end))
1185}
1186
1187#[cfg(test)]
1188mod tests {
1189 use super::*;
1190 use crate::file::reader::Length;
1191 use crate::util::test_common::file_util::get_test_file;
1192 use std::ops::Range;
1193
1194 #[test]
1195 fn test_parse_metadata_size_smaller_than_footer() {
1196 let test_file = tempfile::tempfile().unwrap();
1197 let err = ParquetMetaDataReader::new()
1198 .parse_metadata(&test_file)
1199 .unwrap_err();
1200 assert!(matches!(err, ParquetError::NeedMoreData(FOOTER_SIZE)));
1201 }
1202
1203 #[test]
1204 fn test_parse_metadata_corrupt_footer() {
1205 let data = Bytes::from(vec![1, 2, 3, 4, 5, 6, 7, 8]);
1206 let reader_result = ParquetMetaDataReader::new().parse_metadata(&data);
1207 assert_eq!(
1208 reader_result.unwrap_err().to_string(),
1209 "Parquet error: Invalid Parquet file. Corrupt footer"
1210 );
1211 }
1212
1213 #[test]
1214 fn test_parse_metadata_invalid_start() {
1215 let test_file = Bytes::from(vec![255, 0, 0, 0, b'P', b'A', b'R', b'1']);
1216 let err = ParquetMetaDataReader::new()
1217 .parse_metadata(&test_file)
1218 .unwrap_err();
1219 assert!(matches!(err, ParquetError::NeedMoreData(263)));
1220 }
1221
1222 #[test]
1223 #[cfg_attr(miri, ignore)] fn test_try_parse() {
1225 let file = get_test_file("alltypes_tiny_pages.parquet");
1226 let len = file.len();
1227
1228 let mut reader =
1229 ParquetMetaDataReader::new().with_page_index_policy(PageIndexPolicy::Required);
1230
1231 let bytes_for_range = |range: Range<u64>| {
1232 file.get_bytes(range.start, (range.end - range.start).try_into().unwrap())
1233 .unwrap()
1234 };
1235
1236 let bytes = bytes_for_range(0..len);
1238 reader.try_parse(&bytes).unwrap();
1239 let metadata = reader.finish().unwrap();
1240 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1241
1242 let bytes = bytes_for_range(320000..len);
1244 reader.try_parse_sized(&bytes, len).unwrap();
1245 let metadata = reader.finish().unwrap();
1246 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1247
1248 let bytes = bytes_for_range(323583..len);
1250 reader.try_parse_sized(&bytes, len).unwrap();
1251 let metadata = reader.finish().unwrap();
1252 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1253
1254 let bytes = bytes_for_range(323584..len);
1256 match reader.try_parse_sized(&bytes, len).unwrap_err() {
1258 ParquetError::NeedMoreData(needed) => {
1260 let bytes = bytes_for_range(len - needed as u64..len);
1261 reader.try_parse_sized(&bytes, len).unwrap();
1262 let metadata = reader.finish().unwrap();
1263 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1264 }
1265 _ => panic!("unexpected error"),
1266 }
1267
1268 let mut reader =
1270 ParquetMetaDataReader::new().with_page_index_policy(PageIndexPolicy::Required);
1271 let mut bytes = bytes_for_range(452505..len);
1272 loop {
1273 match reader.try_parse_sized(&bytes, len) {
1274 Ok(()) => break,
1275 Err(ParquetError::NeedMoreData(needed)) => {
1276 bytes = bytes_for_range(len - needed as u64..len);
1277 if reader.has_metadata() {
1278 reader.read_page_indexes_sized(&bytes, len).unwrap();
1279 break;
1280 }
1281 }
1282 _ => panic!("unexpected error"),
1283 }
1284 }
1285 let metadata = reader.finish().unwrap();
1286 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1287
1288 let bytes = bytes_for_range(323584..len);
1290 let reader_result = reader.try_parse_sized(&bytes, len - 323584).unwrap_err();
1291 assert_eq!(
1292 reader_result.to_string(),
1293 "EOF: Parquet file too small. Range 323583..452504 is beyond file bounds 130649"
1294 );
1295
1296 let mut reader = ParquetMetaDataReader::new();
1298 let bytes = bytes_for_range(452505..len);
1299 match reader.try_parse_sized(&bytes, len).unwrap_err() {
1301 ParquetError::NeedMoreData(needed) => {
1303 let bytes = bytes_for_range(len - needed as u64..len);
1304 reader.try_parse_sized(&bytes, len).unwrap();
1305 reader.finish().unwrap();
1306 }
1307 _ => panic!("unexpected error"),
1308 }
1309
1310 let reader_result = reader.try_parse(&bytes).unwrap_err();
1312 assert_eq!(
1313 reader_result.to_string(),
1314 "EOF: Parquet file too small. Size is 1728 but need 1729"
1315 );
1316
1317 let bytes = bytes_for_range(0..1000);
1319 let reader_result = reader.try_parse_sized(&bytes, len).unwrap_err();
1320 assert_eq!(
1321 reader_result.to_string(),
1322 "Parquet error: Invalid Parquet file. Corrupt footer"
1323 );
1324
1325 let bytes = bytes_for_range(452510..len);
1327 let reader_result = reader.try_parse_sized(&bytes, len - 452505).unwrap_err();
1328 assert_eq!(
1329 reader_result.to_string(),
1330 "EOF: Parquet file too small. Size is 1728 but need 1729"
1331 );
1332 }
1333
1334 #[test]
1335 fn test_chunk_mask() {
1336 let mask = ColumnChunkMask::row_groups_and_columns([0], [1]);
1337 assert!(mask.includes_row_group(0, 1));
1338 assert!(!mask.includes_row_group(1, 1));
1339 assert!(!mask.includes_column(0, 2));
1340 assert!(mask.includes_column(1, 2));
1341
1342 let mask = ColumnChunkMask::columns([]);
1343 assert!(!mask.includes_column(0, 3));
1344 assert_eq!(mask.column_indices(3).count(), 0);
1345
1346 let all = ColumnChunkMask::all();
1347 assert!(all.is_all());
1348 assert!(all.includes_column(2, 3));
1349 assert!(!all.includes_column(3, 3));
1350
1351 assert_eq!(all.row_group_indices(3).collect::<Vec<_>>(), [0, 1, 2]);
1352 assert_eq!(all.column_indices(2).collect::<Vec<_>>(), [0, 1]);
1353
1354 let none = ColumnChunkMask::none();
1355 assert_eq!(none.row_group_indices(3).count(), 0);
1356 assert_eq!(none.column_indices(3).count(), 0);
1357
1358 let sparse = ColumnChunkMask::row_groups_and_columns([0, 2, 4], [1, 3, 5]);
1359 assert_eq!(sparse.row_group_indices(3).collect::<Vec<_>>(), [0, 2]);
1360 assert_eq!(sparse.column_indices(4).collect::<Vec<_>>(), [1, 3]);
1361 }
1362}
1363
1364#[cfg(all(feature = "async", feature = "arrow", test))]
1365mod async_tests {
1366 use super::*;
1367
1368 use arrow::{array::Int32Array, datatypes::DataType};
1369 use arrow_array::RecordBatch;
1370 use arrow_schema::{Field, Schema};
1371 use bytes::Bytes;
1372 use futures::FutureExt;
1373 use futures::future::BoxFuture;
1374 use std::fs::File;
1375 use std::future::Future;
1376 use std::io::{Read, Seek, SeekFrom};
1377 use std::ops::Range;
1378 use std::sync::Arc;
1379 use std::sync::atomic::{AtomicUsize, Ordering};
1380 use tempfile::NamedTempFile;
1381
1382 use crate::arrow::ArrowWriter;
1383 use crate::file::properties::WriterProperties;
1384 use crate::file::reader::Length;
1385 use crate::util::test_common::file_util::get_test_file;
1386
1387 struct MetadataFetchFn<F>(F);
1388
1389 impl<F, Fut> MetadataFetch for MetadataFetchFn<F>
1390 where
1391 F: FnMut(Range<u64>) -> Fut + Send,
1392 Fut: Future<Output = Result<Bytes>> + Send,
1393 {
1394 fn fetch(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
1395 async move { self.0(range).await }.boxed()
1396 }
1397 }
1398
1399 struct MetadataSuffixFetchFn<F1, F2>(F1, F2);
1400
1401 impl<F1, Fut, F2> MetadataFetch for MetadataSuffixFetchFn<F1, F2>
1402 where
1403 F1: FnMut(Range<u64>) -> Fut + Send,
1404 Fut: Future<Output = Result<Bytes>> + Send,
1405 F2: Send,
1406 {
1407 fn fetch(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
1408 async move { self.0(range).await }.boxed()
1409 }
1410 }
1411
1412 impl<F1, Fut, F2> MetadataSuffixFetch for MetadataSuffixFetchFn<F1, F2>
1413 where
1414 F1: FnMut(Range<u64>) -> Fut + Send,
1415 F2: FnMut(usize) -> Fut + Send,
1416 Fut: Future<Output = Result<Bytes>> + Send,
1417 {
1418 fn fetch_suffix(&mut self, suffix: usize) -> BoxFuture<'_, Result<Bytes>> {
1419 async move { self.1(suffix).await }.boxed()
1420 }
1421 }
1422
1423 fn read_range(file: &mut File, range: Range<u64>) -> Result<Bytes> {
1424 file.seek(SeekFrom::Start(range.start))?;
1425 let len = range.end - range.start;
1426 let mut buf = Vec::with_capacity(len.try_into().unwrap());
1427 file.take(len).read_to_end(&mut buf)?;
1428 Ok(buf.into())
1429 }
1430
1431 fn read_suffix(file: &mut File, suffix: usize) -> Result<Bytes> {
1432 let file_len = file.len();
1433 file.seek(SeekFrom::End(0 - suffix.min(file_len as _) as i64))?;
1435 let mut buf = Vec::with_capacity(suffix);
1436 file.take(suffix as _).read_to_end(&mut buf)?;
1437 Ok(buf.into())
1438 }
1439
1440 #[tokio::test]
1441 async fn test_simple() {
1442 let mut file = get_test_file("nulls.snappy.parquet");
1443 let len = file.len();
1444
1445 let expected = ParquetMetaDataReader::new()
1446 .parse_and_finish(&file)
1447 .unwrap();
1448 let expected = expected.file_metadata().schema();
1449 let fetch_count = AtomicUsize::new(0);
1450
1451 let mut fetch = |range| {
1452 fetch_count.fetch_add(1, Ordering::SeqCst);
1453 futures::future::ready(read_range(&mut file, range))
1454 };
1455
1456 let input = MetadataFetchFn(&mut fetch);
1457 let actual = ParquetMetaDataReader::new()
1458 .load_and_finish(input, len)
1459 .await
1460 .unwrap();
1461 assert_eq!(actual.file_metadata().schema(), expected);
1462 assert_eq!(fetch_count.load(Ordering::SeqCst), 2);
1463
1464 fetch_count.store(0, Ordering::SeqCst);
1466 let input = MetadataFetchFn(&mut fetch);
1467 let actual = ParquetMetaDataReader::new()
1468 .with_prefetch_hint(Some(7))
1469 .load_and_finish(input, len)
1470 .await
1471 .unwrap();
1472 assert_eq!(actual.file_metadata().schema(), expected);
1473 assert_eq!(fetch_count.load(Ordering::SeqCst), 2);
1474
1475 fetch_count.store(0, Ordering::SeqCst);
1477 let input = MetadataFetchFn(&mut fetch);
1478 let actual = ParquetMetaDataReader::new()
1479 .with_prefetch_hint(Some(10))
1480 .load_and_finish(input, len)
1481 .await
1482 .unwrap();
1483 assert_eq!(actual.file_metadata().schema(), expected);
1484 assert_eq!(fetch_count.load(Ordering::SeqCst), 2);
1485
1486 fetch_count.store(0, Ordering::SeqCst);
1488 let input = MetadataFetchFn(&mut fetch);
1489 let actual = ParquetMetaDataReader::new()
1490 .with_prefetch_hint(Some(500))
1491 .load_and_finish(input, len)
1492 .await
1493 .unwrap();
1494 assert_eq!(actual.file_metadata().schema(), expected);
1495 assert_eq!(fetch_count.load(Ordering::SeqCst), 1);
1496
1497 fetch_count.store(0, Ordering::SeqCst);
1499 let input = MetadataFetchFn(&mut fetch);
1500 let actual = ParquetMetaDataReader::new()
1501 .with_prefetch_hint(Some(428))
1502 .load_and_finish(input, len)
1503 .await
1504 .unwrap();
1505 assert_eq!(actual.file_metadata().schema(), expected);
1506 assert_eq!(fetch_count.load(Ordering::SeqCst), 1);
1507
1508 let input = MetadataFetchFn(&mut fetch);
1509 let err = ParquetMetaDataReader::new()
1510 .load_and_finish(input, 4)
1511 .await
1512 .unwrap_err()
1513 .to_string();
1514 assert_eq!(err, "EOF: file size of 4 is less than footer");
1515
1516 let input = MetadataFetchFn(&mut fetch);
1517 let err = ParquetMetaDataReader::new()
1518 .load_and_finish(input, 20)
1519 .await
1520 .unwrap_err()
1521 .to_string();
1522 assert_eq!(err, "Parquet error: Invalid Parquet file. Corrupt footer");
1523 }
1524
1525 #[tokio::test]
1526 async fn test_suffix() {
1527 let mut file = get_test_file("nulls.snappy.parquet");
1528 let mut file2 = file.try_clone().unwrap();
1529
1530 let expected = ParquetMetaDataReader::new()
1531 .parse_and_finish(&file)
1532 .unwrap();
1533 let expected = expected.file_metadata().schema();
1534 let fetch_count = AtomicUsize::new(0);
1535 let suffix_fetch_count = AtomicUsize::new(0);
1536
1537 let mut fetch = |range| {
1538 fetch_count.fetch_add(1, Ordering::SeqCst);
1539 futures::future::ready(read_range(&mut file, range))
1540 };
1541 let mut suffix_fetch = |suffix| {
1542 suffix_fetch_count.fetch_add(1, Ordering::SeqCst);
1543 futures::future::ready(read_suffix(&mut file2, suffix))
1544 };
1545
1546 let input = MetadataSuffixFetchFn(&mut fetch, &mut suffix_fetch);
1547 let actual = ParquetMetaDataReader::new()
1548 .load_via_suffix_and_finish(input)
1549 .await
1550 .unwrap();
1551 assert_eq!(actual.file_metadata().schema(), expected);
1552 assert_eq!(fetch_count.load(Ordering::SeqCst), 0);
1553 assert_eq!(suffix_fetch_count.load(Ordering::SeqCst), 2);
1554
1555 fetch_count.store(0, Ordering::SeqCst);
1557 suffix_fetch_count.store(0, Ordering::SeqCst);
1558 let input = MetadataSuffixFetchFn(&mut fetch, &mut suffix_fetch);
1559 let actual = ParquetMetaDataReader::new()
1560 .with_prefetch_hint(Some(7))
1561 .load_via_suffix_and_finish(input)
1562 .await
1563 .unwrap();
1564 assert_eq!(actual.file_metadata().schema(), expected);
1565 assert_eq!(fetch_count.load(Ordering::SeqCst), 0);
1566 assert_eq!(suffix_fetch_count.load(Ordering::SeqCst), 2);
1567
1568 fetch_count.store(0, Ordering::SeqCst);
1570 suffix_fetch_count.store(0, Ordering::SeqCst);
1571 let input = MetadataSuffixFetchFn(&mut fetch, &mut suffix_fetch);
1572 let actual = ParquetMetaDataReader::new()
1573 .with_prefetch_hint(Some(10))
1574 .load_via_suffix_and_finish(input)
1575 .await
1576 .unwrap();
1577 assert_eq!(actual.file_metadata().schema(), expected);
1578 assert_eq!(fetch_count.load(Ordering::SeqCst), 0);
1579 assert_eq!(suffix_fetch_count.load(Ordering::SeqCst), 2);
1580
1581 fetch_count.store(0, Ordering::SeqCst);
1583 suffix_fetch_count.store(0, Ordering::SeqCst);
1584 let input = MetadataSuffixFetchFn(&mut fetch, &mut suffix_fetch);
1585 let actual = ParquetMetaDataReader::new()
1586 .with_prefetch_hint(Some(500))
1587 .load_via_suffix_and_finish(input)
1588 .await
1589 .unwrap();
1590 assert_eq!(actual.file_metadata().schema(), expected);
1591 assert_eq!(fetch_count.load(Ordering::SeqCst), 0);
1592 assert_eq!(suffix_fetch_count.load(Ordering::SeqCst), 1);
1593
1594 fetch_count.store(0, Ordering::SeqCst);
1596 suffix_fetch_count.store(0, Ordering::SeqCst);
1597 let input = MetadataSuffixFetchFn(&mut fetch, &mut suffix_fetch);
1598 let actual = ParquetMetaDataReader::new()
1599 .with_prefetch_hint(Some(428))
1600 .load_via_suffix_and_finish(input)
1601 .await
1602 .unwrap();
1603 assert_eq!(actual.file_metadata().schema(), expected);
1604 assert_eq!(fetch_count.load(Ordering::SeqCst), 0);
1605 assert_eq!(suffix_fetch_count.load(Ordering::SeqCst), 1);
1606 }
1607
1608 #[cfg(feature = "encryption")]
1609 #[tokio::test]
1610 async fn test_suffix_with_encryption() {
1611 let mut file = get_test_file("uniform_encryption.parquet.encrypted");
1612 let mut file2 = file.try_clone().unwrap();
1613
1614 let mut fetch = |range| futures::future::ready(read_range(&mut file, range));
1615 let mut suffix_fetch = |suffix| futures::future::ready(read_suffix(&mut file2, suffix));
1616
1617 let input = MetadataSuffixFetchFn(&mut fetch, &mut suffix_fetch);
1618
1619 let key_code: &[u8] = b"0123456789012345";
1620 let decryption_properties = FileDecryptionProperties::builder(key_code.to_vec())
1621 .build()
1622 .unwrap();
1623
1624 let expected = ParquetMetaDataReader::new()
1626 .with_decryption_properties(Some(decryption_properties))
1627 .load_via_suffix_and_finish(input)
1628 .await
1629 .unwrap();
1630 assert_eq!(expected.num_row_groups(), 1);
1631 }
1632
1633 #[tokio::test]
1634 async fn test_page_index() {
1635 let mut file = get_test_file("alltypes_tiny_pages.parquet");
1636 let len = file.len();
1637 let fetch_count = AtomicUsize::new(0);
1638 let mut fetch = |range| {
1639 fetch_count.fetch_add(1, Ordering::SeqCst);
1640 futures::future::ready(read_range(&mut file, range))
1641 };
1642
1643 let f = MetadataFetchFn(&mut fetch);
1644 let mut loader =
1645 ParquetMetaDataReader::new().with_page_index_policy(PageIndexPolicy::Required);
1646 loader.try_load(f, len).await.unwrap();
1647 assert_eq!(fetch_count.load(Ordering::SeqCst), 3);
1648 let metadata = loader.finish().unwrap();
1649 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1650
1651 fetch_count.store(0, Ordering::SeqCst);
1653 let f = MetadataFetchFn(&mut fetch);
1654 let mut loader = ParquetMetaDataReader::new()
1655 .with_page_index_policy(PageIndexPolicy::Required)
1656 .with_prefetch_hint(Some(1729));
1657 loader.try_load(f, len).await.unwrap();
1658 assert_eq!(fetch_count.load(Ordering::SeqCst), 2);
1659 let metadata = loader.finish().unwrap();
1660 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1661
1662 fetch_count.store(0, Ordering::SeqCst);
1664 let f = MetadataFetchFn(&mut fetch);
1665 let mut loader = ParquetMetaDataReader::new()
1666 .with_page_index_policy(PageIndexPolicy::Required)
1667 .with_prefetch_hint(Some(130649));
1668 loader.try_load(f, len).await.unwrap();
1669 assert_eq!(fetch_count.load(Ordering::SeqCst), 2);
1670 let metadata = loader.finish().unwrap();
1671 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1672
1673 fetch_count.store(0, Ordering::SeqCst);
1675 let f = MetadataFetchFn(&mut fetch);
1676 let metadata = ParquetMetaDataReader::new()
1677 .with_page_index_policy(PageIndexPolicy::Required)
1678 .with_prefetch_hint(Some(130650))
1679 .load_and_finish(f, len)
1680 .await
1681 .unwrap();
1682 assert_eq!(fetch_count.load(Ordering::SeqCst), 1);
1683 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1684
1685 fetch_count.store(0, Ordering::SeqCst);
1687 let f = MetadataFetchFn(&mut fetch);
1688 let metadata = ParquetMetaDataReader::new()
1689 .with_page_index_policy(PageIndexPolicy::Required)
1690 .with_prefetch_hint(Some((len - 1000) as usize)) .load_and_finish(f, len)
1692 .await
1693 .unwrap();
1694 assert_eq!(fetch_count.load(Ordering::SeqCst), 1);
1695 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1696
1697 fetch_count.store(0, Ordering::SeqCst);
1699 let f = MetadataFetchFn(&mut fetch);
1700 let metadata = ParquetMetaDataReader::new()
1701 .with_page_index_policy(PageIndexPolicy::Required)
1702 .with_prefetch_hint(Some(len as usize)) .load_and_finish(f, len)
1704 .await
1705 .unwrap();
1706 assert_eq!(fetch_count.load(Ordering::SeqCst), 1);
1707 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1708
1709 fetch_count.store(0, Ordering::SeqCst);
1711 let f = MetadataFetchFn(&mut fetch);
1712 let metadata = ParquetMetaDataReader::new()
1713 .with_page_index_policy(PageIndexPolicy::Required)
1714 .with_prefetch_hint(Some((len + 1000) as usize)) .load_and_finish(f, len)
1716 .await
1717 .unwrap();
1718 assert_eq!(fetch_count.load(Ordering::SeqCst), 1);
1719 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1720 }
1721
1722 #[tokio::test]
1723 async fn test_page_index_via_suffix_with_prefetch() {
1724 let mut file = get_test_file("alltypes_tiny_pages.parquet");
1725 let mut suffix_file = file.try_clone().unwrap();
1726 let len = file.len();
1727 let fetch_count = AtomicUsize::new(0);
1728 let suffix_fetch_count = AtomicUsize::new(0);
1729
1730 let mut fetch = |range| {
1731 fetch_count.fetch_add(1, Ordering::SeqCst);
1732 futures::future::ready(read_range(&mut file, range))
1733 };
1734 let mut suffix_fetch = |suffix| {
1735 suffix_fetch_count.fetch_add(1, Ordering::SeqCst);
1736 futures::future::ready(read_suffix(&mut suffix_file, suffix))
1737 };
1738
1739 let input = MetadataSuffixFetchFn(&mut fetch, &mut suffix_fetch);
1740 let metadata = ParquetMetaDataReader::new()
1741 .with_page_index_policy(PageIndexPolicy::Required)
1742 .with_prefetch_hint(Some((len - 1000) as usize))
1743 .load_via_suffix_and_finish(input)
1744 .await
1745 .unwrap();
1746
1747 assert_eq!(suffix_fetch_count.load(Ordering::SeqCst), 1);
1748 assert_eq!(fetch_count.load(Ordering::SeqCst), 1);
1749 assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
1750 }
1751
1752 fn write_parquet_file(offset_index_disabled: bool) -> Result<NamedTempFile> {
1753 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
1754 let batch = RecordBatch::try_new(
1755 schema.clone(),
1756 vec![Arc::new(Int32Array::from(vec![1, 2, 3]))],
1757 )?;
1758
1759 let file = NamedTempFile::new().unwrap();
1760
1761 let props = WriterProperties::builder()
1763 .set_offset_index_disabled(offset_index_disabled)
1764 .build();
1765
1766 let mut writer = ArrowWriter::try_new(file.reopen()?, schema, Some(props))?;
1767 writer.write(&batch)?;
1768 writer.close()?;
1769
1770 Ok(file)
1771 }
1772
1773 fn read_and_check(file: &File, policy: PageIndexPolicy) -> Result<ParquetMetaData> {
1774 let mut reader = ParquetMetaDataReader::new().with_page_index_policy(policy);
1775 reader.try_parse(file)?;
1776 reader.finish()
1777 }
1778
1779 #[test]
1780 fn test_page_index_policy() {
1781 let f = write_parquet_file(false).unwrap();
1783 read_and_check(f.as_file(), PageIndexPolicy::Required).unwrap();
1784 read_and_check(f.as_file(), PageIndexPolicy::Optional).unwrap();
1785 read_and_check(f.as_file(), PageIndexPolicy::Skip).unwrap();
1786
1787 let f = write_parquet_file(true).unwrap();
1789 let res = read_and_check(f.as_file(), PageIndexPolicy::Required);
1790 assert!(matches!(
1791 res,
1792 Err(ParquetError::General(e)) if e == "missing offset index"
1793 ));
1794 read_and_check(f.as_file(), PageIndexPolicy::Optional).unwrap();
1795 read_and_check(f.as_file(), PageIndexPolicy::Skip).unwrap();
1796 }
1797}