1use super::plan::agg as proto_agg;
4use super::schema::data_type::Kind as DataTypeKind;
5use super::schema::metadata_value::Value as MetadataValueKind;
6use super::schema::primitive_type::Kind as PrimitiveTypeKind;
7use super::schema::SimplePrimitiveType as Simple;
8use super::{
9 expressions as proto_expr, operation as proto_op, plan as proto_plan, schema as proto_schema,
10};
11use crate::expressions::{
12 ArrayData, BinaryExpression, BinaryExpressionOp, BinaryPredicate, BinaryPredicateOp,
13 ColumnName, DecimalData, Expression, ExpressionFieldPatch, ExpressionStructPatch,
14 JunctionPredicate, JunctionPredicateOp, MapData, MapToStructExpression, OpaqueExpression,
15 OpaquePredicate, ParseJsonExpression, Predicate, Scalar, StructData, UnaryExpression,
16 UnaryExpressionOp, UnaryPredicate, UnaryPredicateOp, VariadicExpression, VariadicExpressionOp,
17};
18use crate::plans::ir::nodes::{
19 Agg, Aggregate, FileType, Filter, Load, LoadColumnFileMeta, Operator, Project, ScanFile,
20 ScanJson, ScanParquet, SemiJoin, Values,
21};
22use crate::plans::ir::plan::{Plan, PlanNode};
23use crate::plans::{IoOperation, Operation};
24use crate::schema::{
25 ArrayType, DataType, DecimalType, MapType, MetadataValue, PrimitiveType, StructField,
26 StructType,
27};
28use crate::{DeltaResult, Error, FileMeta, FileSlice};
29
30fn convert_vec<'a, T, U>(items: &'a [T]) -> Vec<U>
34where
35 U: From<&'a T>,
36{
37 items.iter().map(U::from).collect()
38}
39
40fn convert_expr_vec<E>(items: &[E]) -> Vec<proto_expr::Expression>
42where
43 E: AsRef<Expression>,
44{
45 items.iter().map(|e| e.as_ref().into()).collect()
46}
47
48impl From<&Operation> for proto_op::Operation {
51 fn from(op: &Operation) -> Self {
52 let op = match op {
53 Operation::IoOperation(io) => proto_op::operation::Op::Io(io.into()),
54 Operation::QueryPlan(plan) => proto_op::operation::Op::QueryPlan(plan.into()),
55 };
56 proto_op::Operation { op: Some(op) }
57 }
58}
59
60impl From<&IoOperation> for proto_op::IoOperation {
61 fn from(io: &IoOperation) -> Self {
62 use proto_op::io_operation::Op;
63 let op = match io {
64 IoOperation::FileListing { url } => Op::FileListing(proto_op::FileListing {
65 url: url.to_string(),
66 }),
67 IoOperation::ReadBytes { files } => Op::ReadBytes(proto_op::ReadBytes {
68 files: convert_vec(files),
69 }),
70 IoOperation::WriteBytes {
71 url,
72 data,
73 overwrite,
74 } => Op::WriteBytes(proto_op::WriteBytes {
75 url: url.to_string(),
76 data: data.to_vec(),
77 overwrite: *overwrite,
78 }),
79 IoOperation::HeadFile { url } => Op::HeadFile(proto_op::HeadFile {
80 url: url.to_string(),
81 }),
82 IoOperation::AtomicCopy {
83 source,
84 destination,
85 } => Op::AtomicCopy(proto_op::AtomicCopy {
86 source: source.to_string(),
87 destination: destination.to_string(),
88 }),
89 IoOperation::ParquetFooter { file } => Op::ParquetFooter(proto_op::ParquetFooter {
90 file: Some(file.into()),
91 }),
92 };
93 proto_op::IoOperation { op: Some(op) }
94 }
95}
96
97impl From<&FileSlice> for proto_op::FileSlice {
98 fn from(slice: &FileSlice) -> Self {
99 let (url, range) = slice;
100 proto_op::FileSlice {
101 url: url.to_string(),
102 range_start: range.as_ref().map(|r| r.start),
103 range_end: range.as_ref().map(|r| r.end),
104 }
105 }
106}
107
108impl From<&FileMeta> for proto_plan::FileMeta {
109 fn from(meta: &FileMeta) -> Self {
110 proto_plan::FileMeta {
111 location: meta.location.to_string(),
112 size: meta.size,
113 last_modified: meta.last_modified,
114 }
115 }
116}
117
118impl From<&Plan> for proto_plan::Plan {
121 fn from(plan: &Plan) -> Self {
122 proto_plan::Plan {
123 nodes: convert_vec(&plan.nodes),
124 }
125 }
126}
127
128impl From<&PlanNode> for proto_plan::PlanNode {
129 fn from(node: &PlanNode) -> Self {
130 proto_plan::PlanNode {
131 op: Some((&node.op).into()),
132 inputs: node.inputs.iter().map(|&i| i as u32).collect(),
133 }
134 }
135}
136
137impl From<&Operator> for proto_plan::Operator {
138 fn from(op: &Operator) -> Self {
139 use proto_plan::operator::Op;
140 let op = match op {
141 Operator::ScanParquet(n) => Op::ScanParquet(n.into()),
142 Operator::ScanJson(n) => Op::ScanJson(n.into()),
143 Operator::Values(n) => Op::Values(n.into()),
144 Operator::Project(n) => Op::Project(n.into()),
145 Operator::Filter(n) => Op::Filter(n.into()),
146 Operator::Load(n) => Op::Load(n.into()),
147 Operator::Aggregate(n) => Op::Aggregate(n.into()),
148 Operator::SemiJoin(n) => Op::SemiJoin(n.into()),
149 Operator::UnionAll(_) => Op::UnionAll(proto_plan::UnionAllNode {}),
150 };
151 proto_plan::Operator { op: Some(op) }
152 }
153}
154
155impl From<&ScanFile> for proto_plan::ScanFile {
156 fn from(file: &ScanFile) -> Self {
157 proto_plan::ScanFile {
158 meta: Some((&file.meta).into()),
159 file_constants: convert_vec(&file.file_constants),
160 }
161 }
162}
163
164impl From<&ScanParquet> for proto_plan::ScanParquetNode {
165 fn from(node: &ScanParquet) -> Self {
166 proto_plan::ScanParquetNode {
167 files: convert_vec(&node.files),
168 file_constant_columns: node.file_constant_columns.clone(),
169 schema: Some(node.schema.as_ref().into()),
170 }
171 }
172}
173
174impl From<&ScanJson> for proto_plan::ScanJsonNode {
175 fn from(node: &ScanJson) -> Self {
176 proto_plan::ScanJsonNode {
177 files: convert_vec(&node.files),
178 file_constant_columns: node.file_constant_columns.clone(),
179 schema: Some(node.schema.as_ref().into()),
180 }
181 }
182}
183
184impl From<&Values> for proto_plan::ValuesNode {
185 fn from(node: &Values) -> Self {
186 let rows = node
187 .rows
188 .iter()
189 .map(|row| proto_plan::ValuesRow {
190 values: convert_vec(row),
191 })
192 .collect();
193 proto_plan::ValuesNode {
194 schema: Some(node.schema.as_ref().into()),
195 rows,
196 }
197 }
198}
199
200impl From<&Project> for proto_plan::ProjectNode {
201 fn from(node: &Project) -> Self {
202 proto_plan::ProjectNode {
203 expr: Some(node.expr.as_ref().into()),
204 schema: Some(node.schema.as_ref().into()),
205 }
206 }
207}
208
209impl From<&Filter> for proto_plan::FilterNode {
210 fn from(node: &Filter) -> Self {
211 proto_plan::FilterNode {
212 predicate: Some(node.predicate.as_ref().into()),
213 }
214 }
215}
216
217impl From<&Load> for proto_plan::LoadNode {
218 fn from(node: &Load) -> Self {
219 proto_plan::LoadNode {
220 schema: Some(node.schema.as_ref().into()),
221 file_type: proto_plan::FileType::from(node.file_type) as i32,
222 base_url: node.base_url.as_ref().map(ToString::to_string),
223 file_constant_columns: node.file_constant_columns.clone(),
224 file_meta: Some((&node.file_meta).into()),
225 dv_column: Some((&node.dv_column).into()),
226 }
227 }
228}
229
230impl From<&LoadColumnFileMeta> for proto_plan::LoadColumnFileMeta {
231 fn from(meta: &LoadColumnFileMeta) -> Self {
232 proto_plan::LoadColumnFileMeta {
233 path_column: Some((&meta.path_column).into()),
234 file_size_column: Some((&meta.file_size_column).into()),
235 num_records_column: Some((&meta.num_records_column).into()),
236 }
237 }
238}
239
240impl From<&Aggregate> for proto_plan::AggregateNode {
241 fn from(node: &Aggregate) -> Self {
242 proto_plan::AggregateNode {
243 group_by: convert_vec(&node.group_by),
244 aggs: convert_vec(&node.aggs),
245 schema: Some(node.schema.as_ref().into()),
246 }
247 }
248}
249
250impl From<&Agg> for proto_plan::Agg {
251 fn from(agg: &Agg) -> Self {
252 let func = match agg {
253 Agg::Min { value } => proto_agg::Func::Min(proto_plan::MinAgg {
254 value: Some(value.into()),
255 }),
256 Agg::Max { value } => proto_agg::Func::Max(proto_plan::MaxAgg {
257 value: Some(value.into()),
258 }),
259 Agg::MinNonNullBy { value, key } => {
260 proto_agg::Func::MinNonNullBy(proto_plan::MinNonNullByAgg {
261 value: Some(value.into()),
262 key: Some(key.into()),
263 })
264 }
265 Agg::MaxNonNullBy { value, key } => {
266 proto_agg::Func::MaxNonNullBy(proto_plan::MaxNonNullByAgg {
267 value: Some(value.into()),
268 key: Some(key.into()),
269 })
270 }
271 };
272 proto_plan::Agg { func: Some(func) }
273 }
274}
275
276impl From<&SemiJoin> for proto_plan::SemiJoinNode {
277 fn from(node: &SemiJoin) -> Self {
278 proto_plan::SemiJoinNode {
279 inverted: node.inverted,
280 probe_keys: convert_vec(&node.probe_keys),
281 build_keys: convert_vec(&node.build_keys),
282 }
283 }
284}
285
286impl From<FileType> for proto_plan::FileType {
287 fn from(file_type: FileType) -> Self {
288 match file_type {
289 FileType::Parquet => proto_plan::FileType::Parquet,
290 FileType::Json => proto_plan::FileType::Json,
291 }
292 }
293}
294
295impl From<&Expression> for proto_expr::Expression {
298 fn from(expr: &Expression) -> Self {
299 use proto_expr::expression::Kind;
300 let kind = match expr {
301 Expression::Literal(scalar) => Kind::Literal(scalar.into()),
302 Expression::Column(column) => Kind::Column(column.into()),
303 Expression::Predicate(pred) => Kind::Predicate(Box::new(pred.as_ref().into())),
304 Expression::Struct(exprs, nullability) => {
305 let nullability_predicate =
306 nullability.as_ref().map(|n| Box::new(n.as_ref().into()));
307 Kind::StructExpr(Box::new(proto_expr::StructExpression {
308 exprs: convert_expr_vec(exprs),
309 nullability_predicate,
310 }))
311 }
312 Expression::StructPatch(patch) => Kind::Transform(patch.into()),
313 Expression::Unary(unary) => Kind::Unary(Box::new(unary.into())),
314 Expression::Binary(binary) => Kind::Binary(Box::new(binary.into())),
315 Expression::Variadic(variadic) => Kind::Variadic(variadic.into()),
316 Expression::Opaque(opaque) => Kind::Opaque(opaque.into()),
317 Expression::Unknown(name) => Kind::Unknown(name.clone()),
318 Expression::ParseJson(parse_json) => Kind::ParseJson(Box::new(parse_json.into())),
319 Expression::MapToStruct(map_to_struct) => {
320 Kind::MapToStruct(Box::new(map_to_struct.into()))
321 }
322 };
323 proto_expr::Expression { kind: Some(kind) }
324 }
325}
326
327impl From<&Predicate> for proto_expr::Predicate {
328 fn from(pred: &Predicate) -> Self {
329 use proto_expr::predicate::Kind;
330 let kind = match pred {
331 Predicate::BooleanExpression(expr) => Kind::BooleanExpression(Box::new(expr.into())),
332 Predicate::Not(inner) => Kind::Not(Box::new(inner.as_ref().into())),
333 Predicate::Unary(unary) => Kind::Unary(Box::new(unary.into())),
334 Predicate::Binary(binary) => Kind::Binary(Box::new(binary.into())),
335 Predicate::Junction(junction) => Kind::Junction(junction.into()),
336 Predicate::Opaque(opaque) => Kind::Opaque(opaque.into()),
337 Predicate::Unknown(name) => Kind::Unknown(name.clone()),
338 };
339 proto_expr::Predicate { kind: Some(kind) }
340 }
341}
342
343impl From<&ColumnName> for proto_expr::ColumnName {
344 fn from(column: &ColumnName) -> Self {
345 proto_expr::ColumnName {
346 path: column.path().to_vec(),
347 }
348 }
349}
350
351impl From<&UnaryExpression> for proto_expr::UnaryExpression {
352 fn from(unary: &UnaryExpression) -> Self {
353 proto_expr::UnaryExpression {
354 op: proto_expr::UnaryExpressionOp::from(unary.op) as i32,
355 expr: Some(Box::new(unary.expr.as_ref().into())),
356 }
357 }
358}
359
360impl From<&BinaryExpression> for proto_expr::BinaryExpression {
361 fn from(binary: &BinaryExpression) -> Self {
362 proto_expr::BinaryExpression {
363 op: proto_expr::BinaryExpressionOp::from(binary.op) as i32,
364 left: Some(Box::new(binary.left.as_ref().into())),
365 right: Some(Box::new(binary.right.as_ref().into())),
366 }
367 }
368}
369
370impl From<&VariadicExpression> for proto_expr::VariadicExpression {
371 fn from(variadic: &VariadicExpression) -> Self {
372 proto_expr::VariadicExpression {
373 op: proto_expr::VariadicExpressionOp::from(variadic.op) as i32,
374 exprs: convert_vec(&variadic.exprs),
375 }
376 }
377}
378
379impl From<&OpaqueExpression> for proto_expr::OpaqueExpression {
380 fn from(opaque: &OpaqueExpression) -> Self {
381 proto_expr::OpaqueExpression {
382 name: opaque.op.name().to_string(),
383 exprs: convert_vec(&opaque.exprs),
384 }
385 }
386}
387
388impl From<&ParseJsonExpression> for proto_expr::ParseJsonExpression {
389 fn from(parse_json: &ParseJsonExpression) -> Self {
390 proto_expr::ParseJsonExpression {
391 json_expr: Some(Box::new(parse_json.json_expr.as_ref().into())),
392 output_schema: Some(parse_json.output_schema.as_ref().into()),
393 }
394 }
395}
396
397impl From<&MapToStructExpression> for proto_expr::MapToStructExpression {
398 fn from(map_to_struct: &MapToStructExpression) -> Self {
399 proto_expr::MapToStructExpression {
400 map_expr: Some(Box::new(map_to_struct.map_expr.as_ref().into())),
401 }
402 }
403}
404
405impl From<&UnaryPredicate> for proto_expr::UnaryPredicate {
406 fn from(unary: &UnaryPredicate) -> Self {
407 proto_expr::UnaryPredicate {
408 op: proto_expr::UnaryPredicateOp::from(unary.op) as i32,
409 expr: Some(Box::new(unary.expr.as_ref().into())),
410 }
411 }
412}
413
414impl From<&BinaryPredicate> for proto_expr::BinaryPredicate {
415 fn from(binary: &BinaryPredicate) -> Self {
416 proto_expr::BinaryPredicate {
417 op: proto_expr::BinaryPredicateOp::from(binary.op) as i32,
418 left: Some(Box::new(binary.left.as_ref().into())),
419 right: Some(Box::new(binary.right.as_ref().into())),
420 }
421 }
422}
423
424impl From<&JunctionPredicate> for proto_expr::JunctionPredicate {
425 fn from(junction: &JunctionPredicate) -> Self {
426 proto_expr::JunctionPredicate {
427 op: proto_expr::JunctionPredicateOp::from(junction.op) as i32,
428 preds: convert_vec(&junction.preds),
429 }
430 }
431}
432
433impl From<&OpaquePredicate> for proto_expr::OpaquePredicate {
434 fn from(opaque: &OpaquePredicate) -> Self {
435 proto_expr::OpaquePredicate {
436 name: opaque.op.name().to_string(),
437 exprs: convert_vec(&opaque.exprs),
438 }
439 }
440}
441
442impl From<&ExpressionStructPatch> for proto_expr::Transform {
444 fn from(patch: &ExpressionStructPatch) -> Self {
445 let field_transforms = patch
446 .field_patches
447 .iter()
448 .map(|(name, field_patch)| (name.clone(), field_patch.into()))
449 .collect();
450 proto_expr::Transform {
451 input_path: patch.input_path.as_ref().map(Into::into),
452 field_transforms,
453 prepended_fields: convert_expr_vec(&patch.prepended_fields),
454 appended_fields: convert_expr_vec(&patch.appended_fields),
455 }
456 }
457}
458
459impl From<&ExpressionFieldPatch> for proto_expr::FieldTransform {
461 fn from(field_patch: &ExpressionFieldPatch) -> Self {
462 proto_expr::FieldTransform {
463 exprs: convert_expr_vec(&field_patch.insertions),
464 is_replace: !field_patch.keep_input,
465 optional: field_patch.optional,
466 }
467 }
468}
469
470impl From<UnaryExpressionOp> for proto_expr::UnaryExpressionOp {
471 fn from(op: UnaryExpressionOp) -> Self {
472 match op {
473 UnaryExpressionOp::ToJson => proto_expr::UnaryExpressionOp::ToJson,
474 }
475 }
476}
477
478impl From<BinaryExpressionOp> for proto_expr::BinaryExpressionOp {
479 fn from(op: BinaryExpressionOp) -> Self {
480 match op {
481 BinaryExpressionOp::Plus => proto_expr::BinaryExpressionOp::Plus,
482 BinaryExpressionOp::Minus => proto_expr::BinaryExpressionOp::Minus,
483 BinaryExpressionOp::Multiply => proto_expr::BinaryExpressionOp::Multiply,
484 BinaryExpressionOp::Divide => proto_expr::BinaryExpressionOp::Divide,
485 }
486 }
487}
488
489impl From<VariadicExpressionOp> for proto_expr::VariadicExpressionOp {
490 fn from(op: VariadicExpressionOp) -> Self {
491 match op {
492 VariadicExpressionOp::Coalesce => proto_expr::VariadicExpressionOp::Coalesce,
493 VariadicExpressionOp::Array => proto_expr::VariadicExpressionOp::Array,
494 }
495 }
496}
497
498impl From<UnaryPredicateOp> for proto_expr::UnaryPredicateOp {
499 fn from(op: UnaryPredicateOp) -> Self {
500 match op {
501 UnaryPredicateOp::IsNull => proto_expr::UnaryPredicateOp::IsNull,
502 }
503 }
504}
505
506impl From<BinaryPredicateOp> for proto_expr::BinaryPredicateOp {
507 fn from(op: BinaryPredicateOp) -> Self {
508 match op {
509 BinaryPredicateOp::LessThan => proto_expr::BinaryPredicateOp::LessThan,
510 BinaryPredicateOp::GreaterThan => proto_expr::BinaryPredicateOp::GreaterThan,
511 BinaryPredicateOp::Equal => proto_expr::BinaryPredicateOp::Equal,
512 BinaryPredicateOp::Distinct => proto_expr::BinaryPredicateOp::Distinct,
513 BinaryPredicateOp::In => proto_expr::BinaryPredicateOp::In,
514 }
515 }
516}
517
518impl From<JunctionPredicateOp> for proto_expr::JunctionPredicateOp {
519 fn from(op: JunctionPredicateOp) -> Self {
520 match op {
521 JunctionPredicateOp::And => proto_expr::JunctionPredicateOp::And,
522 JunctionPredicateOp::Or => proto_expr::JunctionPredicateOp::Or,
523 }
524 }
525}
526
527impl From<&Scalar> for proto_expr::Scalar {
530 fn from(scalar: &Scalar) -> Self {
531 use proto_expr::scalar::Value;
532 let value = match scalar {
533 Scalar::Integer(v) => Value::Integer(*v),
534 Scalar::Long(v) => Value::Long(*v),
535 Scalar::Short(v) => Value::Short(*v as i32),
536 Scalar::Byte(v) => Value::Byte(*v as i32),
537 Scalar::Float(v) => Value::Float(*v),
538 Scalar::Double(v) => Value::Double(*v),
539 Scalar::String(v) => Value::String(v.clone()),
540 Scalar::Boolean(v) => Value::Boolean(*v),
541 Scalar::Timestamp(v) => Value::Timestamp(*v),
542 Scalar::TimestampNtz(v) => Value::TimestampNtz(*v),
543 #[cfg(feature = "nanosecond-timestamps")]
544 Scalar::TimestampNanos(v) => Value::TimestampNanos(*v),
545 #[cfg(feature = "nanosecond-timestamps")]
546 Scalar::TimestampNanosNtz(v) => Value::TimestampNanosNtz(*v),
547 Scalar::Date(v) => Value::Date(*v),
548 Scalar::Binary(v) => Value::Binary(v.clone()),
549 Scalar::Decimal(decimal) => Value::Decimal(decimal.into()),
550 Scalar::Null(data_type) => Value::Null(data_type.into()),
551 Scalar::Struct(struct_data) => Value::Struct(struct_data.into()),
552 Scalar::Array(array_data) => Value::Array(array_data.into()),
553 Scalar::Map(map_data) => Value::Map(map_data.into()),
554 };
555 proto_expr::Scalar { value: Some(value) }
556 }
557}
558
559impl From<&DecimalData> for proto_expr::DecimalData {
560 fn from(decimal: &DecimalData) -> Self {
561 proto_expr::DecimalData {
562 bits: decimal.bits().to_be_bytes().to_vec(),
563 decimal_type: Some((*decimal.ty()).into()),
564 }
565 }
566}
567
568impl From<&StructData> for proto_expr::StructData {
569 fn from(struct_data: &StructData) -> Self {
570 proto_expr::StructData {
571 fields: convert_vec(struct_data.fields()),
572 values: convert_vec(struct_data.values()),
573 }
574 }
575}
576
577impl From<&ArrayData> for proto_expr::ArrayData {
578 fn from(array_data: &ArrayData) -> Self {
579 proto_expr::ArrayData {
580 array_type: Some(array_data.array_type().into()),
581 elements: convert_vec(array_data.array_elements()),
582 }
583 }
584}
585
586impl From<&MapData> for proto_expr::MapData {
587 fn from(map_data: &MapData) -> Self {
588 let pairs = map_data
589 .pairs()
590 .iter()
591 .map(|(key, value)| proto_expr::MapEntry {
592 key: Some(key.into()),
593 value: Some(value.into()),
594 })
595 .collect();
596 proto_expr::MapData {
597 map_type: Some(map_data.map_type().into()),
598 pairs,
599 }
600 }
601}
602
603impl From<&DataType> for proto_schema::DataType {
606 fn from(data_type: &DataType) -> Self {
607 let kind = match data_type {
608 DataType::Primitive(primitive) => DataTypeKind::Primitive(primitive.into()),
609 DataType::Array(array) => DataTypeKind::Array(Box::new(array.as_ref().into())),
610 DataType::Struct(struct_type) => DataTypeKind::Struct(struct_type.as_ref().into()),
611 DataType::Map(map) => DataTypeKind::Map(Box::new(map.as_ref().into())),
612 DataType::Variant(_) => DataTypeKind::Variant(proto_schema::VariantType {}),
614 };
615 proto_schema::DataType { kind: Some(kind) }
616 }
617}
618
619impl From<&PrimitiveType> for proto_schema::PrimitiveType {
620 fn from(primitive: &PrimitiveType) -> Self {
621 let kind = match primitive {
622 PrimitiveType::String => Kind::Simple(Simple::String as i32),
623 PrimitiveType::Long => Kind::Simple(Simple::Long as i32),
624 PrimitiveType::Integer => Kind::Simple(Simple::Integer as i32),
625 PrimitiveType::Short => Kind::Simple(Simple::Short as i32),
626 PrimitiveType::Byte => Kind::Simple(Simple::Byte as i32),
627 PrimitiveType::Float => Kind::Simple(Simple::Float as i32),
628 PrimitiveType::Double => Kind::Simple(Simple::Double as i32),
629 PrimitiveType::Boolean => Kind::Simple(Simple::Boolean as i32),
630 PrimitiveType::Binary => Kind::Simple(Simple::Binary as i32),
631 PrimitiveType::Date => Kind::Simple(Simple::Date as i32),
632 PrimitiveType::Timestamp => Kind::Simple(Simple::Timestamp as i32),
633 PrimitiveType::TimestampNtz => Kind::Simple(Simple::TimestampNtz as i32),
634 #[cfg(feature = "nanosecond-timestamps")]
635 PrimitiveType::TimestampNanos => Kind::Simple(Simple::TimestampNanos as i32),
636 #[cfg(feature = "nanosecond-timestamps")]
637 PrimitiveType::TimestampNanosNtz => Kind::Simple(Simple::TimestampNanosNtz as i32),
638 PrimitiveType::Decimal(decimal) => Kind::Decimal((*decimal).into()),
639 PrimitiveType::Void => Kind::Simple(Simple::Void as i32),
640 PrimitiveType::IntervalYearMonth => Kind::Simple(Simple::IntervalYearMonth as i32),
641 PrimitiveType::IntervalDayTime => Kind::Simple(Simple::IntervalDayTime as i32),
642 };
643 proto_schema::PrimitiveType { kind: Some(kind) }
644 }
645}
646
647impl From<DecimalType> for proto_schema::DecimalType {
648 fn from(decimal: DecimalType) -> Self {
649 proto_schema::DecimalType {
650 precision: u32::from(decimal.precision()),
651 scale: u32::from(decimal.scale()),
652 }
653 }
654}
655
656impl From<&ArrayType> for proto_schema::ArrayType {
657 fn from(array: &ArrayType) -> Self {
658 proto_schema::ArrayType {
659 element_type: Some(Box::new(array.element_type().into())),
660 contains_null: array.contains_null(),
661 }
662 }
663}
664
665impl From<&MapType> for proto_schema::MapType {
666 fn from(map: &MapType) -> Self {
667 proto_schema::MapType {
668 key_type: Some(Box::new(map.key_type().into())),
669 value_type: Some(Box::new(map.value_type().into())),
670 value_contains_null: map.value_contains_null(),
671 }
672 }
673}
674
675impl From<&StructType> for proto_schema::StructType {
676 fn from(struct_type: &StructType) -> Self {
677 proto_schema::StructType {
678 fields: struct_type.fields().map(Into::into).collect(),
679 }
680 }
681}
682
683impl From<&StructField> for proto_schema::StructField {
684 fn from(field: &StructField) -> Self {
685 let metadata = field
686 .metadata
687 .iter()
688 .map(|(key, value)| (key.clone(), value.into()))
689 .collect();
690 proto_schema::StructField {
691 name: field.name.clone(),
692 data_type: Some((&field.data_type).into()),
693 nullable: field.nullable,
694 metadata,
695 }
696 }
697}
698
699impl From<&MetadataValue> for proto_schema::MetadataValue {
700 fn from(metadata: &MetadataValue) -> Self {
701 let value = match metadata {
702 MetadataValue::Number(n) => MetadataValueKind::Number(*n),
703 MetadataValue::String(s) => MetadataValueKind::String(s.clone()),
704 MetadataValue::Boolean(b) => MetadataValueKind::Boolean(*b),
705 MetadataValue::Other(json) => MetadataValueKind::OtherJson(json.to_string()),
706 };
707 proto_schema::MetadataValue { value: Some(value) }
708 }
709}
710
711impl TryFrom<proto_schema::StructType> for StructType {
714 type Error = Error;
715 fn try_from(proto: proto_schema::StructType) -> DeltaResult<Self> {
716 let fields = proto
717 .fields
718 .into_iter()
719 .map(StructField::try_from)
720 .collect::<DeltaResult<Vec<_>>>()?;
721 StructType::try_new(fields)
722 }
723}
724
725impl TryFrom<proto_schema::StructField> for StructField {
726 type Error = Error;
727
728 fn try_from(proto: proto_schema::StructField) -> DeltaResult<Self> {
729 let data_type = proto
730 .data_type
731 .ok_or_else(|| Error::schema("StructField proto missing data_type"))?;
732 let metadata = proto
733 .metadata
734 .into_iter()
735 .map(|(key, value)| Ok::<_, Error>((key, MetadataValue::try_from(value)?)))
736 .collect::<DeltaResult<std::collections::HashMap<_, _>>>()?;
737 Ok(StructField {
738 name: proto.name,
739 data_type: DataType::try_from(data_type)?,
740 nullable: proto.nullable,
741 metadata,
742 })
743 }
744}
745
746impl TryFrom<proto_schema::DataType> for DataType {
747 type Error = Error;
748 fn try_from(proto: proto_schema::DataType) -> DeltaResult<Self> {
749 let kind = proto
750 .kind
751 .ok_or_else(|| Error::schema("DataType proto missing kind"))?;
752 let data_type = match kind {
753 DataTypeKind::Primitive(primitive) => DataType::Primitive(primitive.try_into()?),
754 DataTypeKind::Array(array) => DataType::Array(Box::new((*array).try_into()?)),
755 DataTypeKind::Struct(struct_type) => {
756 DataType::Struct(Box::new(struct_type.try_into()?))
757 }
758 DataTypeKind::Map(map) => DataType::Map(Box::new((*map).try_into()?)),
759 DataTypeKind::Variant(_) => DataType::unshredded_variant(),
761 };
762 Ok(data_type)
763 }
764}
765
766impl TryFrom<proto_schema::PrimitiveType> for PrimitiveType {
767 type Error = Error;
768 fn try_from(proto: proto_schema::PrimitiveType) -> DeltaResult<Self> {
769 let kind = proto
770 .kind
771 .ok_or_else(|| Error::schema("PrimitiveType proto missing kind"))?;
772 let primitive = match kind {
773 PrimitiveTypeKind::Simple(simple) => {
774 let simple = Simple::try_from(simple).map_err(|_| {
775 Error::schema(format!("unknown SimplePrimitiveType value: {simple}"))
776 })?;
777 match simple {
778 Simple::String => PrimitiveType::String,
779 Simple::Long => PrimitiveType::Long,
780 Simple::Integer => PrimitiveType::Integer,
781 Simple::Short => PrimitiveType::Short,
782 Simple::Byte => PrimitiveType::Byte,
783 Simple::Float => PrimitiveType::Float,
784 Simple::Double => PrimitiveType::Double,
785 Simple::Boolean => PrimitiveType::Boolean,
786 Simple::Binary => PrimitiveType::Binary,
787 Simple::Date => PrimitiveType::Date,
788 Simple::Timestamp => PrimitiveType::Timestamp,
789 Simple::TimestampNtz => PrimitiveType::TimestampNtz,
790 Simple::Void => PrimitiveType::Void,
791 Simple::IntervalYearMonth => PrimitiveType::IntervalYearMonth,
792 Simple::IntervalDayTime => PrimitiveType::IntervalDayTime,
793 Simple::Unspecified => {
794 return Err(Error::schema("SimplePrimitiveType is unspecified"))
795 }
796 }
797 }
798 PrimitiveTypeKind::Decimal(decimal) => PrimitiveType::Decimal(decimal.try_into()?),
799 };
800 Ok(primitive)
801 }
802}
803
804impl TryFrom<proto_schema::DecimalType> for DecimalType {
805 type Error = Error;
806 fn try_from(proto: proto_schema::DecimalType) -> DeltaResult<Self> {
807 let precision = u8::try_from(proto.precision).map_err(|_| {
808 Error::invalid_decimal(format!("precision out of range: {}", proto.precision))
809 })?;
810 let scale = u8::try_from(proto.scale)
811 .map_err(|_| Error::invalid_decimal(format!("scale out of range: {}", proto.scale)))?;
812 DecimalType::try_new(precision, scale)
813 }
814}
815
816impl TryFrom<proto_schema::ArrayType> for ArrayType {
817 type Error = Error;
818 fn try_from(proto: proto_schema::ArrayType) -> DeltaResult<Self> {
819 let element_type = proto
820 .element_type
821 .ok_or_else(|| Error::schema("ArrayType proto missing element_type"))?;
822 Ok(ArrayType::new(
823 DataType::try_from(*element_type)?,
824 proto.contains_null,
825 ))
826 }
827}
828
829impl TryFrom<proto_schema::MapType> for MapType {
830 type Error = Error;
831 fn try_from(proto: proto_schema::MapType) -> DeltaResult<Self> {
832 let key_type = proto
833 .key_type
834 .ok_or_else(|| Error::schema("MapType proto missing key_type"))?;
835 let value_type = proto
836 .value_type
837 .ok_or_else(|| Error::schema("MapType proto missing value_type"))?;
838 Ok(MapType::new(
839 DataType::try_from(*key_type)?,
840 DataType::try_from(*value_type)?,
841 proto.value_contains_null,
842 ))
843 }
844}
845
846impl TryFrom<proto_schema::MetadataValue> for MetadataValue {
847 type Error = Error;
848 fn try_from(proto: proto_schema::MetadataValue) -> DeltaResult<Self> {
849 let value = proto
850 .value
851 .ok_or_else(|| Error::schema("MetadataValue proto missing value"))?;
852 let metadata = match value {
853 MetadataValueKind::Number(n) => MetadataValue::Number(n),
854 MetadataValueKind::String(s) => MetadataValue::String(s),
855 MetadataValueKind::Boolean(b) => MetadataValue::Boolean(b),
856 MetadataValueKind::OtherJson(json) => {
857 MetadataValue::Other(serde_json::from_str(&json)?)
858 }
859 };
860 Ok(metadata)
861 }
862}
863
864#[cfg(test)]
865mod tests {
866 use std::sync::Arc;
867
868 use bytes::Bytes;
869 use rstest::rstest;
870 use url::Url;
871
872 use crate::expressions::{
873 lit, ArrayData, BinaryExpressionOp, BinaryPredicateOp, ColumnName, DecimalData, Expression,
874 ExpressionStructPatchBuilder, JunctionPredicateOp, MapData, OpaqueExpressionOp,
875 OpaquePredicateOp, Predicate, Scalar, ScalarExpressionEvaluator, StructData,
876 UnaryExpressionOp, UnaryPredicateOp, VariadicExpressionOp,
877 };
878 use crate::kernel_predicates::{
879 DirectDataSkippingPredicateEvaluator, DirectPredicateEvaluator,
880 IndirectDataSkippingPredicateEvaluator,
881 };
882 use crate::plans::ir::nodes::{
883 Agg, Aggregate, FileType, Filter, Load, LoadColumnFileMeta, Operator, Project, ScanFile,
884 ScanJson, ScanParquet, SemiJoin, UnionAll, Values,
885 };
886 use crate::plans::ir::plan::{Plan, PlanNode};
887 use crate::plans::proto::{
888 expressions as proto_expr, operation as proto_op, plan as proto_plan,
889 schema as proto_schema,
890 };
891 use crate::plans::{IoOperation, Operation};
892 use crate::schema::{
893 ArrayType, DataType, DecimalType, MapType, MetadataValue, PrimitiveType, SchemaRef,
894 StructField, StructType,
895 };
896 use crate::{DeltaResult, FileMeta, FileSlice};
897
898 #[derive(Debug, PartialEq)]
901 struct TestOpaqueExprOp;
902
903 impl OpaqueExpressionOp for TestOpaqueExprOp {
904 fn name(&self) -> &str {
905 "test_opaque_expr"
906 }
907 fn eval_expr_scalar(
908 &self,
909 _eval_expr: &ScalarExpressionEvaluator<'_>,
910 _exprs: &[Expression],
911 ) -> DeltaResult<Scalar> {
912 Ok(Scalar::Integer(0))
913 }
914 }
915
916 #[derive(Debug, PartialEq)]
917 struct TestOpaquePredOp;
918
919 impl OpaquePredicateOp for TestOpaquePredOp {
920 fn name(&self) -> &str {
921 "test_opaque_pred"
922 }
923 fn eval_pred_scalar(
924 &self,
925 _eval_expr: &ScalarExpressionEvaluator<'_>,
926 _eval_pred: &DirectPredicateEvaluator<'_>,
927 _exprs: &[Expression],
928 _inverted: bool,
929 ) -> DeltaResult<Option<bool>> {
930 Ok(Some(true))
931 }
932 fn eval_as_data_skipping_predicate(
933 &self,
934 _evaluator: &DirectDataSkippingPredicateEvaluator<'_>,
935 _exprs: &[Expression],
936 _inverted: bool,
937 ) -> Option<bool> {
938 None
939 }
940 fn as_data_skipping_predicate(
941 &self,
942 _evaluator: &IndirectDataSkippingPredicateEvaluator<'_>,
943 _exprs: &[Expression],
944 _inverted: bool,
945 ) -> Option<Predicate> {
946 None
947 }
948 }
949
950 fn sample_file_meta() -> FileMeta {
951 FileMeta {
952 location: Url::parse("memory:///table/part-0.parquet").unwrap(),
953 last_modified: 123,
954 size: 456,
955 }
956 }
957
958 fn sample_schema() -> SchemaRef {
959 Arc::new(StructType::try_new(vec![StructField::nullable("id", DataType::INTEGER)]).unwrap())
960 }
961
962 fn decode(op: &Operation) -> proto_op::Operation {
963 let bytes = op.to_proto_bytes();
964 prost::Message::decode(bytes.as_slice()).expect("decode succeeds")
965 }
966
967 fn io_op(op: &Operation) -> proto_op::io_operation::Op {
968 let Some(proto_op::operation::Op::Io(io)) = decode(op).op else {
969 panic!("expected an IoOperation");
970 };
971 io.op.expect("io op present")
972 }
973
974 fn expr_kind_of(expr: Expression) -> proto_expr::expression::Kind {
975 proto_expr::Expression::from(&expr)
976 .kind
977 .expect("expression kind present")
978 }
979
980 fn pred_kind_of(pred: Predicate) -> proto_expr::predicate::Kind {
981 proto_expr::Predicate::from(&pred)
982 .kind
983 .expect("predicate kind present")
984 }
985
986 fn scalar_value_of(scalar: Scalar) -> proto_expr::scalar::Value {
987 proto_expr::Scalar::from(&scalar)
988 .value
989 .expect("scalar value present")
990 }
991
992 #[rstest]
995 #[case(
996 Operation::IoOperation(IoOperation::head_file(Url::parse("memory:///h").unwrap())),
997 "io"
998 )]
999 #[case(Operation::QueryPlan(Plan { nodes: vec![] }), "query_plan")]
1000 fn from_operation(#[case] op: Operation, #[case] expected: &str) {
1001 use proto_op::operation::Op;
1002 let kind = match decode(&op).op.unwrap() {
1003 Op::Io(_) => "io",
1004 Op::QueryPlan(_) => "query_plan",
1005 };
1006 assert_eq!(kind, expected);
1007 }
1008
1009 #[test]
1010 fn from_io_operation_file_listing() {
1011 let url = "memory:///table/";
1012 let op = Operation::IoOperation(IoOperation::file_listing(Url::parse(url).unwrap()));
1013 let proto_op::io_operation::Op::FileListing(file_listing) = io_op(&op) else {
1014 panic!("expected FileListing");
1015 };
1016 assert_eq!(file_listing.url, url);
1017 }
1018
1019 #[test]
1020 fn from_io_operation_read_bytes() {
1021 let files = vec![
1022 (Url::parse("memory:///a").unwrap(), Some(0u64..10u64)),
1023 (Url::parse("memory:///b").unwrap(), None),
1024 ];
1025 let op = Operation::IoOperation(IoOperation::read_bytes(files));
1026 let proto_op::io_operation::Op::ReadBytes(read_bytes) = io_op(&op) else {
1027 panic!("expected ReadBytes");
1028 };
1029 assert_eq!(read_bytes.files.len(), 2);
1030 assert_eq!(read_bytes.files[0].url, "memory:///a");
1031 }
1032
1033 #[test]
1034 fn from_io_operation_write_bytes() {
1035 let op = Operation::IoOperation(IoOperation::write_bytes(
1036 Url::parse("memory:///out").unwrap(),
1037 Bytes::from_static(b"hello"),
1038 true,
1039 ));
1040 let proto_op::io_operation::Op::WriteBytes(write_bytes) = io_op(&op) else {
1041 panic!("expected WriteBytes");
1042 };
1043 assert_eq!(write_bytes.url, "memory:///out");
1044 assert_eq!(write_bytes.data, b"hello");
1045 assert!(write_bytes.overwrite);
1046 }
1047
1048 #[test]
1049 fn from_io_operation_head_file() {
1050 let op = Operation::IoOperation(IoOperation::head_file(
1051 Url::parse("memory:///head").unwrap(),
1052 ));
1053 let proto_op::io_operation::Op::HeadFile(head_file) = io_op(&op) else {
1054 panic!("expected HeadFile");
1055 };
1056 assert_eq!(head_file.url, "memory:///head");
1057 }
1058
1059 #[test]
1060 fn from_io_operation_atomic_copy() {
1061 let op = Operation::IoOperation(IoOperation::atomic_copy(
1062 Url::parse("memory:///src").unwrap(),
1063 Url::parse("memory:///dst").unwrap(),
1064 ));
1065 let proto_op::io_operation::Op::AtomicCopy(atomic_copy) = io_op(&op) else {
1066 panic!("expected AtomicCopy");
1067 };
1068 assert_eq!(atomic_copy.source, "memory:///src");
1069 assert_eq!(atomic_copy.destination, "memory:///dst");
1070 }
1071
1072 #[test]
1073 fn from_io_operation_parquet_footer() {
1074 let op = Operation::IoOperation(IoOperation::parquet_footer(sample_file_meta()));
1075 let proto_op::io_operation::Op::ParquetFooter(parquet_footer) = io_op(&op) else {
1076 panic!("expected ParquetFooter");
1077 };
1078 let file = parquet_footer.file.expect("file meta present");
1079 assert_eq!(file.location, "memory:///table/part-0.parquet");
1080 assert_eq!(file.size, 456);
1081 assert_eq!(file.last_modified, 123);
1082 }
1083
1084 #[rstest]
1085 #[case(Some(0u64..10u64), Some(0), Some(10))]
1086 #[case(None, None, None)]
1087 fn from_file_slice(
1088 #[case] range: Option<std::ops::Range<u64>>,
1089 #[case] expected_start: Option<u64>,
1090 #[case] expected_end: Option<u64>,
1091 ) {
1092 let slice: FileSlice = (Url::parse("memory:///a").unwrap(), range);
1093 let proto = proto_op::FileSlice::from(&slice);
1094 assert_eq!(proto.url, "memory:///a");
1095 assert_eq!(proto.range_start, expected_start);
1096 assert_eq!(proto.range_end, expected_end);
1097 }
1098
1099 #[test]
1100 fn from_file_meta() {
1101 let proto = proto_plan::FileMeta::from(&sample_file_meta());
1102 assert_eq!(proto.location, "memory:///table/part-0.parquet");
1103 assert_eq!(proto.size, 456);
1104 assert_eq!(proto.last_modified, 123);
1105 }
1106
1107 #[test]
1110 fn from_plan() {
1111 let schema = Arc::new(
1112 StructType::try_new(vec![
1113 StructField::nullable("id", DataType::INTEGER),
1114 StructField::not_null("name", DataType::STRING),
1115 ])
1116 .unwrap(),
1117 );
1118 let plan = Plan {
1119 nodes: vec![
1120 PlanNode {
1121 op: Operator::ScanParquet(ScanParquet {
1122 files: vec![ScanFile::new(sample_file_meta())],
1123 file_constant_columns: vec![],
1124 schema: schema.clone(),
1125 }),
1126 inputs: vec![],
1127 },
1128 PlanNode {
1129 op: Operator::Filter(Filter {
1130 predicate: Arc::new(Predicate::gt(
1131 Expression::Column(ColumnName::new(["id"])),
1132 lit(5i32),
1133 )),
1134 }),
1135 inputs: vec![0],
1136 },
1137 ],
1138 };
1139
1140 let Some(proto_op::operation::Op::QueryPlan(plan)) = decode(&Operation::QueryPlan(plan)).op
1141 else {
1142 panic!("expected a QueryPlan");
1143 };
1144 assert_eq!(plan.nodes.len(), 2);
1145
1146 let source = &plan.nodes[0];
1148 assert!(source.inputs.is_empty());
1149 let Some(proto_plan::operator::Op::ScanParquet(scan)) = &source.op.as_ref().unwrap().op
1150 else {
1151 panic!("expected ScanParquet");
1152 };
1153 assert_eq!(scan.files.len(), 1);
1154 assert_eq!(scan.schema.as_ref().unwrap().fields.len(), 2);
1155
1156 let filter_node = &plan.nodes[1];
1158 assert_eq!(filter_node.inputs.len(), 1);
1159 assert_eq!(filter_node.inputs[0], 0);
1160 let Some(proto_plan::operator::Op::Filter(filter)) = &filter_node.op.as_ref().unwrap().op
1161 else {
1162 panic!("expected Filter");
1163 };
1164 let predicate = filter.predicate.as_ref().unwrap();
1165 let Some(proto_expr::predicate::Kind::Binary(binary)) = &predicate.kind else {
1166 panic!("expected a binary predicate");
1167 };
1168 assert_eq!(binary.op, proto_expr::BinaryPredicateOp::GreaterThan as i32);
1169 assert!(matches!(
1170 binary.left.as_ref().unwrap().kind,
1171 Some(proto_expr::expression::Kind::Column(_))
1172 ));
1173 }
1174
1175 #[rstest]
1176 #[case(
1177 Operator::ScanParquet(ScanParquet {
1178 files: vec![],
1179 file_constant_columns: vec![],
1180 schema: sample_schema(),
1181 }),
1182 "scan_parquet"
1183 )]
1184 #[case(
1185 Operator::ScanJson(ScanJson {
1186 files: vec![],
1187 file_constant_columns: vec![],
1188 schema: sample_schema(),
1189 }),
1190 "scan_json"
1191 )]
1192 #[case(Operator::Values(Values { schema: sample_schema(), rows: vec![] }), "values")]
1193 #[case(
1194 Operator::Project(Project {
1195 expr: Arc::new(Expression::struct_from([lit(1)])),
1196 schema: sample_schema(),
1197 }),
1198 "project"
1199 )]
1200 #[case(Operator::Filter(Filter { predicate: Arc::new(Predicate::literal(true)) }), "filter")]
1201 #[case(
1202 Operator::Load(Load {
1203 schema: sample_schema(),
1204 file_type: FileType::Parquet,
1205 base_url: None,
1206 file_constant_columns: vec![],
1207 file_meta: sample_load_column_file_meta(),
1208 dv_column: ColumnName::new(["dv"]),
1209 }),
1210 "load"
1211 )]
1212 #[case(
1213 Operator::Aggregate(Aggregate {
1214 group_by: vec![],
1215 aggs: vec![],
1216 schema: sample_schema(),
1217 }),
1218 "aggregate"
1219 )]
1220 #[case(
1221 Operator::SemiJoin(SemiJoin { inverted: false, probe_keys: vec![], build_keys: vec![] }),
1222 "semi_join"
1223 )]
1224 #[case(Operator::UnionAll(UnionAll), "union_all")]
1225 fn from_operator(#[case] op: Operator, #[case] expected: &str) {
1226 use proto_plan::operator::Op;
1227 let kind = match proto_plan::Operator::from(&op).op.unwrap() {
1228 Op::ScanParquet(_) => "scan_parquet",
1229 Op::ScanJson(_) => "scan_json",
1230 Op::Values(_) => "values",
1231 Op::Project(_) => "project",
1232 Op::Filter(_) => "filter",
1233 Op::Load(_) => "load",
1234 Op::Aggregate(_) => "aggregate",
1235 Op::SemiJoin(_) => "semi_join",
1236 Op::UnionAll(_) => "union_all",
1237 };
1238 assert_eq!(kind, expected);
1239 }
1240
1241 #[test]
1242 fn from_scan_file() {
1243 let scan_file = ScanFile {
1244 meta: sample_file_meta(),
1245 file_constants: vec![Scalar::Integer(1), Scalar::String("x".into())],
1246 };
1247 let proto = proto_plan::ScanFile::from(&scan_file);
1248 assert!(proto.meta.is_some());
1249 assert_eq!(proto.file_constants.len(), 2);
1250 }
1251
1252 #[test]
1253 fn from_scan_parquet() {
1254 let node = ScanParquet {
1255 files: vec![ScanFile::new(sample_file_meta())],
1256 file_constant_columns: vec!["c".to_string()],
1257 schema: sample_schema(),
1258 };
1259 let proto = proto_plan::ScanParquetNode::from(&node);
1260 assert_eq!(proto.files.len(), 1);
1261 assert_eq!(proto.file_constant_columns.len(), 1);
1262 assert!(proto.schema.is_some());
1263 }
1264
1265 #[test]
1266 fn from_scan_json() {
1267 let node = ScanJson {
1268 files: vec![ScanFile::new(sample_file_meta())],
1269 file_constant_columns: vec!["c".to_string()],
1270 schema: sample_schema(),
1271 };
1272 let proto = proto_plan::ScanJsonNode::from(&node);
1273 assert_eq!(proto.files.len(), 1);
1274 assert_eq!(proto.file_constant_columns.len(), 1);
1275 assert!(proto.schema.is_some());
1276 }
1277
1278 #[test]
1279 fn from_values() {
1280 let node = Values {
1281 schema: sample_schema(),
1282 rows: vec![vec![Scalar::Integer(1)], vec![Scalar::Integer(2)]],
1283 };
1284 let proto = proto_plan::ValuesNode::from(&node);
1285 assert!(proto.schema.is_some());
1286 assert_eq!(proto.rows.len(), 2);
1287 assert_eq!(proto.rows[0].values.len(), 1);
1288 }
1289
1290 #[test]
1291 fn from_project() {
1292 let node = Project {
1293 expr: Arc::new(Expression::struct_from([lit(1)])),
1294 schema: sample_schema(),
1295 };
1296 let proto = proto_plan::ProjectNode::from(&node);
1297 assert!(proto.expr.is_some());
1298 assert!(proto.schema.is_some());
1299 }
1300
1301 #[test]
1302 fn from_filter() {
1303 let node = Filter {
1304 predicate: Arc::new(Predicate::literal(true)),
1305 };
1306 let proto = proto_plan::FilterNode::from(&node);
1307 assert!(proto.predicate.is_some());
1308 }
1309
1310 fn sample_load_column_file_meta() -> LoadColumnFileMeta {
1311 LoadColumnFileMeta {
1312 path_column: ColumnName::new(["path"]),
1313 file_size_column: ColumnName::new(["size"]),
1314 num_records_column: ColumnName::new(["num_records"]),
1315 }
1316 }
1317
1318 #[rstest]
1319 #[case(
1320 FileType::Json,
1321 Some(Url::parse("memory:///base/").unwrap()),
1322 Some("memory:///base/")
1323 )]
1324 #[case(FileType::Parquet, None, None)]
1325 fn from_load(
1326 #[case] file_type: FileType,
1327 #[case] base_url: Option<Url>,
1328 #[case] expected_base_url: Option<&str>,
1329 ) {
1330 let node = Load {
1331 schema: sample_schema(),
1332 file_type,
1333 base_url,
1334 file_constant_columns: vec!["c".to_string()],
1335 file_meta: sample_load_column_file_meta(),
1336 dv_column: ColumnName::new(["dv"]),
1337 };
1338 let proto = proto_plan::LoadNode::from(&node);
1339 assert!(proto.schema.is_some());
1340 assert_eq!(
1341 proto.file_type,
1342 proto_plan::FileType::from(file_type) as i32
1343 );
1344 assert_eq!(proto.base_url.as_deref(), expected_base_url);
1345 assert_eq!(proto.file_constant_columns.len(), 1);
1346 assert!(proto.dv_column.is_some());
1347
1348 let file_meta = proto.file_meta.expect("file_meta present");
1349 assert!(file_meta.path_column.is_some());
1350 assert!(file_meta.file_size_column.is_some());
1351 assert!(file_meta.num_records_column.is_some());
1352 }
1353
1354 #[test]
1355 fn from_aggregate() {
1356 let node = Aggregate {
1357 group_by: vec![ColumnName::new(["g"])],
1358 aggs: vec![Agg::max(ColumnName::new(["a"]))],
1359 schema: sample_schema(),
1360 };
1361 let proto = proto_plan::AggregateNode::from(&node);
1362 assert_eq!(proto.group_by.len(), 1);
1363 assert_eq!(proto.aggs.len(), 1);
1364 assert!(proto.aggs[0].func.is_some());
1365 assert!(proto.schema.is_some());
1366 }
1367
1368 #[rstest]
1369 #[case(Agg::min(ColumnName::new(["a"])), "min")]
1370 #[case(Agg::max(ColumnName::new(["a"])), "max")]
1371 #[case(Agg::min_non_null_by(ColumnName::new(["a"]), ColumnName::new(["k"])), "min_non_null_by")]
1372 #[case(Agg::max_non_null_by(ColumnName::new(["a"]), ColumnName::new(["k"])), "max_non_null_by")]
1373 fn from_agg(#[case] agg: Agg, #[case] expected: &str) {
1374 use proto_plan::agg::Func;
1375 let proto = proto_plan::Agg::from(&agg);
1376 let kind = match proto.func.unwrap() {
1377 Func::Min(_) => "min",
1378 Func::Max(_) => "max",
1379 Func::MinNonNullBy(_) => "min_non_null_by",
1380 Func::MaxNonNullBy(_) => "max_non_null_by",
1381 };
1382 assert_eq!(kind, expected);
1383 }
1384
1385 #[rstest]
1386 #[case(true)]
1387 #[case(false)]
1388 fn from_semi_join(#[case] inverted: bool) {
1389 let node = SemiJoin {
1390 inverted,
1391 probe_keys: vec![ColumnName::new(["p"])],
1392 build_keys: vec![ColumnName::new(["b"])],
1393 };
1394 let proto = proto_plan::SemiJoinNode::from(&node);
1395 assert_eq!(proto.inverted, inverted);
1396 assert_eq!(proto.probe_keys.len(), 1);
1397 assert_eq!(proto.build_keys.len(), 1);
1398 }
1399
1400 #[rstest]
1401 #[case(FileType::Parquet, proto_plan::FileType::Parquet)]
1402 #[case(FileType::Json, proto_plan::FileType::Json)]
1403 fn from_file_type(#[case] value: FileType, #[case] expected: proto_plan::FileType) {
1404 assert_eq!(proto_plan::FileType::from(value) as i32, expected as i32);
1405 }
1406
1407 #[rstest]
1410 #[case(lit(1), "literal")]
1411 #[case(Expression::Column(ColumnName::new(["a"])), "column")]
1412 #[case(Expression::Predicate(Box::new(Predicate::literal(true))), "predicate")]
1413 #[case(Expression::struct_from([lit(1)]), "struct_expr")]
1414 #[case(
1415 Expression::StructPatch(
1416 ExpressionStructPatchBuilder::new().append(lit(1)).build().unwrap(),
1417 ),
1418 "transform"
1419 )]
1420 #[case(Expression::unary(UnaryExpressionOp::ToJson, lit(1)), "unary")]
1421 #[case(Expression::binary(BinaryExpressionOp::Plus, lit(1), lit(2)), "binary")]
1422 #[case(Expression::coalesce([lit(1), lit(2)]), "variadic")]
1423 #[case(Expression::opaque(TestOpaqueExprOp, [lit(1)]), "opaque")]
1424 #[case(Expression::parse_json(lit("{}"), sample_schema()), "parse_json")]
1425 #[case(Expression::map_to_struct(Expression::Column(ColumnName::new(["m"]))), "map_to_struct")]
1426 #[case(Expression::unknown("x"), "unknown")]
1427 fn from_expression(#[case] expr: Expression, #[case] expected: &str) {
1428 use proto_expr::expression::Kind;
1429 let kind = match proto_expr::Expression::from(&expr).kind.unwrap() {
1430 Kind::Literal(_) => "literal",
1431 Kind::Column(_) => "column",
1432 Kind::Predicate(_) => "predicate",
1433 Kind::StructExpr(_) => "struct_expr",
1434 Kind::Transform(_) => "transform",
1435 Kind::Unary(_) => "unary",
1436 Kind::Binary(_) => "binary",
1437 Kind::Variadic(_) => "variadic",
1438 Kind::IfExpr(_) => "if_expr",
1439 Kind::Opaque(_) => "opaque",
1440 Kind::ParseJson(_) => "parse_json",
1441 Kind::MapToStruct(_) => "map_to_struct",
1442 Kind::Unknown(_) => "unknown",
1443 };
1444 assert_eq!(kind, expected);
1445 }
1446
1447 #[rstest]
1448 #[case(Predicate::literal(true), "boolean_expression")]
1449 #[case(Predicate::not(Predicate::literal(true)), "not")]
1450 #[case(Predicate::is_null(lit(1)), "unary")]
1451 #[case(Predicate::gt(lit(1), lit(2)), "binary")]
1452 #[case(
1453 Predicate::and(Predicate::literal(true), Predicate::literal(false)),
1454 "junction"
1455 )]
1456 #[case(Predicate::opaque(TestOpaquePredOp, [lit(1)]), "opaque")]
1457 #[case(Predicate::Unknown("x".to_string()), "unknown")]
1458 fn from_predicate(#[case] pred: Predicate, #[case] expected: &str) {
1459 use proto_expr::predicate::Kind;
1460 let kind = match proto_expr::Predicate::from(&pred).kind.unwrap() {
1461 Kind::BooleanExpression(_) => "boolean_expression",
1462 Kind::Not(_) => "not",
1463 Kind::Unary(_) => "unary",
1464 Kind::Binary(_) => "binary",
1465 Kind::Junction(_) => "junction",
1466 Kind::Opaque(_) => "opaque",
1467 Kind::Unknown(_) => "unknown",
1468 };
1469 assert_eq!(kind, expected);
1470 }
1471
1472 #[test]
1473 fn from_column_name() {
1474 let proto = proto_expr::ColumnName::from(&ColumnName::new(["a", "b", "c"]));
1475 assert_eq!(proto.path, vec!["a", "b", "c"]);
1476 }
1477
1478 #[test]
1479 fn from_struct_expression() {
1480 let proto_expr::expression::Kind::StructExpr(plain) =
1481 expr_kind_of(Expression::struct_from([lit(1), lit(2)]))
1482 else {
1483 panic!("expected a struct expression");
1484 };
1485 assert_eq!(plain.exprs.len(), 2);
1486 assert!(plain.nullability_predicate.is_none());
1487
1488 let proto_expr::expression::Kind::StructExpr(guarded) = expr_kind_of(
1489 Expression::struct_with_nullability_from([lit(1)], lit(true)),
1490 ) else {
1491 panic!("expected a struct expression");
1492 };
1493 assert_eq!(guarded.exprs.len(), 1);
1494 assert!(guarded.nullability_predicate.is_some());
1495 }
1496
1497 #[test]
1498 fn from_unary_expression() {
1499 let proto_expr::expression::Kind::Unary(unary) =
1500 expr_kind_of(Expression::unary(UnaryExpressionOp::ToJson, lit(1)))
1501 else {
1502 panic!("expected a unary expression");
1503 };
1504 assert_eq!(unary.op, proto_expr::UnaryExpressionOp::ToJson as i32);
1505 assert!(unary.expr.is_some());
1506 }
1507
1508 #[test]
1509 fn from_binary_expression() {
1510 let proto_expr::expression::Kind::Binary(binary) =
1511 expr_kind_of(Expression::binary(BinaryExpressionOp::Plus, lit(1), lit(2)))
1512 else {
1513 panic!("expected a binary expression");
1514 };
1515 assert_eq!(binary.op, proto_expr::BinaryExpressionOp::Plus as i32);
1516 assert!(binary.left.is_some());
1517 assert!(binary.right.is_some());
1518 }
1519
1520 #[test]
1521 fn from_variadic_expression() {
1522 let proto_expr::expression::Kind::Variadic(variadic) =
1523 expr_kind_of(Expression::coalesce([lit(1), lit(2), lit(3)]))
1524 else {
1525 panic!("expected a variadic expression");
1526 };
1527 assert_eq!(
1528 variadic.op,
1529 proto_expr::VariadicExpressionOp::Coalesce as i32
1530 );
1531 assert_eq!(variadic.exprs.len(), 3);
1532 }
1533
1534 #[test]
1535 fn from_opaque_expression() {
1536 let proto_expr::expression::Kind::Opaque(opaque) =
1537 expr_kind_of(Expression::opaque(TestOpaqueExprOp, [lit(1), lit(2)]))
1538 else {
1539 panic!("expected an opaque expression");
1540 };
1541 assert_eq!(opaque.name, "test_opaque_expr");
1542 assert_eq!(opaque.exprs.len(), 2);
1543 }
1544
1545 #[test]
1546 fn from_parse_json_expression() {
1547 let proto_expr::expression::Kind::ParseJson(parse_json) =
1548 expr_kind_of(Expression::parse_json(lit("{}"), sample_schema()))
1549 else {
1550 panic!("expected a parse_json expression");
1551 };
1552 assert!(parse_json.json_expr.is_some());
1553 assert!(parse_json.output_schema.is_some());
1554 }
1555
1556 #[test]
1557 fn from_map_to_struct_expression() {
1558 let proto_expr::expression::Kind::MapToStruct(map_to_struct) = expr_kind_of(
1559 Expression::map_to_struct(Expression::Column(ColumnName::new(["m"]))),
1560 ) else {
1561 panic!("expected a map_to_struct expression");
1562 };
1563 assert!(map_to_struct.map_expr.is_some());
1564 }
1565
1566 #[test]
1567 fn from_unary_predicate() {
1568 let proto_expr::predicate::Kind::Unary(unary) = pred_kind_of(Predicate::is_null(lit(1)))
1569 else {
1570 panic!("expected a unary predicate");
1571 };
1572 assert_eq!(unary.op, proto_expr::UnaryPredicateOp::IsNull as i32);
1573 assert!(unary.expr.is_some());
1574 }
1575
1576 #[test]
1577 fn from_binary_predicate() {
1578 let proto_expr::predicate::Kind::Binary(binary) =
1579 pred_kind_of(Predicate::gt(lit(1), lit(2)))
1580 else {
1581 panic!("expected a binary predicate");
1582 };
1583 assert_eq!(binary.op, proto_expr::BinaryPredicateOp::GreaterThan as i32);
1584 assert!(binary.left.is_some());
1585 assert!(binary.right.is_some());
1586 }
1587
1588 #[test]
1589 fn from_junction_predicate() {
1590 let proto_expr::predicate::Kind::Junction(junction) = pred_kind_of(Predicate::and(
1591 Predicate::literal(true),
1592 Predicate::literal(false),
1593 )) else {
1594 panic!("expected a junction predicate");
1595 };
1596 assert_eq!(junction.op, proto_expr::JunctionPredicateOp::And as i32);
1597 assert_eq!(junction.preds.len(), 2);
1598 }
1599
1600 #[test]
1601 fn from_opaque_predicate() {
1602 let proto_expr::predicate::Kind::Opaque(opaque) =
1603 pred_kind_of(Predicate::opaque(TestOpaquePredOp, [lit(1), lit(2)]))
1604 else {
1605 panic!("expected an opaque predicate");
1606 };
1607 assert_eq!(opaque.name, "test_opaque_pred");
1608 assert_eq!(opaque.exprs.len(), 2);
1609 }
1610
1611 #[test]
1612 fn from_struct_patch() {
1613 let patch = ExpressionStructPatchBuilder::new()
1614 .replace("a", lit(1))
1615 .insert_after("b", lit(2))
1616 .drop_if_exists("c")
1617 .prepend(lit(0))
1618 .append(lit(3))
1619 .build()
1620 .unwrap();
1621
1622 let transform = proto_expr::Transform::from(&patch);
1623
1624 assert!(transform.field_transforms["a"].is_replace);
1626 assert!(!transform.field_transforms["a"].optional);
1627 assert!(!transform.field_transforms["b"].is_replace);
1629 assert!(transform.field_transforms["c"].is_replace);
1631 assert!(transform.field_transforms["c"].optional);
1632 assert!(transform.input_path.is_none());
1634 assert_eq!(transform.prepended_fields.len(), 1);
1635 assert_eq!(transform.appended_fields.len(), 1);
1636 }
1637
1638 #[rstest]
1641 #[case(UnaryExpressionOp::ToJson, proto_expr::UnaryExpressionOp::ToJson)]
1642 fn from_unary_expression_op(
1643 #[case] op: UnaryExpressionOp,
1644 #[case] expected: proto_expr::UnaryExpressionOp,
1645 ) {
1646 assert_eq!(
1647 proto_expr::UnaryExpressionOp::from(op) as i32,
1648 expected as i32
1649 );
1650 }
1651
1652 #[rstest]
1653 #[case(BinaryExpressionOp::Plus, proto_expr::BinaryExpressionOp::Plus)]
1654 #[case(BinaryExpressionOp::Minus, proto_expr::BinaryExpressionOp::Minus)]
1655 #[case(BinaryExpressionOp::Multiply, proto_expr::BinaryExpressionOp::Multiply)]
1656 #[case(BinaryExpressionOp::Divide, proto_expr::BinaryExpressionOp::Divide)]
1657 fn from_binary_expression_op(
1658 #[case] op: BinaryExpressionOp,
1659 #[case] expected: proto_expr::BinaryExpressionOp,
1660 ) {
1661 assert_eq!(
1662 proto_expr::BinaryExpressionOp::from(op) as i32,
1663 expected as i32
1664 );
1665 }
1666
1667 #[rstest]
1668 #[case(
1669 VariadicExpressionOp::Coalesce,
1670 proto_expr::VariadicExpressionOp::Coalesce
1671 )]
1672 #[case(VariadicExpressionOp::Array, proto_expr::VariadicExpressionOp::Array)]
1673 fn from_variadic_expression_op(
1674 #[case] op: VariadicExpressionOp,
1675 #[case] expected: proto_expr::VariadicExpressionOp,
1676 ) {
1677 assert_eq!(
1678 proto_expr::VariadicExpressionOp::from(op) as i32,
1679 expected as i32
1680 );
1681 }
1682
1683 #[rstest]
1684 #[case(UnaryPredicateOp::IsNull, proto_expr::UnaryPredicateOp::IsNull)]
1685 fn from_unary_predicate_op(
1686 #[case] op: UnaryPredicateOp,
1687 #[case] expected: proto_expr::UnaryPredicateOp,
1688 ) {
1689 assert_eq!(
1690 proto_expr::UnaryPredicateOp::from(op) as i32,
1691 expected as i32
1692 );
1693 }
1694
1695 #[rstest]
1696 #[case(BinaryPredicateOp::LessThan, proto_expr::BinaryPredicateOp::LessThan)]
1697 #[case(
1698 BinaryPredicateOp::GreaterThan,
1699 proto_expr::BinaryPredicateOp::GreaterThan
1700 )]
1701 #[case(BinaryPredicateOp::Equal, proto_expr::BinaryPredicateOp::Equal)]
1702 #[case(BinaryPredicateOp::Distinct, proto_expr::BinaryPredicateOp::Distinct)]
1703 #[case(BinaryPredicateOp::In, proto_expr::BinaryPredicateOp::In)]
1704 fn from_binary_predicate_op(
1705 #[case] op: BinaryPredicateOp,
1706 #[case] expected: proto_expr::BinaryPredicateOp,
1707 ) {
1708 assert_eq!(
1709 proto_expr::BinaryPredicateOp::from(op) as i32,
1710 expected as i32
1711 );
1712 }
1713
1714 #[rstest]
1715 #[case(JunctionPredicateOp::And, proto_expr::JunctionPredicateOp::And)]
1716 #[case(JunctionPredicateOp::Or, proto_expr::JunctionPredicateOp::Or)]
1717 fn from_junction_predicate_op(
1718 #[case] op: JunctionPredicateOp,
1719 #[case] expected: proto_expr::JunctionPredicateOp,
1720 ) {
1721 assert_eq!(
1722 proto_expr::JunctionPredicateOp::from(op) as i32,
1723 expected as i32
1724 );
1725 }
1726
1727 #[rstest]
1730 #[case(Scalar::Integer(7), "integer")]
1731 #[case(Scalar::Long(7), "long")]
1732 #[case(Scalar::Short(7), "short")]
1733 #[case(Scalar::Byte(7), "byte")]
1734 #[case(Scalar::Float(1.5), "float")]
1735 #[case(Scalar::Double(1.5), "double")]
1736 #[case(Scalar::String("hi".into()), "string")]
1737 #[case(Scalar::Boolean(true), "boolean")]
1738 #[case(Scalar::Timestamp(9), "timestamp")]
1739 #[case(Scalar::TimestampNtz(9), "timestamp_ntz")]
1740 #[case(Scalar::Date(9), "date")]
1741 #[case(Scalar::Binary(vec![1, 2, 3]), "binary")]
1742 #[case(
1743 Scalar::Decimal(
1744 DecimalData::try_new(1234i128, DecimalType::try_new(10, 2).unwrap()).unwrap(),
1745 ),
1746 "decimal"
1747 )]
1748 #[case(Scalar::Null(DataType::LONG), "null")]
1749 #[case(
1750 Scalar::Struct(
1751 StructData::try_new(
1752 vec![StructField::nullable("a", DataType::INTEGER)],
1753 vec![Scalar::Integer(1)],
1754 )
1755 .unwrap(),
1756 ),
1757 "struct"
1758 )]
1759 #[case(
1760 Scalar::Array(
1761 ArrayData::try_new(ArrayType::new(DataType::INTEGER, false), [1, 2, 3]).unwrap(),
1762 ),
1763 "array"
1764 )]
1765 #[case(
1766 Scalar::Map(
1767 MapData::try_new(MapType::new(DataType::STRING, DataType::INTEGER, false), [("k", 1)])
1768 .unwrap(),
1769 ),
1770 "map"
1771 )]
1772 fn from_scalar(#[case] value: Scalar, #[case] expected: &str) {
1773 use proto_expr::scalar::Value;
1774 let kind = match proto_expr::Scalar::from(&value).value.unwrap() {
1775 Value::Integer(_) => "integer",
1776 Value::Long(_) => "long",
1777 Value::Short(_) => "short",
1778 Value::Byte(_) => "byte",
1779 Value::Float(_) => "float",
1780 Value::Double(_) => "double",
1781 Value::String(_) => "string",
1782 Value::Boolean(_) => "boolean",
1783 Value::Timestamp(_) => "timestamp",
1784 Value::TimestampNtz(_) => "timestamp_ntz",
1785 Value::TimestampNanos(_) => "timestamp_nanos",
1786 Value::TimestampNanosNtz(_) => "timestamp_nanos_ntz",
1787 Value::Date(_) => "date",
1788 Value::Binary(_) => "binary",
1789 Value::Decimal(_) => "decimal",
1790 Value::Null(_) => "null",
1791 Value::Struct(_) => "struct",
1792 Value::Array(_) => "array",
1793 Value::Map(_) => "map",
1794 };
1795 assert_eq!(kind, expected);
1796 }
1797
1798 #[test]
1799 fn from_decimal_data() {
1800 let decimal = DecimalData::try_new(1234i128, DecimalType::try_new(10, 2).unwrap()).unwrap();
1801 let proto_expr::scalar::Value::Decimal(decimal) = scalar_value_of(Scalar::Decimal(decimal))
1802 else {
1803 panic!("expected a decimal scalar");
1804 };
1805 assert_eq!(decimal.bits, 1234i128.to_be_bytes().to_vec());
1806 let decimal_type = decimal.decimal_type.expect("decimal type present");
1807 assert_eq!((decimal_type.precision, decimal_type.scale), (10, 2));
1808 }
1809
1810 #[test]
1811 fn from_struct_data() {
1812 let struct_data = StructData::try_new(
1813 vec![StructField::nullable("a", DataType::INTEGER)],
1814 vec![Scalar::Integer(1)],
1815 )
1816 .unwrap();
1817 let proto_expr::scalar::Value::Struct(struct_data) =
1818 scalar_value_of(Scalar::Struct(struct_data))
1819 else {
1820 panic!("expected a struct scalar");
1821 };
1822 assert_eq!(struct_data.fields.len(), 1);
1823 assert_eq!(struct_data.values.len(), 1);
1824 assert_eq!(struct_data.fields[0].name, "a");
1825 }
1826
1827 #[test]
1828 fn from_array_data() {
1829 let array_data =
1830 ArrayData::try_new(ArrayType::new(DataType::INTEGER, false), [1, 2, 3]).unwrap();
1831 let proto_expr::scalar::Value::Array(array_data) =
1832 scalar_value_of(Scalar::Array(array_data))
1833 else {
1834 panic!("expected an array scalar");
1835 };
1836 assert!(array_data.array_type.is_some());
1837 assert_eq!(array_data.elements.len(), 3);
1838 }
1839
1840 #[test]
1841 fn from_map_data() {
1842 let map_data = MapData::try_new(
1843 MapType::new(DataType::STRING, DataType::INTEGER, false),
1844 [("k", 1)],
1845 )
1846 .unwrap();
1847 let proto_expr::scalar::Value::Map(map_data) = scalar_value_of(Scalar::Map(map_data))
1848 else {
1849 panic!("expected a map scalar");
1850 };
1851 assert!(map_data.map_type.is_some());
1852 assert_eq!(map_data.pairs.len(), 1);
1853 assert!(map_data.pairs[0].key.is_some());
1854 assert!(map_data.pairs[0].value.is_some());
1855 }
1856
1857 #[rstest]
1860 #[case(DataType::INTEGER, "primitive")]
1861 #[case(ArrayType::new(DataType::INTEGER, true).into(), "array")]
1862 #[case(
1863 StructType::try_new(vec![StructField::nullable("a", DataType::INTEGER)])
1864 .unwrap()
1865 .into(),
1866 "struct"
1867 )]
1868 #[case(MapType::new(DataType::STRING, DataType::INTEGER, true).into(), "map")]
1869 #[case(DataType::unshredded_variant(), "variant")]
1870 fn from_data_type(#[case] value: DataType, #[case] expected: &str) {
1871 use proto_schema::data_type::Kind;
1872 let kind = match proto_schema::DataType::from(&value).kind.unwrap() {
1873 Kind::Primitive(_) => "primitive",
1874 Kind::Array(_) => "array",
1875 Kind::Struct(_) => "struct",
1876 Kind::Map(_) => "map",
1877 Kind::Variant(_) => "variant",
1878 };
1879 assert_eq!(kind, expected);
1880 }
1881
1882 #[rstest]
1883 #[case(PrimitiveType::String, proto_schema::SimplePrimitiveType::String)]
1884 #[case(PrimitiveType::Long, proto_schema::SimplePrimitiveType::Long)]
1885 #[case(PrimitiveType::Integer, proto_schema::SimplePrimitiveType::Integer)]
1886 #[case(PrimitiveType::Short, proto_schema::SimplePrimitiveType::Short)]
1887 #[case(PrimitiveType::Byte, proto_schema::SimplePrimitiveType::Byte)]
1888 #[case(PrimitiveType::Float, proto_schema::SimplePrimitiveType::Float)]
1889 #[case(PrimitiveType::Double, proto_schema::SimplePrimitiveType::Double)]
1890 #[case(PrimitiveType::Boolean, proto_schema::SimplePrimitiveType::Boolean)]
1891 #[case(PrimitiveType::Binary, proto_schema::SimplePrimitiveType::Binary)]
1892 #[case(PrimitiveType::Date, proto_schema::SimplePrimitiveType::Date)]
1893 #[case(PrimitiveType::Timestamp, proto_schema::SimplePrimitiveType::Timestamp)]
1894 #[case(
1895 PrimitiveType::TimestampNtz,
1896 proto_schema::SimplePrimitiveType::TimestampNtz
1897 )]
1898 #[case(PrimitiveType::Void, proto_schema::SimplePrimitiveType::Void)]
1899 #[case(
1900 PrimitiveType::IntervalYearMonth,
1901 proto_schema::SimplePrimitiveType::IntervalYearMonth
1902 )]
1903 #[case(
1904 PrimitiveType::IntervalDayTime,
1905 proto_schema::SimplePrimitiveType::IntervalDayTime
1906 )]
1907 fn from_primitive_type(
1908 #[case] primitive: PrimitiveType,
1909 #[case] expected: proto_schema::SimplePrimitiveType,
1910 ) {
1911 let Some(proto_schema::primitive_type::Kind::Simple(simple)) =
1912 proto_schema::PrimitiveType::from(&primitive).kind
1913 else {
1914 panic!("expected a simple primitive type");
1915 };
1916 assert_eq!(simple, expected as i32);
1917 }
1918
1919 #[test]
1920 fn from_decimal_type() {
1921 let primitive = PrimitiveType::decimal(10, 2).unwrap();
1922 let Some(proto_schema::primitive_type::Kind::Decimal(decimal)) =
1923 proto_schema::PrimitiveType::from(&primitive).kind
1924 else {
1925 panic!("expected a decimal primitive type");
1926 };
1927 assert_eq!((decimal.precision, decimal.scale), (10, 2));
1928 }
1929
1930 #[rstest]
1931 #[case(true)]
1932 #[case(false)]
1933 fn from_array_type(#[case] contains_null: bool) {
1934 let proto =
1935 proto_schema::ArrayType::from(&ArrayType::new(DataType::INTEGER, contains_null));
1936 assert!(proto.element_type.is_some());
1937 assert_eq!(proto.contains_null, contains_null);
1938 }
1939
1940 #[rstest]
1941 #[case(true)]
1942 #[case(false)]
1943 fn from_map_type(#[case] value_contains_null: bool) {
1944 let proto = proto_schema::MapType::from(&MapType::new(
1945 DataType::STRING,
1946 DataType::INTEGER,
1947 value_contains_null,
1948 ));
1949 assert!(proto.key_type.is_some());
1950 assert!(proto.value_type.is_some());
1951 assert_eq!(proto.value_contains_null, value_contains_null);
1952 }
1953
1954 #[test]
1955 fn from_struct_type() {
1956 let struct_type = StructType::try_new(vec![
1957 StructField::nullable("a", DataType::INTEGER),
1958 StructField::not_null("b", DataType::STRING),
1959 ])
1960 .unwrap();
1961 let proto = proto_schema::StructType::from(&struct_type);
1962 assert_eq!(proto.fields.len(), 2);
1963 assert!(proto.fields[0].nullable);
1964 assert!(!proto.fields[1].nullable);
1965 }
1966
1967 #[test]
1968 fn from_struct_field() {
1969 let field = StructField::nullable("a", DataType::INTEGER)
1970 .with_metadata([("k", MetadataValue::Number(7))]);
1971 let proto = proto_schema::StructField::from(&field);
1972 assert_eq!(proto.name, "a");
1973 assert!(proto.nullable);
1974 assert!(proto.data_type.is_some());
1975 assert_eq!(
1976 proto.metadata["k"].value,
1977 Some(proto_schema::metadata_value::Value::Number(7))
1978 );
1979 }
1980
1981 #[rstest]
1982 #[case(
1983 MetadataValue::Number(5),
1984 proto_schema::metadata_value::Value::Number(5)
1985 )]
1986 #[case(
1987 MetadataValue::String("s".to_string()),
1988 proto_schema::metadata_value::Value::String("s".to_string())
1989 )]
1990 #[case(
1991 MetadataValue::Boolean(true),
1992 proto_schema::metadata_value::Value::Boolean(true)
1993 )]
1994 #[case(
1995 MetadataValue::Other(serde_json::json!([1, 2])),
1996 proto_schema::metadata_value::Value::OtherJson("[1,2]".to_string())
1997 )]
1998 fn from_metadata_value(
1999 #[case] metadata: MetadataValue,
2000 #[case] expected: proto_schema::metadata_value::Value,
2001 ) {
2002 assert_eq!(
2003 proto_schema::MetadataValue::from(&metadata).value.unwrap(),
2004 expected
2005 );
2006 }
2007
2008 fn assert_data_type_round_trips(data_type: DataType) {
2012 let decoded = DataType::try_from(proto_schema::DataType::from(&data_type));
2013 assert_eq!(decoded.expect("decode succeeds"), data_type);
2014 }
2015
2016 fn assert_schema_round_trips(schema: StructType) {
2018 let decoded = StructType::try_from(proto_schema::StructType::from(&schema));
2019 assert_eq!(decoded.expect("decode succeeds"), schema);
2020 }
2021
2022 #[rstest]
2023 fn round_trip_primitive(
2024 #[values(
2025 PrimitiveType::String,
2026 PrimitiveType::Long,
2027 PrimitiveType::Integer,
2028 PrimitiveType::Short,
2029 PrimitiveType::Byte,
2030 PrimitiveType::Float,
2031 PrimitiveType::Double,
2032 PrimitiveType::Boolean,
2033 PrimitiveType::Binary,
2034 PrimitiveType::Date,
2035 PrimitiveType::Timestamp,
2036 PrimitiveType::TimestampNtz,
2037 PrimitiveType::Void,
2038 PrimitiveType::IntervalYearMonth,
2039 PrimitiveType::IntervalDayTime
2040 )]
2041 primitive: PrimitiveType,
2042 ) {
2043 assert_data_type_round_trips(DataType::Primitive(primitive));
2044 }
2045
2046 #[rstest]
2047 #[case(1, 0)]
2048 #[case(10, 2)]
2049 #[case(38, 0)]
2050 #[case(38, 38)]
2051 fn round_trip_decimal(#[case] precision: u8, #[case] scale: u8) {
2052 assert_data_type_round_trips(DataType::Primitive(
2053 PrimitiveType::decimal(precision, scale).unwrap(),
2054 ));
2055 }
2056
2057 #[rstest]
2058 #[case(DataType::from(ArrayType::new(DataType::INTEGER, true)))]
2059 #[case(DataType::from(ArrayType::new(DataType::STRING, false)))]
2060 #[case(DataType::from(MapType::new(DataType::STRING, DataType::LONG, true)))]
2061 #[case(DataType::from(MapType::new(DataType::INTEGER, DataType::BOOLEAN, false)))]
2062 #[case(DataType::from(ArrayType::new(
2063 MapType::new(DataType::STRING, DataType::LONG, true),
2064 true
2065 )))]
2066 #[case(DataType::from(StructType::try_new(vec![
2067 StructField::nullable("a", DataType::INTEGER),
2068 StructField::not_null("b", DataType::STRING),
2069 ]).unwrap()))]
2070 fn round_trip_composite(#[case] data_type: DataType) {
2071 assert_data_type_round_trips(data_type);
2072 }
2073
2074 #[rstest]
2075 #[case(MetadataValue::Number(7))]
2076 #[case(MetadataValue::String("hello".to_string()))]
2077 #[case(MetadataValue::Boolean(true))]
2078 #[case(MetadataValue::Other(serde_json::json!({"nested": [1, 2, 3]})))]
2079 fn round_trip_field_metadata(#[case] value: MetadataValue) {
2080 let field = StructField::nullable("f", DataType::INTEGER).with_metadata([("k", value)]);
2081 let decoded = StructField::try_from(proto_schema::StructField::from(&field));
2082 assert_eq!(decoded.expect("decode succeeds"), field);
2083 }
2084
2085 #[test]
2086 fn round_trip_full_schema() {
2087 let schema = StructType::try_new(vec![
2088 StructField::nullable("id", DataType::LONG)
2089 .with_metadata([("k", MetadataValue::Number(7))]),
2090 StructField::not_null("name", DataType::STRING),
2091 StructField::nullable("scores", ArrayType::new(DataType::INTEGER, true)),
2092 StructField::nullable(
2093 "attrs",
2094 MapType::new(DataType::STRING, DataType::LONG, true),
2095 ),
2096 StructField::nullable(
2097 "price",
2098 DataType::Primitive(PrimitiveType::decimal(10, 2).unwrap()),
2099 ),
2100 StructField::nullable(
2101 "nested",
2102 StructType::try_new(vec![StructField::not_null("inner", DataType::BOOLEAN)])
2103 .unwrap(),
2104 ),
2105 ])
2106 .unwrap();
2107 assert_schema_round_trips(schema);
2108 }
2109
2110 #[test]
2113 fn variant_decodes_to_unshredded() {
2114 let shredded = DataType::Variant(Box::new(
2115 StructType::try_new(vec![
2116 StructField::not_null("metadata", DataType::BINARY),
2117 StructField::not_null("value", DataType::BINARY),
2118 StructField::nullable("typed_value", DataType::INTEGER),
2119 ])
2120 .unwrap(),
2121 ));
2122 let decoded = DataType::try_from(proto_schema::DataType::from(&shredded));
2123 assert_eq!(
2124 decoded.expect("decode succeeds"),
2125 DataType::unshredded_variant()
2126 );
2127 }
2128
2129 fn primitive_data_type(
2131 kind: Option<proto_schema::primitive_type::Kind>,
2132 ) -> proto_schema::DataType {
2133 proto_schema::DataType {
2134 kind: Some(proto_schema::data_type::Kind::Primitive(
2135 proto_schema::PrimitiveType { kind },
2136 )),
2137 }
2138 }
2139
2140 fn simple_primitive_data_type(value: i32) -> proto_schema::DataType {
2141 primitive_data_type(Some(proto_schema::primitive_type::Kind::Simple(value)))
2142 }
2143
2144 fn decimal_data_type(precision: u32, scale: u32) -> proto_schema::DataType {
2145 primitive_data_type(Some(proto_schema::primitive_type::Kind::Decimal(
2146 proto_schema::DecimalType { precision, scale },
2147 )))
2148 }
2149
2150 #[rstest]
2151 #[case::missing_data_type_kind(proto_schema::DataType { kind: None })]
2152 #[case::missing_primitive_kind(primitive_data_type(None))]
2153 #[case::unspecified_primitive(simple_primitive_data_type(
2154 proto_schema::SimplePrimitiveType::Unspecified as i32
2155 ))]
2156 #[case::unknown_primitive(simple_primitive_data_type(9999))]
2157 #[case::decimal_zero_precision(decimal_data_type(0, 0))]
2158 #[case::decimal_precision_too_large(decimal_data_type(39, 0))]
2159 #[case::decimal_scale_exceeds_precision(decimal_data_type(5, 6))]
2160 #[case::decimal_precision_out_of_u8_range(decimal_data_type(256, 0))]
2161 #[case::array_missing_element_type(proto_schema::DataType {
2162 kind: Some(proto_schema::data_type::Kind::Array(Box::new(proto_schema::ArrayType {
2163 element_type: None,
2164 contains_null: true,
2165 }))),
2166 })]
2167 #[case::map_missing_key_type(proto_schema::DataType {
2168 kind: Some(proto_schema::data_type::Kind::Map(Box::new(proto_schema::MapType {
2169 key_type: None,
2170 value_type: Some(Box::new(proto_schema::DataType::from(&DataType::STRING))),
2171 value_contains_null: true,
2172 }))),
2173 })]
2174 #[case::map_missing_value_type(proto_schema::DataType {
2175 kind: Some(proto_schema::data_type::Kind::Map(Box::new(proto_schema::MapType {
2176 key_type: Some(Box::new(proto_schema::DataType::from(&DataType::STRING))),
2177 value_type: None,
2178 value_contains_null: true,
2179 }))),
2180 })]
2181 fn data_type_decode_rejects_invalid(#[case] proto: proto_schema::DataType) {
2182 assert!(DataType::try_from(proto).is_err());
2183 }
2184
2185 #[test]
2186 fn struct_field_decode_rejects_missing_data_type() {
2187 let proto = proto_schema::StructField {
2188 name: "f".to_string(),
2189 data_type: None,
2190 nullable: true,
2191 metadata: Default::default(),
2192 };
2193 assert!(StructField::try_from(proto).is_err());
2194 }
2195
2196 #[rstest]
2197 #[case::missing_value(proto_schema::MetadataValue { value: None })]
2198 #[case::invalid_other_json(proto_schema::MetadataValue {
2199 value: Some(proto_schema::metadata_value::Value::OtherJson("not json".to_string())),
2200 })]
2201 fn metadata_value_decode_rejects_invalid(#[case] proto: proto_schema::MetadataValue) {
2202 assert!(MetadataValue::try_from(proto).is_err());
2203 }
2204}