Skip to main content

datafusion_proto/logical_plan/
file_formats.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 std::sync::Arc;
19
20use super::LogicalExtensionCodec;
21use crate::protobuf::{CsvOptions as CsvOptionsProto, JsonOptions as JsonOptionsProto};
22use datafusion_common::config::{CsvOptions, JsonOptions};
23use datafusion_common::{TableReference, exec_datafusion_err, exec_err, not_impl_err};
24use datafusion_datasource::file_format::FileFormatFactory;
25use datafusion_datasource_arrow::file_format::ArrowFormatFactory;
26use datafusion_datasource_csv::file_format::CsvFormatFactory;
27use datafusion_datasource_json::file_format::JsonFormatFactory;
28use datafusion_execution::TaskContext;
29use prost::Message;
30
31#[derive(Debug)]
32pub struct CsvLogicalExtensionCodec;
33
34// TODO! This is a placeholder for now and needs to be implemented for real.
35impl LogicalExtensionCodec for CsvLogicalExtensionCodec {
36    fn try_decode(
37        &self,
38        _buf: &[u8],
39        _inputs: &[datafusion_expr::LogicalPlan],
40        _ctx: &TaskContext,
41    ) -> datafusion_common::Result<datafusion_expr::Extension> {
42        not_impl_err!("Method not implemented")
43    }
44
45    fn try_encode(
46        &self,
47        _node: &datafusion_expr::Extension,
48        _buf: &mut Vec<u8>,
49    ) -> datafusion_common::Result<()> {
50        not_impl_err!("Method not implemented")
51    }
52
53    fn try_decode_table_provider(
54        &self,
55        _buf: &[u8],
56        _table_ref: &TableReference,
57        _schema: arrow::datatypes::SchemaRef,
58        _ctx: &TaskContext,
59    ) -> datafusion_common::Result<Arc<dyn datafusion_catalog::TableProvider>> {
60        not_impl_err!("Method not implemented")
61    }
62
63    fn try_encode_table_provider(
64        &self,
65        _table_ref: &TableReference,
66        _node: Arc<dyn datafusion_catalog::TableProvider>,
67        _buf: &mut Vec<u8>,
68    ) -> datafusion_common::Result<()> {
69        not_impl_err!("Method not implemented")
70    }
71
72    fn try_decode_file_format(
73        &self,
74        buf: &[u8],
75        _ctx: &TaskContext,
76    ) -> datafusion_common::Result<Arc<dyn FileFormatFactory>> {
77        let proto = CsvOptionsProto::decode(buf).map_err(|e| {
78            exec_datafusion_err!("Failed to decode CsvOptionsProto: {e:?}")
79        })?;
80        let options = CsvOptions::from(&proto);
81        Ok(Arc::new(CsvFormatFactory {
82            options: Some(options),
83        }))
84    }
85
86    fn try_encode_file_format(
87        &self,
88        buf: &mut Vec<u8>,
89        node: Arc<dyn FileFormatFactory>,
90    ) -> datafusion_common::Result<()> {
91        let options = if let Some(csv_factory) = node.downcast_ref::<CsvFormatFactory>() {
92            csv_factory.options.clone().unwrap_or_default()
93        } else {
94            return exec_err!("{}", "Unsupported FileFormatFactory type".to_string());
95        };
96
97        let proto = CsvOptionsProto::from(&CsvFormatFactory {
98            options: Some(options),
99        });
100
101        proto
102            .encode(buf)
103            .map_err(|e| exec_datafusion_err!("Failed to encode CsvOptions: {e:?}"))?;
104
105        Ok(())
106    }
107}
108
109#[derive(Debug)]
110pub struct JsonLogicalExtensionCodec;
111
112// TODO! This is a placeholder for now and needs to be implemented for real.
113impl LogicalExtensionCodec for JsonLogicalExtensionCodec {
114    fn try_decode(
115        &self,
116        _buf: &[u8],
117        _inputs: &[datafusion_expr::LogicalPlan],
118        _ctx: &TaskContext,
119    ) -> datafusion_common::Result<datafusion_expr::Extension> {
120        not_impl_err!("Method not implemented")
121    }
122
123    fn try_encode(
124        &self,
125        _node: &datafusion_expr::Extension,
126        _buf: &mut Vec<u8>,
127    ) -> datafusion_common::Result<()> {
128        not_impl_err!("Method not implemented")
129    }
130
131    fn try_decode_table_provider(
132        &self,
133        _buf: &[u8],
134        _table_ref: &TableReference,
135        _schema: arrow::datatypes::SchemaRef,
136        _ctx: &TaskContext,
137    ) -> datafusion_common::Result<Arc<dyn datafusion_catalog::TableProvider>> {
138        not_impl_err!("Method not implemented")
139    }
140
141    fn try_encode_table_provider(
142        &self,
143        _table_ref: &TableReference,
144        _node: Arc<dyn datafusion_catalog::TableProvider>,
145        _buf: &mut Vec<u8>,
146    ) -> datafusion_common::Result<()> {
147        not_impl_err!("Method not implemented")
148    }
149
150    fn try_decode_file_format(
151        &self,
152        buf: &[u8],
153        _ctx: &TaskContext,
154    ) -> datafusion_common::Result<Arc<dyn FileFormatFactory>> {
155        let proto = JsonOptionsProto::decode(buf).map_err(|e| {
156            exec_datafusion_err!("Failed to decode JsonOptionsProto: {e:?}")
157        })?;
158        let options = JsonOptions::from(&proto);
159        Ok(Arc::new(JsonFormatFactory {
160            options: Some(options),
161        }))
162    }
163
164    fn try_encode_file_format(
165        &self,
166        buf: &mut Vec<u8>,
167        node: Arc<dyn FileFormatFactory>,
168    ) -> datafusion_common::Result<()> {
169        let options = if let Some(json_factory) = node.downcast_ref::<JsonFormatFactory>()
170        {
171            json_factory.options.clone().unwrap_or_default()
172        } else {
173            return exec_err!("Unsupported FileFormatFactory type");
174        };
175
176        let proto = JsonOptionsProto::from(&JsonFormatFactory {
177            options: Some(options),
178        });
179
180        proto
181            .encode(buf)
182            .map_err(|e| exec_datafusion_err!("Failed to encode JsonOptions: {e:?}"))?;
183
184        Ok(())
185    }
186}
187
188#[cfg(feature = "parquet")]
189mod parquet {
190    use super::*;
191
192    use crate::protobuf::TableParquetOptions as TableParquetOptionsProto;
193    use datafusion_common::config::TableParquetOptions;
194    use datafusion_datasource_parquet::file_format::ParquetFormatFactory;
195
196    #[derive(Debug)]
197    pub struct ParquetLogicalExtensionCodec;
198
199    // TODO! This is a placeholder for now and needs to be implemented for real.
200    impl LogicalExtensionCodec for ParquetLogicalExtensionCodec {
201        fn try_decode(
202            &self,
203            _buf: &[u8],
204            _inputs: &[datafusion_expr::LogicalPlan],
205            _ctx: &TaskContext,
206        ) -> datafusion_common::Result<datafusion_expr::Extension> {
207            not_impl_err!("Method not implemented")
208        }
209
210        fn try_encode(
211            &self,
212            _node: &datafusion_expr::Extension,
213            _buf: &mut Vec<u8>,
214        ) -> datafusion_common::Result<()> {
215            not_impl_err!("Method not implemented")
216        }
217
218        fn try_decode_table_provider(
219            &self,
220            _buf: &[u8],
221            _table_ref: &TableReference,
222            _schema: arrow::datatypes::SchemaRef,
223            _ctx: &TaskContext,
224        ) -> datafusion_common::Result<Arc<dyn datafusion_catalog::TableProvider>>
225        {
226            not_impl_err!("Method not implemented")
227        }
228
229        fn try_encode_table_provider(
230            &self,
231            _table_ref: &TableReference,
232            _node: Arc<dyn datafusion_catalog::TableProvider>,
233            _buf: &mut Vec<u8>,
234        ) -> datafusion_common::Result<()> {
235            not_impl_err!("Method not implemented")
236        }
237
238        fn try_decode_file_format(
239            &self,
240            buf: &[u8],
241            _ctx: &TaskContext,
242        ) -> datafusion_common::Result<Arc<dyn FileFormatFactory>> {
243            let proto = TableParquetOptionsProto::decode(buf).map_err(|e| {
244                exec_datafusion_err!("Failed to decode TableParquetOptionsProto: {e:?}")
245            })?;
246            let options = TableParquetOptions::try_from(&proto)?;
247            Ok(Arc::new(ParquetFormatFactory {
248                options: Some(options),
249            }))
250        }
251
252        fn try_encode_file_format(
253            &self,
254            buf: &mut Vec<u8>,
255            node: Arc<dyn FileFormatFactory>,
256        ) -> datafusion_common::Result<()> {
257            use datafusion_datasource_parquet::file_format::ParquetFormatFactory;
258
259            let options = if let Some(parquet_factory) =
260                node.downcast_ref::<ParquetFormatFactory>()
261            {
262                parquet_factory.options.clone().unwrap_or_default()
263            } else {
264                return exec_err!("Unsupported FileFormatFactory type");
265            };
266
267            let proto = TableParquetOptionsProto::from(&ParquetFormatFactory {
268                options: Some(options),
269            });
270
271            proto.encode(buf).map_err(|e| {
272                exec_datafusion_err!("Failed to encode TableParquetOptionsProto: {e:?}")
273            })?;
274
275            Ok(())
276        }
277    }
278
279    #[cfg(test)]
280    mod tests {
281        use super::*;
282        use crate::protobuf::ParquetOptions as ParquetOptionsProto;
283        use datafusion_common::config::ParquetOptions;
284
285        fn encode_table_options(proto: TableParquetOptionsProto) -> Vec<u8> {
286            let mut buf = Vec::new();
287            proto.encode(&mut buf).expect("encode parquet options");
288            buf
289        }
290
291        #[test]
292        fn try_decode_file_format_errors_on_invalid_writer_version() {
293            let proto = TableParquetOptionsProto {
294                global: Some(ParquetOptionsProto {
295                    writer_version: "3.0".to_string(),
296                    ..Default::default()
297                }),
298                ..Default::default()
299            };
300
301            let result = ParquetLogicalExtensionCodec.try_decode_file_format(
302                &encode_table_options(proto),
303                &TaskContext::default(),
304            );
305
306            let err = result.expect_err("invalid writer version should error");
307            assert!(
308                err.to_string()
309                    .contains("Invalid parquet writer version: 3.0"),
310                "{err}"
311            );
312        }
313
314        #[test]
315        fn try_decode_file_format_defaults_empty_writer_version() {
316            let proto = TableParquetOptionsProto {
317                global: Some(ParquetOptionsProto::default()),
318                ..Default::default()
319            };
320
321            let factory = ParquetLogicalExtensionCodec
322                .try_decode_file_format(
323                    &encode_table_options(proto),
324                    &TaskContext::default(),
325                )
326                .expect("decode parquet options");
327            let parquet_factory = factory
328                .downcast_ref::<ParquetFormatFactory>()
329                .expect("parquet format factory");
330            let options = parquet_factory.options.as_ref().expect("parquet options");
331
332            assert_eq!(
333                options.global.writer_version,
334                ParquetOptions::default().writer_version
335            );
336        }
337    }
338}
339#[cfg(feature = "parquet")]
340pub use parquet::ParquetLogicalExtensionCodec;
341
342#[derive(Debug)]
343pub struct ArrowLogicalExtensionCodec;
344
345// TODO! This is a placeholder for now and needs to be implemented for real.
346impl LogicalExtensionCodec for ArrowLogicalExtensionCodec {
347    fn try_decode(
348        &self,
349        _buf: &[u8],
350        _inputs: &[datafusion_expr::LogicalPlan],
351        _ctx: &TaskContext,
352    ) -> datafusion_common::Result<datafusion_expr::Extension> {
353        not_impl_err!("Method not implemented")
354    }
355
356    fn try_encode(
357        &self,
358        _node: &datafusion_expr::Extension,
359        _buf: &mut Vec<u8>,
360    ) -> datafusion_common::Result<()> {
361        not_impl_err!("Method not implemented")
362    }
363
364    fn try_decode_table_provider(
365        &self,
366        _buf: &[u8],
367        _table_ref: &TableReference,
368        _schema: arrow::datatypes::SchemaRef,
369        _ctx: &TaskContext,
370    ) -> datafusion_common::Result<Arc<dyn datafusion_catalog::TableProvider>> {
371        not_impl_err!("Method not implemented")
372    }
373
374    fn try_encode_table_provider(
375        &self,
376        _table_ref: &TableReference,
377        _node: Arc<dyn datafusion_catalog::TableProvider>,
378        _buf: &mut Vec<u8>,
379    ) -> datafusion_common::Result<()> {
380        not_impl_err!("Method not implemented")
381    }
382
383    fn try_decode_file_format(
384        &self,
385        __buf: &[u8],
386        __ctx: &TaskContext,
387    ) -> datafusion_common::Result<Arc<dyn FileFormatFactory>> {
388        Ok(Arc::new(ArrowFormatFactory::new()))
389    }
390
391    fn try_encode_file_format(
392        &self,
393        __buf: &mut Vec<u8>,
394        __node: Arc<dyn FileFormatFactory>,
395    ) -> datafusion_common::Result<()> {
396        Ok(())
397    }
398}
399
400#[derive(Debug)]
401pub struct AvroLogicalExtensionCodec;
402
403// TODO! This is a placeholder for now and needs to be implemented for real.
404impl LogicalExtensionCodec for AvroLogicalExtensionCodec {
405    fn try_decode(
406        &self,
407        _buf: &[u8],
408        _inputs: &[datafusion_expr::LogicalPlan],
409        _ctx: &TaskContext,
410    ) -> datafusion_common::Result<datafusion_expr::Extension> {
411        not_impl_err!("Method not implemented")
412    }
413
414    fn try_encode(
415        &self,
416        _node: &datafusion_expr::Extension,
417        _buf: &mut Vec<u8>,
418    ) -> datafusion_common::Result<()> {
419        not_impl_err!("Method not implemented")
420    }
421
422    fn try_decode_table_provider(
423        &self,
424        _buf: &[u8],
425        _table_ref: &TableReference,
426        _schema: arrow::datatypes::SchemaRef,
427        _cts: &TaskContext,
428    ) -> datafusion_common::Result<Arc<dyn datafusion_catalog::TableProvider>> {
429        not_impl_err!("Method not implemented")
430    }
431
432    fn try_encode_table_provider(
433        &self,
434        _table_ref: &TableReference,
435        _node: Arc<dyn datafusion_catalog::TableProvider>,
436        _buf: &mut Vec<u8>,
437    ) -> datafusion_common::Result<()> {
438        not_impl_err!("Method not implemented")
439    }
440
441    fn try_decode_file_format(
442        &self,
443        __buf: &[u8],
444        __ctx: &TaskContext,
445    ) -> datafusion_common::Result<Arc<dyn FileFormatFactory>> {
446        Ok(Arc::new(ArrowFormatFactory::new()))
447    }
448
449    fn try_encode_file_format(
450        &self,
451        __buf: &mut Vec<u8>,
452        __node: Arc<dyn FileFormatFactory>,
453    ) -> datafusion_common::Result<()> {
454        Ok(())
455    }
456}