laminar_connectors/schema/parquet/decoder/
mod.rs1use arrow_array::RecordBatch;
8use arrow_schema::SchemaRef;
9use bytes::Bytes;
10use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
11use parquet::arrow::ProjectionMask;
12
13use crate::schema::error::{SchemaError, SchemaResult};
14use crate::schema::traits::FormatDecoder;
15use crate::schema::types::RawRecord;
16
17#[derive(Debug, Clone)]
22pub enum RowGroupPredicate {
23 Eq {
25 column: String,
27 value: String,
29 },
30 Gt {
32 column: String,
34 value: String,
36 },
37 Lt {
39 column: String,
41 value: String,
43 },
44 Between {
46 column: String,
48 low: String,
50 high: String,
52 },
53 And(Vec<RowGroupPredicate>),
55 Or(Vec<RowGroupPredicate>),
57}
58
59#[derive(Debug, Clone)]
61pub struct ParquetDecoderConfig {
62 pub projection_indices: Vec<usize>,
64
65 pub row_group_indices: Vec<usize>,
67
68 pub batch_size: usize,
70
71 pub predicate: Option<RowGroupPredicate>,
73}
74
75impl Default for ParquetDecoderConfig {
76 fn default() -> Self {
77 Self {
78 projection_indices: Vec::new(),
79 row_group_indices: Vec::new(),
80 batch_size: 8192,
81 predicate: None,
82 }
83 }
84}
85
86impl ParquetDecoderConfig {
87 #[must_use]
89 pub fn with_projection(mut self, indices: Vec<usize>) -> Self {
90 self.projection_indices = indices;
91 self
92 }
93
94 #[must_use]
96 pub fn with_row_groups(mut self, indices: Vec<usize>) -> Self {
97 self.row_group_indices = indices;
98 self
99 }
100
101 #[must_use]
103 pub fn with_batch_size(mut self, size: usize) -> Self {
104 self.batch_size = size;
105 self
106 }
107
108 #[must_use]
110 pub fn with_predicate(mut self, predicate: RowGroupPredicate) -> Self {
111 self.predicate = Some(predicate);
112 self
113 }
114}
115
116pub struct ParquetDecoder {
123 schema: SchemaRef,
125 config: ParquetDecoderConfig,
127}
128
129impl std::fmt::Debug for ParquetDecoder {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 f.debug_struct("ParquetDecoder")
132 .field("schema", &self.schema)
133 .field("config", &self.config)
134 .finish()
135 }
136}
137
138impl ParquetDecoder {
139 #[must_use]
141 pub fn new(schema: SchemaRef) -> Self {
142 Self::with_config(schema, ParquetDecoderConfig::default())
143 }
144
145 #[must_use]
147 pub fn with_config(schema: SchemaRef, config: ParquetDecoderConfig) -> Self {
148 Self { schema, config }
149 }
150}
151
152impl FormatDecoder for ParquetDecoder {
153 fn output_schema(&self) -> SchemaRef {
154 self.schema.clone()
155 }
156
157 fn decode_batch(&self, records: &[RawRecord]) -> SchemaResult<RecordBatch> {
158 if records.is_empty() {
159 return Ok(RecordBatch::new_empty(self.schema.clone()));
160 }
161
162 let mut all_batches: Vec<RecordBatch> = Vec::new();
163
164 for record in records {
165 let bytes = Bytes::copy_from_slice(&record.value);
166 let mut builder = ParquetRecordBatchReaderBuilder::try_new(bytes)
167 .map_err(|e| SchemaError::DecodeError(format!("Parquet reader init error: {e}")))?;
168
169 builder = builder.with_batch_size(self.config.batch_size);
171
172 if !self.config.projection_indices.is_empty() {
174 let parquet_schema = builder.parquet_schema().clone();
175 let mask = ProjectionMask::roots(
176 &parquet_schema,
177 self.config.projection_indices.iter().copied(),
178 );
179 builder = builder.with_projection(mask);
180 }
181
182 if !self.config.row_group_indices.is_empty() {
184 builder = builder.with_row_groups(self.config.row_group_indices.clone());
185 }
186
187 let reader = builder.build().map_err(|e| {
188 SchemaError::DecodeError(format!("Parquet reader build error: {e}"))
189 })?;
190
191 for batch_result in reader {
192 let batch = batch_result
193 .map_err(|e| SchemaError::DecodeError(format!("Parquet read error: {e}")))?;
194 all_batches.push(batch);
195 }
196 }
197
198 if all_batches.is_empty() {
199 return Ok(RecordBatch::new_empty(self.schema.clone()));
200 }
201
202 if all_batches.len() == 1 {
203 return Ok(all_batches.into_iter().next().unwrap());
204 }
205
206 arrow_select::concat::concat_batches(&self.schema, &all_batches)
208 .map_err(|e| SchemaError::DecodeError(format!("batch concat error: {e}")))
209 }
210
211 fn format_name(&self) -> &'static str {
212 "parquet"
213 }
214}
215
216#[cfg(test)]
217mod tests;