1use std::sync::Arc;
27
28use datafusion_common::config::{
29 CsvOptions, JsonOptions, MaxRowGroupBytes, ParquetCdcOptions, ParquetColumnOptions,
30 ParquetOptions, TableParquetOptions,
31};
32use datafusion_common::display::{PlanType, StringifiedPlan};
33use datafusion_common::parsers::{CompressionTypeVariant, CsvQuoteStyle};
34use datafusion_common::{
35 JoinConstraint, JoinType, NullEquality, RecursionUnnestOption, TableReference,
36 UnnestOptions,
37};
38use datafusion_proto_common::FromProtoError as Error;
39
40use crate::protobuf::{
41 self, AnalyzedLogicalPlanType, CsvOptions as CsvOptionsProto,
42 CsvQuoteStyle as CsvQuoteStyleProto, JsonOptions as JsonOptionsProto,
43 OptimizedLogicalPlanType, OptimizedPhysicalPlanType,
44 ParquetCdcOptions as ParquetCdcOptionsProto,
45 ParquetColumnOptions as ParquetColumnOptionsProto,
46 ParquetOptions as ParquetOptionsProto,
47 TableParquetOptions as TableParquetOptionsProto, parquet_column_options,
48 parquet_options,
49 plan_type::PlanTypeEnum::{
50 AnalyzedLogicalPlan, FinalAnalyzedLogicalPlan, FinalLogicalPlan,
51 FinalPhysicalPlan, FinalPhysicalPlanWithSchema, FinalPhysicalPlanWithStats,
52 InitialLogicalPlan, InitialPhysicalPlan, InitialPhysicalPlanWithSchema,
53 InitialPhysicalPlanWithStats, OptimizedLogicalPlan, OptimizedPhysicalPlan,
54 PhysicalPlanError,
55 },
56};
57
58impl From<&protobuf::UnnestOptions> for UnnestOptions {
59 fn from(opts: &protobuf::UnnestOptions) -> Self {
60 use datafusion_common::NullHandling;
61 use protobuf::unnest_options::NullHandling as ProtoNullHandling;
62 let null_handling = match ProtoNullHandling::try_from(opts.null_handling) {
63 Ok(ProtoNullHandling::Preserve) => NullHandling::Preserve,
64 Ok(ProtoNullHandling::Drop) => NullHandling::Drop,
65 Ok(ProtoNullHandling::PreserveAndExpandEmpty) => {
66 NullHandling::PreserveAndExpandEmpty
67 }
68 Err(_) => NullHandling::Preserve,
71 };
72 Self {
73 null_handling,
74 recursions: opts
75 .recursions
76 .iter()
77 .map(|r| RecursionUnnestOption {
78 input_column: r.input_column.as_ref().unwrap().into(),
79 output_column: r.output_column.as_ref().unwrap().into(),
80 depth: r.depth as usize,
81 })
82 .collect::<Vec<_>>(),
83 }
84 }
85}
86
87impl TryFrom<protobuf::TableReference> for TableReference {
88 type Error = Error;
89
90 fn try_from(value: protobuf::TableReference) -> Result<Self, Self::Error> {
91 use protobuf::table_reference::TableReferenceEnum;
92 let table_reference_enum = value
93 .table_reference_enum
94 .ok_or_else(|| Error::required("table_reference_enum"))?;
95
96 match table_reference_enum {
97 TableReferenceEnum::Bare(protobuf::BareTableReference { table }) => {
98 Ok(TableReference::bare(table))
99 }
100 TableReferenceEnum::Partial(protobuf::PartialTableReference {
101 schema,
102 table,
103 }) => Ok(TableReference::partial(schema, table)),
104 TableReferenceEnum::Full(protobuf::FullTableReference {
105 catalog,
106 schema,
107 table,
108 }) => Ok(TableReference::full(catalog, schema, table)),
109 }
110 }
111}
112
113impl From<&protobuf::StringifiedPlan> for StringifiedPlan {
114 fn from(stringified_plan: &protobuf::StringifiedPlan) -> Self {
115 Self {
116 plan_type: match stringified_plan
117 .plan_type
118 .as_ref()
119 .and_then(|pt| pt.plan_type_enum.as_ref())
120 .unwrap_or_else(|| {
121 panic!(
122 "Cannot create protobuf::StringifiedPlan from {stringified_plan:?}"
123 )
124 }) {
125 InitialLogicalPlan(_) => PlanType::InitialLogicalPlan,
126 AnalyzedLogicalPlan(AnalyzedLogicalPlanType { analyzer_name }) => {
127 PlanType::AnalyzedLogicalPlan {
128 analyzer_name:analyzer_name.clone()
129 }
130 }
131 FinalAnalyzedLogicalPlan(_) => PlanType::FinalAnalyzedLogicalPlan,
132 OptimizedLogicalPlan(OptimizedLogicalPlanType { optimizer_name }) => {
133 PlanType::OptimizedLogicalPlan {
134 optimizer_name: optimizer_name.clone(),
135 }
136 }
137 FinalLogicalPlan(_) => PlanType::FinalLogicalPlan,
138 InitialPhysicalPlan(_) => PlanType::InitialPhysicalPlan,
139 InitialPhysicalPlanWithStats(_) => PlanType::InitialPhysicalPlanWithStats,
140 InitialPhysicalPlanWithSchema(_) => PlanType::InitialPhysicalPlanWithSchema,
141 OptimizedPhysicalPlan(OptimizedPhysicalPlanType { optimizer_name }) => {
142 PlanType::OptimizedPhysicalPlan {
143 optimizer_name: optimizer_name.clone(),
144 }
145 }
146 FinalPhysicalPlan(_) => PlanType::FinalPhysicalPlan,
147 FinalPhysicalPlanWithStats(_) => PlanType::FinalPhysicalPlanWithStats,
148 FinalPhysicalPlanWithSchema(_) => PlanType::FinalPhysicalPlanWithSchema,
149 PhysicalPlanError(_) => PlanType::PhysicalPlanError,
150 },
151 plan: Arc::new(stringified_plan.plan.clone()),
152 }
153 }
154}
155
156impl From<protobuf::JoinType> for JoinType {
157 fn from(t: protobuf::JoinType) -> Self {
158 match t {
159 protobuf::JoinType::Inner => JoinType::Inner,
160 protobuf::JoinType::Left => JoinType::Left,
161 protobuf::JoinType::Right => JoinType::Right,
162 protobuf::JoinType::Full => JoinType::Full,
163 protobuf::JoinType::Leftsemi => JoinType::LeftSemi,
164 protobuf::JoinType::Rightsemi => JoinType::RightSemi,
165 protobuf::JoinType::Leftanti => JoinType::LeftAnti,
166 protobuf::JoinType::Rightanti => JoinType::RightAnti,
167 protobuf::JoinType::Leftmark => JoinType::LeftMark,
168 protobuf::JoinType::Rightmark => JoinType::RightMark,
169 }
170 }
171}
172
173impl From<protobuf::JoinConstraint> for JoinConstraint {
174 fn from(t: protobuf::JoinConstraint) -> Self {
175 match t {
176 protobuf::JoinConstraint::On => JoinConstraint::On,
177 protobuf::JoinConstraint::Using => JoinConstraint::Using,
178 }
179 }
180}
181
182impl From<protobuf::NullEquality> for NullEquality {
183 fn from(t: protobuf::NullEquality) -> Self {
184 match t {
185 protobuf::NullEquality::NullEqualsNothing => NullEquality::NullEqualsNothing,
186 protobuf::NullEquality::NullEqualsNull => NullEquality::NullEqualsNull,
187 }
188 }
189}
190
191impl From<&CsvOptionsProto> for CsvOptions {
192 fn from(proto: &CsvOptionsProto) -> Self {
193 CsvOptions {
194 has_header: if !proto.has_header.is_empty() {
195 Some(proto.has_header[0] != 0)
196 } else {
197 None
198 },
199 delimiter: proto.delimiter.first().copied().unwrap_or(b','),
200 quote: proto.quote.first().copied().unwrap_or(b'"'),
201 terminator: if !proto.terminator.is_empty() {
202 Some(proto.terminator[0])
203 } else {
204 None
205 },
206 escape: if !proto.escape.is_empty() {
207 Some(proto.escape[0])
208 } else {
209 None
210 },
211 double_quote: if !proto.double_quote.is_empty() {
212 Some(proto.double_quote[0] != 0)
213 } else {
214 None
215 },
216 compression: match proto.compression {
217 0 => CompressionTypeVariant::GZIP,
218 1 => CompressionTypeVariant::BZIP2,
219 2 => CompressionTypeVariant::XZ,
220 3 => CompressionTypeVariant::ZSTD,
221 _ => CompressionTypeVariant::UNCOMPRESSED,
222 },
223 schema_infer_max_rec: proto.schema_infer_max_rec.map(|v| v as usize),
224 date_format: if proto.date_format.is_empty() {
225 None
226 } else {
227 Some(proto.date_format.clone())
228 },
229 datetime_format: if proto.datetime_format.is_empty() {
230 None
231 } else {
232 Some(proto.datetime_format.clone())
233 },
234 timestamp_format: if proto.timestamp_format.is_empty() {
235 None
236 } else {
237 Some(proto.timestamp_format.clone())
238 },
239 timestamp_tz_format: if proto.timestamp_tz_format.is_empty() {
240 None
241 } else {
242 Some(proto.timestamp_tz_format.clone())
243 },
244 time_format: if proto.time_format.is_empty() {
245 None
246 } else {
247 Some(proto.time_format.clone())
248 },
249 null_value: if proto.null_value.is_empty() {
250 None
251 } else {
252 Some(proto.null_value.clone())
253 },
254 null_regex: if proto.null_regex.is_empty() {
255 None
256 } else {
257 Some(proto.null_regex.clone())
258 },
259 comment: if !proto.comment.is_empty() {
260 Some(proto.comment[0])
261 } else {
262 None
263 },
264 newlines_in_values: if proto.newlines_in_values.is_empty() {
265 None
266 } else {
267 Some(proto.newlines_in_values[0] != 0)
268 },
269 truncated_rows: if proto.truncated_rows.is_empty() {
270 None
271 } else {
272 Some(proto.truncated_rows[0] != 0)
273 },
274 compression_level: proto.compression_level,
275 quote_style: match CsvQuoteStyleProto::try_from(proto.quote_style) {
276 Ok(CsvQuoteStyleProto::Always) => CsvQuoteStyle::Always,
277 Ok(CsvQuoteStyleProto::NonNumeric) => CsvQuoteStyle::NonNumeric,
278 Ok(CsvQuoteStyleProto::Never) => CsvQuoteStyle::Never,
279 Ok(CsvQuoteStyleProto::Necessary) => CsvQuoteStyle::Necessary,
280 _ => CsvQuoteStyle::Necessary,
281 },
282 ignore_leading_whitespace: if proto.ignore_leading_whitespace.is_empty() {
283 None
284 } else {
285 Some(proto.ignore_leading_whitespace[0] != 0)
286 },
287 ignore_trailing_whitespace: if proto.ignore_trailing_whitespace.is_empty() {
288 None
289 } else {
290 Some(proto.ignore_trailing_whitespace[0] != 0)
291 },
292 }
293 }
294}
295
296impl From<&JsonOptionsProto> for JsonOptions {
297 fn from(proto: &JsonOptionsProto) -> Self {
298 JsonOptions {
299 compression: match proto.compression {
300 0 => CompressionTypeVariant::GZIP,
301 1 => CompressionTypeVariant::BZIP2,
302 2 => CompressionTypeVariant::XZ,
303 3 => CompressionTypeVariant::ZSTD,
304 _ => CompressionTypeVariant::UNCOMPRESSED,
305 },
306 schema_infer_max_rec: proto.schema_infer_max_rec.map(|v| v as usize),
307 compression_level: proto.compression_level,
308 newline_delimited: proto.newline_delimited.unwrap_or(true),
309 }
310 }
311}
312
313impl From<ParquetCdcOptionsProto> for ParquetCdcOptions {
314 fn from(value: ParquetCdcOptionsProto) -> Self {
315 ParquetCdcOptions {
316 enabled: value.enabled,
317 min_chunk_size: value.min_chunk_size as usize,
318 max_chunk_size: value.max_chunk_size as usize,
319 norm_level: value.norm_level,
320 }
321 }
322}
323
324impl TryFrom<&ParquetOptionsProto> for ParquetOptions {
325 type Error = datafusion_common::DataFusionError;
326
327 fn try_from(
328 proto: &ParquetOptionsProto,
329 ) -> datafusion_common::Result<Self, Self::Error> {
330 let writer_version = match proto.writer_version.as_str() {
331 "" => ParquetOptions::default().writer_version,
335 version => version.parse()?,
336 };
337
338 Ok(ParquetOptions {
339 enable_page_index: proto.enable_page_index,
340 pruning: proto.pruning,
341 skip_metadata: proto.skip_metadata,
342 metadata_size_hint: proto
343 .metadata_size_hint_opt
344 .as_ref()
345 .map(|opt| match opt {
346 parquet_options::MetadataSizeHintOpt::MetadataSizeHint(size) => {
347 *size as usize
348 }
349 }),
350 pushdown_filters: proto.pushdown_filters,
351 reorder_filters: proto.reorder_filters,
352 force_filter_selections: proto.force_filter_selections,
353 data_pagesize_limit: proto.data_pagesize_limit as usize,
354 write_batch_size: proto.write_batch_size as usize,
355 writer_version,
356 compression: proto.compression_opt.as_ref().map(|opt| match opt {
357 parquet_options::CompressionOpt::Compression(compression) => {
358 compression.clone()
359 }
360 }),
361 dictionary_enabled: proto.dictionary_enabled_opt.as_ref().map(|opt| {
362 match opt {
363 parquet_options::DictionaryEnabledOpt::DictionaryEnabled(
364 enabled,
365 ) => *enabled,
366 }
367 }),
368 dictionary_page_size_limit: proto.dictionary_page_size_limit as usize,
369 statistics_enabled: proto.statistics_enabled_opt.as_ref().map(
370 |opt| match opt {
371 parquet_options::StatisticsEnabledOpt::StatisticsEnabled(
372 statistics,
373 ) => statistics.clone(),
374 },
375 ),
376 max_row_group_size: proto.max_row_group_size as usize,
377 max_in_list_size: proto.max_in_list_size as usize,
378 created_by: proto.created_by.clone(),
379 column_index_truncate_length: proto
380 .column_index_truncate_length_opt
381 .as_ref()
382 .map(|opt| match opt {
383 parquet_options::ColumnIndexTruncateLengthOpt::ColumnIndexTruncateLength(length) => *length as usize,
384 }),
385 statistics_truncate_length: proto
386 .statistics_truncate_length_opt
387 .as_ref()
388 .map(|opt| match opt {
389 parquet_options::StatisticsTruncateLengthOpt::StatisticsTruncateLength(length) => *length as usize,
390 }),
391 data_page_row_count_limit: proto.data_page_row_count_limit as usize,
392 encoding: proto.encoding_opt.as_ref().map(|opt| match opt {
393 parquet_options::EncodingOpt::Encoding(encoding) => {
394 encoding.clone()
395 }
396 }),
397 bloom_filter_on_read: proto.bloom_filter_on_read,
398 bloom_filter_on_write: proto.bloom_filter_on_write,
399 bloom_filter_fpp: proto
400 .bloom_filter_fpp_opt
401 .as_ref()
402 .map(|opt| match opt {
403 parquet_options::BloomFilterFppOpt::BloomFilterFpp(fpp) => *fpp,
404 }),
405 bloom_filter_ndv: proto
406 .bloom_filter_ndv_opt
407 .as_ref()
408 .map(|opt| match opt {
409 parquet_options::BloomFilterNdvOpt::BloomFilterNdv(ndv) => *ndv,
410 }),
411 allow_single_file_parallelism: proto.allow_single_file_parallelism,
412 maximum_parallel_row_group_writers: proto
413 .maximum_parallel_row_group_writers
414 as usize,
415 maximum_buffered_record_batches_per_stream: proto
416 .maximum_buffered_record_batches_per_stream
417 as usize,
418 schema_force_view_types: proto.schema_force_view_types,
419 binary_as_string: proto.binary_as_string,
420 skip_arrow_metadata: proto.skip_arrow_metadata,
421 coerce_int96: proto.coerce_int96_opt.as_ref().map(|opt| match opt {
422 parquet_options::CoerceInt96Opt::CoerceInt96(coerce_int96) => {
423 coerce_int96.clone()
424 }
425 }),
426 coerce_int96_tz: proto
427 .coerce_int96_tz_opt
428 .as_ref()
429 .map(|opt| match opt {
430 parquet_options::CoerceInt96TzOpt::CoerceInt96Tz(tz) => {
431 tz.clone()
432 }
433 }),
434 max_predicate_cache_size: proto
435 .max_predicate_cache_size_opt
436 .as_ref()
437 .map(|opt| match opt {
438 parquet_options::MaxPredicateCacheSizeOpt::MaxPredicateCacheSize(
439 size,
440 ) => *size as usize,
441 }),
442 max_row_group_bytes: proto
443 .max_row_group_bytes_opt
444 .as_ref()
445 .and_then(|opt| match opt {
446 parquet_options::MaxRowGroupBytesOpt::MaxRowGroupBytes(size) => {
447 MaxRowGroupBytes::try_new(*size as usize).ok()
448 }
449 }),
450 content_defined_chunking: proto
451 .content_defined_chunking
452 .map(ParquetCdcOptions::from)
453 .unwrap_or_default(),
454 })
455 }
456}
457
458impl From<ParquetColumnOptionsProto> for ParquetColumnOptions {
459 fn from(proto: ParquetColumnOptionsProto) -> Self {
460 ParquetColumnOptions {
461 bloom_filter_enabled: proto.bloom_filter_enabled_opt.map(
462 |parquet_column_options::BloomFilterEnabledOpt::BloomFilterEnabled(v)| v,
463 ),
464 encoding: proto
465 .encoding_opt
466 .map(|parquet_column_options::EncodingOpt::Encoding(v)| v),
467 dictionary_enabled: proto.dictionary_enabled_opt.map(
468 |parquet_column_options::DictionaryEnabledOpt::DictionaryEnabled(v)| v,
469 ),
470 compression: proto
471 .compression_opt
472 .map(|parquet_column_options::CompressionOpt::Compression(v)| v),
473 statistics_enabled: proto.statistics_enabled_opt.map(
474 |parquet_column_options::StatisticsEnabledOpt::StatisticsEnabled(v)| v,
475 ),
476 bloom_filter_fpp: proto
477 .bloom_filter_fpp_opt
478 .map(|parquet_column_options::BloomFilterFppOpt::BloomFilterFpp(v)| v),
479 bloom_filter_ndv: proto
480 .bloom_filter_ndv_opt
481 .map(|parquet_column_options::BloomFilterNdvOpt::BloomFilterNdv(v)| v),
482 }
483 }
484}
485
486impl TryFrom<&TableParquetOptionsProto> for TableParquetOptions {
487 type Error = datafusion_common::DataFusionError;
488
489 fn try_from(
490 proto: &TableParquetOptionsProto,
491 ) -> datafusion_common::Result<Self, Self::Error> {
492 Ok(TableParquetOptions {
493 global: proto
494 .global
495 .as_ref()
496 .map(ParquetOptions::try_from)
497 .transpose()?
498 .unwrap_or_default(),
499 column_specific_options: proto
500 .column_specific_options
501 .iter()
502 .map(|parquet_column_options| {
503 (
504 parquet_column_options.column_name.clone(),
505 ParquetColumnOptions::from(
506 parquet_column_options.options.clone().unwrap_or_default(),
507 ),
508 )
509 })
510 .collect(),
511 key_value_metadata: proto
512 .key_value_metadata
513 .iter()
514 .map(|(k, v)| (k.clone(), Some(v.clone())))
515 .collect(),
516 ..Default::default()
517 })
518 }
519}