mito2/sst/parquet/
index_reader.rs1use std::collections::HashSet;
18use std::sync::Arc;
19
20use datafusion_common::pruning::PruningStatistics;
21use datafusion_common::{Column, ScalarValue};
22use datatypes::arrow::array::{ArrayRef, BooleanArray};
23use datatypes::arrow::datatypes::SchemaRef;
24use datatypes::arrow::record_batch::RecordBatch;
25use futures::StreamExt;
26use futures::stream::BoxStream;
27use object_store::ObjectStore;
28use parquet::DecodeResult;
29use parquet::arrow::ProjectionMask;
30use parquet::arrow::arrow_reader::{ArrowReaderMetadata, ArrowReaderOptions};
31use parquet::arrow::push_decoder::ParquetPushDecoderBuilder;
32use parquet::file::metadata::RowGroupMetaData;
33use snafu::{OptionExt, ResultExt};
34use table::predicate::Predicate;
35
36use crate::error::{InvalidRecordBatchSnafu, OpenDalSnafu, ReadParquetSnafu, Result};
37use crate::sst::parquet::format::{column_null_counts, column_values_by_type};
38use crate::sst::parquet::helper::fetch_byte_ranges;
39use crate::sst::parquet::metadata::MetadataLoader;
40use crate::sst::parquet::reader::MetadataCacheMetrics;
41
42pub(crate) struct ParquetIndexReader {
44 object_store: ObjectStore,
45 path: String,
46 arrow_metadata: ArrowReaderMetadata,
47}
48
49impl ParquetIndexReader {
50 pub(crate) async fn open(object_store: ObjectStore, path: &str) -> Result<Self> {
52 let mut metrics = MetadataCacheMetrics::default();
53 let parquet_metadata = MetadataLoader::new(object_store.clone(), path, 0)
54 .load(&mut metrics)
55 .await?;
56 let arrow_metadata =
57 ArrowReaderMetadata::try_new(Arc::new(parquet_metadata), ArrowReaderOptions::new())
58 .with_context(|_| ReadParquetSnafu {
59 path: path.to_string(),
60 })?;
61
62 Ok(Self {
63 object_store,
64 path: path.to_string(),
65 arrow_metadata,
66 })
67 }
68
69 pub(crate) fn schema(&self) -> &SchemaRef {
71 self.arrow_metadata.schema()
72 }
73
74 pub(crate) fn row_groups_to_read(&self, predicate: &Predicate) -> Vec<usize> {
76 let stats = IndexRowGroupPruningStats {
77 row_groups: self.arrow_metadata.metadata().row_groups(),
78 schema: self.arrow_metadata.schema(),
79 };
80 predicate
81 .prune_with_stats(&stats, stats.schema)
82 .into_iter()
83 .enumerate()
84 .filter_map(|(row_group, keep)| keep.then_some(row_group))
85 .collect()
86 }
87
88 pub(crate) fn read(
90 &self,
91 predicate: &Predicate,
92 projection_columns: &[&str],
93 ) -> Result<BoxStream<'static, Result<RecordBatch>>> {
94 let projection = self.projection_mask(projection_columns)?;
95 let row_groups = self.row_groups_to_read(predicate);
96 if row_groups.is_empty() {
97 return Ok(futures::stream::empty().boxed());
98 }
99
100 let mut decoder = ParquetPushDecoderBuilder::new_with_metadata(self.arrow_metadata.clone())
101 .with_row_groups(row_groups)
102 .with_projection(projection)
103 .build()
104 .with_context(|_| ReadParquetSnafu {
105 path: self.path.clone(),
106 })?;
107 let path = self.path.clone();
108 let object_store = self.object_store.clone();
109
110 Ok(async_stream::try_stream! {
111 loop {
112 match decoder
113 .try_decode()
114 .with_context(|_| ReadParquetSnafu { path: path.clone() })?
115 {
116 DecodeResult::NeedsData(ranges) => {
117 let data = fetch_byte_ranges(&path, object_store.clone(), &ranges)
118 .await
119 .context(OpenDalSnafu)?;
120 decoder
121 .push_ranges(ranges, data)
122 .with_context(|_| ReadParquetSnafu { path: path.clone() })?;
123 }
124 DecodeResult::Data(batch) => yield batch,
125 DecodeResult::Finished => break,
126 }
127 }
128 }
129 .boxed())
130 }
131
132 fn projection_mask(&self, projection_columns: &[&str]) -> Result<ProjectionMask> {
133 let mut indices = HashSet::with_capacity(projection_columns.len());
134 for name in projection_columns {
135 let index = self
136 .arrow_metadata
137 .schema()
138 .index_of(name)
139 .ok()
140 .with_context(|| InvalidRecordBatchSnafu {
141 reason: format!("Parquet index is missing projected column {name}"),
142 })?;
143 indices.insert(index);
144 }
145 Ok(ProjectionMask::roots(
146 self.arrow_metadata.parquet_schema(),
147 indices,
148 ))
149 }
150}
151
152struct IndexRowGroupPruningStats<'a> {
153 row_groups: &'a [RowGroupMetaData],
154 schema: &'a SchemaRef,
155}
156
157impl PruningStatistics for IndexRowGroupPruningStats<'_> {
158 fn min_values(&self, column: &Column) -> Option<ArrayRef> {
159 self.column_values(column, true)
160 }
161
162 fn max_values(&self, column: &Column) -> Option<ArrayRef> {
163 self.column_values(column, false)
164 }
165
166 fn num_containers(&self) -> usize {
167 self.row_groups.len()
168 }
169
170 fn null_counts(&self, column: &Column) -> Option<ArrayRef> {
171 let column_index = self.schema.index_of(&column.name).ok()?;
172 column_null_counts(self.row_groups, column_index)
173 }
174
175 fn row_counts(&self, _column: &Column) -> Option<ArrayRef> {
176 None
177 }
178
179 fn contained(&self, _column: &Column, _values: &HashSet<ScalarValue>) -> Option<BooleanArray> {
180 None
181 }
182}
183
184impl IndexRowGroupPruningStats<'_> {
185 fn column_values(&self, column: &Column, is_min: bool) -> Option<ArrayRef> {
186 let column_index = self.schema.index_of(&column.name).ok()?;
187 let data_type = self.schema.field(column_index).data_type();
188 column_values_by_type(self.row_groups, data_type, column_index, is_min)
189 }
190}