1use std::fmt::{self};
2
3use chrono::{DateTime, NaiveDate};
4use expr::RexType;
5use substrait::proto::expression::field_reference::{ReferenceType, RootReference, RootType};
6use substrait::proto::expression::literal::LiteralType;
7use substrait::proto::expression::{
8 Cast, FieldReference, IfThen, ReferenceSegment, ScalarFunction, cast, reference_segment,
9};
10use substrait::proto::function_argument::ArgType;
11use substrait::proto::{
12 AggregateFunction, Expression, FunctionArgument, FunctionOption, expression as expr,
13};
14
15use super::{PlanError, Scope, Textify, Visibility};
16use crate::extensions::simple::ExtensionKind;
17use crate::textify::types::{Name, NamedAnchor, OutputType, escaped};
18
19pub fn textify_binary<S: Scope, W: fmt::Write>(items: &[u8], ctx: &S, w: &mut W) -> fmt::Result {
32 if ctx.options().show_literal_binaries {
33 write!(w, "0x")?;
34 for &n in items {
35 write!(w, "{n:02x}")?;
36 }
37 } else {
38 write!(w, "{{binary}}")?;
39 }
40 Ok(())
41}
42
43fn unimplemented_literal<S: Scope, W: fmt::Write>(
45 variant: &'static str,
46 ctx: &S,
47 w: &mut W,
48) -> fmt::Result {
49 write!(
50 w,
51 "{}",
52 ctx.failure(PlanError::unimplemented(
53 "LiteralType",
54 Some(variant),
55 format!("{variant} literal textification not implemented"),
56 ))
57 )
58}
59
60pub fn textify_enum<S: Scope, W: fmt::Write>(s: &str, _ctx: &S, w: &mut W) -> fmt::Result {
63 write!(w, "&{}", Name(s))
64}
65
66pub fn timestamp_to_string(t: i64) -> String {
67 let ts = chrono::DateTime::from_timestamp_nanos(t);
68 ts.to_rfc3339()
69}
70
71fn days_to_date_string(days: i32) -> String {
73 let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
74 let date = epoch + chrono::Duration::days(days as i64);
75 date.format("%Y-%m-%d").to_string()
76}
77
78fn microseconds_to_time_string(microseconds: i64) -> String {
80 let total_seconds = microseconds / 1_000_000;
81 let remaining_microseconds = microseconds % 1_000_000;
82
83 let hours = total_seconds / 3600;
84 let minutes = (total_seconds % 3600) / 60;
85 let seconds = total_seconds % 60;
86
87 if remaining_microseconds == 0 {
88 format!("{hours:02}:{minutes:02}:{seconds:02}")
89 } else {
90 let fractional = remaining_microseconds as f64 / 1_000_000.0;
92 format!("{hours:02}:{minutes:02}:{seconds:02}{fractional:.6}")
93 .trim_end_matches('0')
94 .trim_end_matches('.')
95 .to_string()
96 }
97}
98
99fn microseconds_to_timestamp_string(microseconds: i64) -> String {
101 let epoch = DateTime::from_timestamp(0, 0).unwrap().naive_utc();
102 let duration = chrono::Duration::microseconds(microseconds);
103 let datetime = epoch + duration;
104
105 let formatted = datetime.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
107
108 if formatted.contains('.') {
110 formatted
111 .trim_end_matches('0')
112 .trim_end_matches('.')
113 .to_string()
114 } else {
115 formatted
116 }
117}
118
119fn write_literal_value<S: Scope, W: fmt::Write>(
124 lit: &LiteralType,
125 ctx: &S,
126 w: &mut W,
127) -> fmt::Result {
128 match lit {
129 LiteralType::Boolean(b) => write!(w, "{b}"),
130 LiteralType::I8(i) | LiteralType::I16(i) | LiteralType::I32(i) => write!(w, "{i}"),
131 LiteralType::I64(i) => write!(w, "{i}"),
132 LiteralType::Fp32(f) => write!(w, "{f}"),
133 LiteralType::Fp64(f) => write!(w, "{f}"),
134 LiteralType::String(s) => write!(w, "'{}'", s.escape_debug()),
135 LiteralType::Binary(items) => textify_binary(items, ctx, w),
136 LiteralType::Date(days) => {
137 write!(w, "'{}'", escaped(&days_to_date_string(*days)))
138 }
139 LiteralType::Time(microseconds) => {
140 write!(
141 w,
142 "'{}'",
143 escaped(µseconds_to_time_string(*microseconds))
144 )
145 }
146 #[allow(deprecated)]
147 LiteralType::Timestamp(microseconds) => {
148 write!(
149 w,
150 "'{}'",
151 escaped(µseconds_to_timestamp_string(*microseconds))
152 )
153 }
154 LiteralType::IntervalYearToMonth(_) => unimplemented_literal("IntervalYearToMonth", ctx, w),
155 LiteralType::IntervalDayToSecond(_) => unimplemented_literal("IntervalDayToSecond", ctx, w),
156 LiteralType::IntervalCompound(_) => unimplemented_literal("IntervalCompound", ctx, w),
157 LiteralType::FixedChar(_) => unimplemented_literal("FixedChar", ctx, w),
158 LiteralType::VarChar(_) => unimplemented_literal("VarChar", ctx, w),
159 LiteralType::FixedBinary(_) => unimplemented_literal("FixedBinary", ctx, w),
160 LiteralType::Decimal(_) => unimplemented_literal("Decimal", ctx, w),
161 LiteralType::PrecisionTime(_) => unimplemented_literal("PrecisionTime", ctx, w),
162 LiteralType::PrecisionTimestamp(_) => unimplemented_literal("PrecisionTimestamp", ctx, w),
163 LiteralType::PrecisionTimestampTz(_) => {
164 unimplemented_literal("PrecisionTimestampTz", ctx, w)
165 }
166 LiteralType::Struct(_) => unimplemented_literal("Struct", ctx, w),
167 LiteralType::Map(_) => unimplemented_literal("Map", ctx, w),
168 #[allow(deprecated)]
169 LiteralType::TimestampTz(_) => unimplemented_literal("TimestampTz", ctx, w),
170 LiteralType::Uuid(_) => unimplemented_literal("Uuid", ctx, w),
171 LiteralType::Null(_) => unimplemented_literal("Null", ctx, w),
172 LiteralType::List(_) => unimplemented_literal("List", ctx, w),
173 LiteralType::EmptyList(_) => unimplemented_literal("EmptyList", ctx, w),
174 LiteralType::EmptyMap(_) => unimplemented_literal("EmptyMap", ctx, w),
175 LiteralType::UserDefined(_) => unimplemented_literal("UserDefined", ctx, w),
176 }
177}
178
179fn literal_type_suffix(lit: &LiteralType) -> Option<&'static str> {
184 match lit {
185 LiteralType::Boolean(_) => Some("boolean"),
186 LiteralType::I8(_) => Some("i8"),
187 LiteralType::I16(_) => Some("i16"),
188 LiteralType::I32(_) => Some("i32"),
189 LiteralType::I64(_) => Some("i64"),
190 LiteralType::Fp32(_) => Some("fp32"),
191 LiteralType::Fp64(_) => Some("fp64"),
192 LiteralType::String(_) => Some("string"),
193 LiteralType::Binary(_) => Some("binary"),
194 LiteralType::Date(_) => Some("date"),
195 LiteralType::Time(_) => Some("time"),
196 #[allow(deprecated)]
197 LiteralType::Timestamp(_) => Some("timestamp"),
198 _ => None,
199 }
200}
201
202fn is_default_for_syntax(lit: &LiteralType) -> bool {
215 matches!(
216 lit,
217 LiteralType::Boolean(_)
218 | LiteralType::String(_)
219 | LiteralType::Binary(_)
220 | LiteralType::I64(_)
221 | LiteralType::Fp64(_)
222 )
223}
224
225impl Textify for expr::Literal {
226 fn name() -> &'static str {
227 "Literal"
228 }
229
230 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
231 let Some(lit) = self.literal_type.as_ref() else {
232 return write!(
233 w,
234 "{}",
235 ctx.failure(PlanError::invalid(
236 "Literal",
237 Some("literal_type"),
238 "missing literal_type",
239 ))
240 );
241 };
242 write_literal_value(lit, ctx, w)?;
243 let show_suffix = match ctx.options().literal_types {
244 Visibility::Never => false,
245 Visibility::Always => true,
246 Visibility::Required => self.nullable || !is_default_for_syntax(lit),
247 };
248 if show_suffix {
249 if let Some(suffix) = literal_type_suffix(lit) {
250 write!(w, ":{suffix}")?;
251 }
252 if self.nullable {
253 write!(w, "?")?;
254 }
255 }
256 Ok(())
257 }
258}
259
260pub struct Reference(pub i32);
261
262impl fmt::Display for Reference {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 write!(f, "${}", self.0)
265 }
266}
267
268impl From<Reference> for Expression {
269 fn from(r: Reference) -> Self {
270 Expression {
273 rex_type: Some(RexType::Selection(Box::new(FieldReference {
274 reference_type: Some(ReferenceType::DirectReference(ReferenceSegment {
275 reference_type: Some(reference_segment::ReferenceType::StructField(Box::new(
276 reference_segment::StructField {
277 field: r.0,
278 child: None,
279 },
280 ))),
281 })),
282 root_type: Some(RootType::RootReference(RootReference {})),
283 }))),
284 }
285 }
286}
287
288impl Textify for Reference {
289 fn name() -> &'static str {
290 "Reference"
291 }
292
293 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
294 write!(w, "{self}")
295 }
296}
297
298impl Textify for FieldReference {
299 fn name() -> &'static str {
300 "FieldReference"
301 }
302
303 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
304 match &self.root_type {
305 Some(RootType::RootReference(_)) => {}
306 None => {
307 return write!(
308 w,
309 "{}",
310 ctx.failure(PlanError::invalid(
311 "FieldReference",
312 Some("root_type"),
313 "Required field root_type is missing",
314 ))
315 );
316 }
317 Some(RootType::Expression(_)) => {
318 return write!(
319 w,
320 "{}",
321 ctx.failure(PlanError::unimplemented(
322 "FieldReference",
323 Some("root_type"),
324 "FieldReference textification not implemented for Expression root_type",
325 ))
326 );
327 }
328 Some(RootType::OuterReference(_)) => {
329 return write!(
330 w,
331 "{}",
332 ctx.failure(PlanError::unimplemented(
333 "FieldReference",
334 Some("root_type"),
335 "FieldReference textification not implemented for OuterReference root_type",
336 ))
337 );
338 }
339 }
340
341 let ref_type = match &self.reference_type {
342 None => {
343 return write!(
344 w,
345 "{}",
346 ctx.failure(PlanError::invalid(
347 "FieldReference",
348 Some("reference_type"),
349 "Required field reference_type is missing",
350 ))
351 );
352 }
353 Some(ReferenceType::DirectReference(r)) => r,
354 _ => {
355 return write!(
356 w,
357 "{}",
358 ctx.failure(PlanError::unimplemented(
359 "FieldReference",
360 Some("FieldReference"),
361 "FieldReference textification implemented only for StructField",
362 ))
363 );
364 }
365 };
366
367 match &ref_type.reference_type {
368 Some(reference_segment::ReferenceType::StructField(s)) => {
369 write!(w, "{}", Reference(s.field))
370 }
371 None => write!(
372 w,
373 "{}",
374 ctx.failure(PlanError::invalid(
375 "ReferenceSegment",
376 Some("reference_type"),
377 "Required field reference_type is missing",
378 ))
379 ),
380 _ => write!(
381 w,
382 "{}",
383 ctx.failure(PlanError::unimplemented(
384 "ReferenceSegment",
385 Some("reference_type"),
386 "ReferenceSegment textification implemented only for StructField",
387 ))
388 ),
389 }
390 }
391}
392
393impl Textify for ScalarFunction {
394 fn name() -> &'static str {
395 "ScalarFunction"
396 }
397
398 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
399 let name_and_anchor =
400 NamedAnchor::lookup(ctx, ExtensionKind::Function, self.function_reference);
401 let name_and_anchor = ctx.display(&name_and_anchor);
402
403 let args = ctx.separated(&self.arguments, ", ");
404 let options = ctx.separated(&self.options, ", ");
405 let between = if self.arguments.is_empty() || self.options.is_empty() {
406 ""
407 } else {
408 ", "
409 };
410
411 let output = OutputType(self.output_type.as_ref());
412 let output_type = ctx.optional(&output, ctx.options().fn_types);
413
414 write!(
415 w,
416 "{name_and_anchor}({args}{between}{options}){output_type}"
417 )?;
418 Ok(())
419 }
420}
421
422impl Textify for FunctionOption {
423 fn name() -> &'static str {
424 "FunctionOption"
425 }
426
427 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
428 write!(w, "{}⇒[", self.name)?;
429 let mut first = true;
430 for pref in self.preference.iter() {
431 if !first {
432 write!(w, ", ")?;
433 } else {
434 first = false;
435 }
436 write!(w, "{pref}")?;
437 }
438 write!(w, "]")?;
439 Ok(())
440 }
441}
442
443impl Textify for FunctionArgument {
444 fn name() -> &'static str {
445 "FunctionArgument"
446 }
447
448 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
449 write!(w, "{}", ctx.expect(self.arg_type.as_ref()))
450 }
451}
452
453impl Textify for ArgType {
454 fn name() -> &'static str {
455 "ArgType"
456 }
457
458 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
459 match self {
460 ArgType::Type(t) => t.textify(ctx, w),
461 ArgType::Value(v) => v.textify(ctx, w),
462 ArgType::Enum(e) => textify_enum(e, ctx, w),
463 }
464 }
465}
466
467impl Textify for Cast {
468 fn name() -> &'static str {
469 "Cast"
470 }
471
472 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
473 let failure_err;
474 let fb: &dyn fmt::Display = match cast::FailureBehavior::try_from(self.failure_behavior) {
475 Ok(cast::FailureBehavior::Unspecified) => &"",
476 Ok(cast::FailureBehavior::ReturnNull) => &"?",
477 Ok(cast::FailureBehavior::ThrowException) => &"!",
478 Err(_) => {
479 failure_err = ctx.failure(PlanError::invalid(
480 "Cast",
481 Some("failure_behavior"),
482 format!("Unknown failure_behavior value: {}", self.failure_behavior),
483 ));
484 &failure_err
485 }
486 };
487 let input = ctx.expect(self.input.as_deref());
488 let target_type = ctx.expect(self.r#type.as_ref());
489 write!(w, "({input})::{fb}{target_type}")
490 }
491}
492
493impl Textify for IfThen {
494 fn name() -> &'static str {
495 "IfThen"
496 }
497
498 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
502 write!(w, "if_then(")?;
503 for clause in &self.ifs {
504 let if_expr = ctx.expect(clause.r#if.as_ref());
505 let then_expr = ctx.expect(clause.then.as_ref());
506 write!(w, "{if_expr} -> {then_expr}, ")?;
507 }
508 let else_expr = ctx.expect(self.r#else.as_deref());
509 write!(w, "_ -> {else_expr})")
510 }
511}
512
513impl Textify for RexType {
514 fn name() -> &'static str {
515 "RexType"
516 }
517
518 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
519 match self {
520 RexType::Literal(literal) => literal.textify(ctx, w),
521 RexType::Selection(f) => f.textify(ctx, w),
522 RexType::ScalarFunction(s) => s.textify(ctx, w),
523 RexType::WindowFunction(_w) => write!(
524 w,
525 "{}",
526 ctx.failure(PlanError::unimplemented(
527 "RexType",
528 Some("WindowFunction"),
529 "WindowFunction textification not implemented",
530 ))
531 ),
532 RexType::IfThen(i) => i.textify(ctx, w),
533 RexType::SwitchExpression(_s) => write!(
534 w,
535 "{}",
536 ctx.failure(PlanError::unimplemented(
537 "RexType",
538 Some("SwitchExpression"),
539 "SwitchExpression textification not implemented",
540 ))
541 ),
542 RexType::SingularOrList(_s) => write!(
543 w,
544 "{}",
545 ctx.failure(PlanError::unimplemented(
546 "RexType",
547 Some("SingularOrList"),
548 "SingularOrList textification not implemented",
549 ))
550 ),
551 RexType::MultiOrList(_m) => write!(
552 w,
553 "{}",
554 ctx.failure(PlanError::unimplemented(
555 "RexType",
556 Some("MultiOrList"),
557 "MultiOrList textification not implemented",
558 ))
559 ),
560 RexType::Cast(c) => c.textify(ctx, w),
561 RexType::Subquery(_s) => write!(
562 w,
563 "{}",
564 ctx.failure(PlanError::unimplemented(
565 "RexType",
566 Some("Subquery"),
567 "Subquery textification not implemented",
568 ))
569 ),
570 RexType::Nested(_n) => write!(
571 w,
572 "{}",
573 ctx.failure(PlanError::unimplemented(
574 "RexType",
575 Some("Nested"),
576 "Nested textification not implemented",
577 ))
578 ),
579 RexType::DynamicParameter(_d) => write!(
580 w,
581 "{}",
582 ctx.failure(PlanError::unimplemented(
583 "RexType",
584 Some("DynamicParameter"),
585 "DynamicParameter textification not implemented",
586 ))
587 ),
588 #[allow(deprecated)]
589 RexType::Enum(_) => write!(
590 w,
591 "{}",
592 ctx.failure(PlanError::unimplemented(
593 "RexType",
594 Some("Enum"),
595 "Enum textification not implemented",
596 ))
597 ),
598 }
599 }
600}
601
602impl Textify for Expression {
603 fn name() -> &'static str {
604 "Expression"
605 }
606
607 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
608 write!(w, "{}", ctx.expect(self.rex_type.as_ref()))
609 }
610}
611
612impl Textify for AggregateFunction {
613 fn name() -> &'static str {
614 "AggregateFunction"
615 }
616
617 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
618 let name_and_anchor =
620 NamedAnchor::lookup(ctx, ExtensionKind::Function, self.function_reference);
621 let name_and_anchor = ctx.display(&name_and_anchor);
622
623 let args = ctx.separated(&self.arguments, ", ");
624 let options = ctx.separated(&self.options, ", ");
625 let between = if self.arguments.is_empty() || self.options.is_empty() {
626 ""
627 } else {
628 ", "
629 };
630
631 let output = OutputType(self.output_type.as_ref());
632 let output_type = ctx.optional(&output, ctx.options().fn_types);
633
634 write!(
635 w,
636 "{name_and_anchor}({args}{between}{options}){output_type}"
637 )
638 }
639}
640
641#[cfg(test)]
642mod tests {
643 use substrait::proto::Type;
644 use substrait::proto::expression::{cast, if_then};
645 use substrait::proto::r#type::{I16, I32, Kind, Nullability};
646
647 use super::*;
648 use crate::extensions::simple::{ExtensionKind, MissingReference};
649 use crate::fixtures::TestContext;
650 use crate::textify::foundation::{FormatError, FormatErrorType};
651
652 fn literal_bool(value: bool) -> Expression {
653 Expression {
654 rex_type: Some(RexType::Literal(expr::Literal {
655 nullable: false,
656 type_variation_reference: 0,
657 literal_type: Some(expr::literal::LiteralType::Boolean(value)),
658 })),
659 }
660 }
661
662 fn non_nullable_literal(lit: expr::literal::LiteralType) -> expr::Literal {
663 expr::Literal {
664 nullable: false,
665 type_variation_reference: 0,
666 literal_type: Some(lit),
667 }
668 }
669
670 #[test]
671 fn test_literal_textify() {
672 let ctx = TestContext::new();
673
674 let literal = non_nullable_literal(LiteralType::Boolean(true));
675 assert_eq!(ctx.textify_no_errors(&literal), "true");
676 }
677
678 fn nullable_literal(lit: expr::literal::LiteralType) -> expr::Literal {
679 expr::Literal {
680 nullable: true,
681 type_variation_reference: 0,
682 literal_type: Some(lit),
683 }
684 }
685
686 #[test]
687 fn test_nullable_boolean_literal_textify() {
688 let ctx = TestContext::new();
689 assert_eq!(
690 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Boolean(true))),
691 "true:boolean?"
692 );
693 assert_eq!(
694 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Boolean(
695 false
696 ))),
697 "false:boolean?"
698 );
699 }
700
701 #[test]
702 fn test_nullable_integer_literal_textify() {
703 let ctx = TestContext::new();
704 assert_eq!(
705 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::I32(78))),
706 "78:i32?"
707 );
708 assert_eq!(
709 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::I64(42))),
710 "42:i64?"
711 );
712 }
713
714 #[test]
715 fn test_nullable_float_literal_textify() {
716 let ctx = TestContext::new();
717 assert_eq!(
718 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Fp32(2.5))),
719 "2.5:fp32?"
720 );
721 assert_eq!(
722 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Fp64(3.19))),
723 "3.19:fp64?"
724 );
725 }
726
727 #[test]
728 fn test_expression_textify() {
729 let ctx = TestContext::new();
730
731 let expr_empty = Expression { rex_type: None }; let (s, errs) = ctx.textify(&expr_empty);
734 assert!(!errs.is_empty());
735 assert_eq!(s, "!{RexType}");
736
737 let expr_lit = Expression {
739 rex_type: Some(RexType::Literal(expr::Literal {
740 nullable: false,
741 type_variation_reference: 0,
742 literal_type: Some(expr::literal::LiteralType::Boolean(true)),
743 })),
744 };
745 assert_eq!(ctx.textify_no_errors(&expr_lit), "true");
746 }
747
748 #[test]
749 fn test_rextype_textify() {
750 let ctx = TestContext::new();
751
752 let func = RexType::ScalarFunction(ScalarFunction {
753 function_reference: 1000, arguments: vec![],
755 options: vec![],
756 output_type: None,
757 #[allow(deprecated)]
758 args: vec![],
759 });
760 let (s, errq) = ctx.textify(&func);
765 let errs: Vec<_> = errq.0;
766 match errs[0] {
767 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => {
768 assert_eq!(k, ExtensionKind::Function);
769 assert_eq!(a, 1000);
770 }
771 _ => panic!("Expected Lookup MissingAnchor: {}", errs[0]),
772 }
773 assert_eq!(s, "!{function}#1000()");
774
775 let ctx = ctx.with_urn(1, "first").with_function(1, 100, "first");
776 let func = RexType::ScalarFunction(ScalarFunction {
777 function_reference: 100,
778 arguments: vec![],
779 options: vec![],
780 output_type: None,
781 #[allow(deprecated)]
782 args: vec![],
783 });
784 let s = ctx.textify_no_errors(&func);
785 assert_eq!(s, "first()");
786
787 let options_show_anchor = Default::default();
789
790 let ctx = TestContext::new()
791 .with_options(options_show_anchor)
792 .with_urn(1, "somewhere_on_the_internet")
793 .with_urn(2, "somewhere_else")
794 .with_function(1, 231, "duplicated")
795 .with_function(2, 232, "duplicated");
796
797 let rex_dup = RexType::ScalarFunction(ScalarFunction {
798 function_reference: 231,
799 arguments: vec![FunctionArgument {
800 arg_type: Some(ArgType::Value(Expression {
801 rex_type: Some(RexType::Literal(expr::Literal {
802 nullable: false,
803 type_variation_reference: 0,
804 literal_type: Some(expr::literal::LiteralType::Boolean(true)),
805 })),
806 })),
807 }],
808 options: vec![],
809 output_type: None,
810 #[allow(deprecated)]
811 args: vec![],
812 });
813 let s = ctx.textify_no_errors(&rex_dup);
814 assert_eq!(s, "duplicated#231(true)");
815 }
816
817 #[test]
818 fn test_ifthen_textify() {
819 let ctx = TestContext::new();
820
821 let if_then = IfThen {
822 ifs: vec![
823 if_then::IfClause {
824 r#if: Some(literal_bool(true)),
825 then: Some(literal_bool(false)),
826 },
827 if_then::IfClause {
828 r#if: Some(literal_bool(false)),
829 then: Some(literal_bool(true)),
830 },
831 ],
832 r#else: Some(Box::new(literal_bool(true))),
833 };
834
835 let s = ctx.textify_no_errors(&if_then);
836 assert_eq!(s, "if_then(true -> false, false -> true, _ -> true)");
837 }
838
839 #[test]
840 fn test_ifthen_textify_missing_else() {
841 let ctx = TestContext::new();
842
843 let if_then = IfThen {
844 ifs: vec![if_then::IfClause {
845 r#if: Some(literal_bool(true)),
846 then: Some(literal_bool(false)),
847 }],
848 r#else: None,
849 };
850
851 let (s, errs) = ctx.textify(&if_then);
852 assert_eq!(s, "if_then(true -> false, _ -> !{Expression})");
853 assert_eq!(errs.0.len(), 1);
854 }
855
856 fn make_i32_type() -> Type {
857 Type {
858 kind: Some(Kind::I32(I32 {
859 nullability: Nullability::Required as i32,
860 type_variation_reference: 0,
861 })),
862 }
863 }
864
865 fn make_i16_type() -> Type {
866 Type {
867 kind: Some(Kind::I16(I16 {
868 nullability: Nullability::Required as i32,
869 type_variation_reference: 0,
870 })),
871 }
872 }
873
874 fn literal_i32(value: i32) -> Expression {
875 Expression {
876 rex_type: Some(RexType::Literal(expr::Literal {
877 nullable: false,
878 type_variation_reference: 0,
879 literal_type: Some(expr::literal::LiteralType::I32(value)),
880 })),
881 }
882 }
883
884 #[test]
885 fn test_cast_textify() {
886 let ctx = TestContext::new();
887 let cast = Cast {
888 r#type: Some(make_i16_type()),
889 input: Some(Box::new(literal_i32(78))),
890 failure_behavior: 0,
891 };
892 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::i16");
893 }
894
895 #[test]
896 fn test_cast_textify_via_rextype() {
897 let ctx = TestContext::new();
898 let rex = RexType::Cast(Box::new(Cast {
899 r#type: Some(make_i16_type()),
900 input: Some(Box::new(literal_i32(78))),
901 failure_behavior: 0,
902 }));
903 assert_eq!(ctx.textify_no_errors(&rex), "(78:i32)::i16");
904 }
905
906 #[test]
907 fn test_cast_textify_nested() {
908 let ctx = TestContext::new();
910 let inner_cast = Expression {
911 rex_type: Some(RexType::Cast(Box::new(Cast {
912 r#type: Some(make_i16_type()),
913 input: Some(Box::new(literal_i32(78))),
914 failure_behavior: 0,
915 }))),
916 };
917 let outer_cast = Cast {
918 r#type: Some(make_i32_type()),
919 input: Some(Box::new(inner_cast)),
920 failure_behavior: 0,
921 };
922 assert_eq!(ctx.textify_no_errors(&outer_cast), "((78:i32)::i16)::i32");
923 }
924
925 #[test]
926 fn test_cast_textify_return_null() {
927 let ctx = TestContext::new();
928 let cast = Cast {
929 r#type: Some(make_i16_type()),
930 input: Some(Box::new(literal_i32(78))),
931 failure_behavior: cast::FailureBehavior::ReturnNull as i32,
932 };
933 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::?i16");
934 }
935
936 #[test]
937 fn test_cast_textify_throw_exception() {
938 let ctx = TestContext::new();
939 let cast = Cast {
940 r#type: Some(make_i16_type()),
941 input: Some(Box::new(literal_i32(78))),
942 failure_behavior: cast::FailureBehavior::ThrowException as i32,
943 };
944 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::!i16");
945 }
946
947 #[test]
948 fn test_cast_textify_missing_input() {
949 let ctx = TestContext::new();
950 let cast = Cast {
951 r#type: Some(make_i16_type()),
952 input: None,
953 failure_behavior: 0,
954 };
955 let (s, errs) = ctx.textify(&cast);
956 assert_eq!(s, "(!{Expression})::i16");
957 match &errs.0[0] {
958 FormatError::Format(e) => {
959 assert_eq!(e.message, "Expression");
960 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
961 }
962 other => panic!("Expected Format(InvalidValue) for missing input, got: {other}"),
963 }
964 }
965
966 #[test]
967 fn test_cast_textify_missing_type() {
968 let ctx = TestContext::new();
969 let cast = Cast {
970 r#type: None,
971 input: Some(Box::new(literal_i32(78))),
972 failure_behavior: 0,
973 };
974 let (s, errs) = ctx.textify(&cast);
975 assert_eq!(s, "(78:i32)::!{Type}");
976 match &errs.0[0] {
977 FormatError::Format(e) => {
978 assert_eq!(e.message, "Type");
979 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
980 }
981 other => panic!("Expected Format(InvalidValue) for missing type, got: {other}"),
982 }
983 }
984
985 fn struct_field_reference(field: i32) -> FieldReference {
986 FieldReference {
987 reference_type: Some(ReferenceType::DirectReference(ReferenceSegment {
988 reference_type: Some(reference_segment::ReferenceType::StructField(Box::new(
989 reference_segment::StructField { field, child: None },
990 ))),
991 })),
992 root_type: Some(RootType::RootReference(RootReference {})),
993 }
994 }
995
996 #[test]
997 fn test_field_reference_missing_root_type() {
998 let ctx = TestContext::new();
999 let mut fr = struct_field_reference(3);
1000 fr.root_type = None;
1001 let (s, errs) = ctx.textify(&fr);
1002 assert_eq!(s, "!{FieldReference}");
1003 match &errs.0[0] {
1004 FormatError::Format(e) => {
1005 assert_eq!(e.message, "FieldReference");
1006 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1007 }
1008 other => panic!("Expected Format(InvalidValue) for missing root_type, got: {other}"),
1009 }
1010 }
1011
1012 #[test]
1013 fn test_field_reference_root_reference() {
1014 let ctx = TestContext::new();
1015 let fr = struct_field_reference(3);
1016 assert_eq!(ctx.textify_no_errors(&fr), "$3");
1017 }
1018
1019 #[test]
1020 fn test_field_reference_outer_reference_unimplemented() {
1021 use substrait::proto::expression::field_reference;
1022
1023 let ctx = TestContext::new();
1024 let mut fr = struct_field_reference(3);
1025 fr.root_type = Some(RootType::OuterReference(field_reference::OuterReference {
1026 steps_out: 1,
1027 }));
1028 let (s, errs) = ctx.textify(&fr);
1029 assert_eq!(s, "!{FieldReference}");
1030 match &errs.0[0] {
1031 FormatError::Format(e) => {
1032 assert_eq!(e.message, "FieldReference");
1033 assert_eq!(e.error_type, FormatErrorType::Unimplemented);
1034 }
1035 other => panic!("Expected Format(Unimplemented) for OuterReference, got: {other}"),
1036 }
1037 }
1038
1039 #[test]
1040 fn test_field_reference_expression_unimplemented() {
1041 let ctx = TestContext::new();
1042 let mut fr = struct_field_reference(3);
1043 fr.root_type = Some(RootType::Expression(Box::new(literal_bool(true))));
1044 let (s, errs) = ctx.textify(&fr);
1045 assert_eq!(s, "!{FieldReference}");
1046 match &errs.0[0] {
1047 FormatError::Format(e) => {
1048 assert_eq!(e.message, "FieldReference");
1049 assert_eq!(e.error_type, FormatErrorType::Unimplemented);
1050 }
1051 other => panic!("Expected Format(Unimplemented) for Expression, got: {other}"),
1052 }
1053 }
1054
1055 #[test]
1056 fn test_cast_textify_invalid_failure_behavior() {
1057 let ctx = TestContext::new();
1058 let cast = Cast {
1059 r#type: Some(make_i16_type()),
1060 input: Some(Box::new(literal_i32(78))),
1061 failure_behavior: 99,
1062 };
1063 let (s, errs) = ctx.textify(&cast);
1064 assert_eq!(s, "(78:i32)::!{Cast}i16");
1066 match &errs.0[0] {
1067 FormatError::Format(e) => {
1068 assert_eq!(e.message, "Cast");
1069 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1070 }
1071 other => {
1072 panic!("Expected Format(InvalidValue) for invalid failure_behavior, got: {other}")
1073 }
1074 }
1075 }
1076}