1use std::fmt::{self};
2
3use chrono::{DateTime, NaiveDate};
4use expr::RexType;
5use substrait::proto::expression::field_reference::ReferenceType;
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: None,
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 let ref_type = match &self.reference_type {
305 None => {
306 return write!(
307 w,
308 "{}",
309 ctx.failure(PlanError::invalid(
310 "FieldReference",
311 Some("reference_type"),
312 "Required field reference_type is missing",
313 ))
314 );
315 }
316 Some(ReferenceType::DirectReference(r)) => r,
317 _ => {
318 return write!(
319 w,
320 "{}",
321 ctx.failure(PlanError::unimplemented(
322 "FieldReference",
323 Some("FieldReference"),
324 "FieldReference textification implemented only for StructField",
325 ))
326 );
327 }
328 };
329
330 match &ref_type.reference_type {
331 Some(reference_segment::ReferenceType::StructField(s)) => {
332 write!(w, "{}", Reference(s.field))
333 }
334 None => write!(
335 w,
336 "{}",
337 ctx.failure(PlanError::invalid(
338 "ReferenceSegment",
339 Some("reference_type"),
340 "Required field reference_type is missing",
341 ))
342 ),
343 _ => write!(
344 w,
345 "{}",
346 ctx.failure(PlanError::unimplemented(
347 "ReferenceSegment",
348 Some("reference_type"),
349 "ReferenceSegment textification implemented only for StructField",
350 ))
351 ),
352 }
353 }
354}
355
356impl Textify for ScalarFunction {
357 fn name() -> &'static str {
358 "ScalarFunction"
359 }
360
361 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
362 let name_and_anchor =
363 NamedAnchor::lookup(ctx, ExtensionKind::Function, self.function_reference);
364 let name_and_anchor = ctx.display(&name_and_anchor);
365
366 let args = ctx.separated(&self.arguments, ", ");
367 let options = ctx.separated(&self.options, ", ");
368 let between = if self.arguments.is_empty() || self.options.is_empty() {
369 ""
370 } else {
371 ", "
372 };
373
374 let output = OutputType(self.output_type.as_ref());
375 let output_type = ctx.optional(&output, ctx.options().fn_types);
376
377 write!(
378 w,
379 "{name_and_anchor}({args}{between}{options}){output_type}"
380 )?;
381 Ok(())
382 }
383}
384
385impl Textify for FunctionOption {
386 fn name() -> &'static str {
387 "FunctionOption"
388 }
389
390 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
391 write!(w, "{}⇒[", self.name)?;
392 let mut first = true;
393 for pref in self.preference.iter() {
394 if !first {
395 write!(w, ", ")?;
396 } else {
397 first = false;
398 }
399 write!(w, "{pref}")?;
400 }
401 write!(w, "]")?;
402 Ok(())
403 }
404}
405
406impl Textify for FunctionArgument {
407 fn name() -> &'static str {
408 "FunctionArgument"
409 }
410
411 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
412 write!(w, "{}", ctx.expect(self.arg_type.as_ref()))
413 }
414}
415
416impl Textify for ArgType {
417 fn name() -> &'static str {
418 "ArgType"
419 }
420
421 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
422 match self {
423 ArgType::Type(t) => t.textify(ctx, w),
424 ArgType::Value(v) => v.textify(ctx, w),
425 ArgType::Enum(e) => textify_enum(e, ctx, w),
426 }
427 }
428}
429
430impl Textify for Cast {
431 fn name() -> &'static str {
432 "Cast"
433 }
434
435 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
436 let failure_err;
437 let fb: &dyn fmt::Display = match cast::FailureBehavior::try_from(self.failure_behavior) {
438 Ok(cast::FailureBehavior::Unspecified) => &"",
439 Ok(cast::FailureBehavior::ReturnNull) => &"?",
440 Ok(cast::FailureBehavior::ThrowException) => &"!",
441 Err(_) => {
442 failure_err = ctx.failure(PlanError::invalid(
443 "Cast",
444 Some("failure_behavior"),
445 format!("Unknown failure_behavior value: {}", self.failure_behavior),
446 ));
447 &failure_err
448 }
449 };
450 let input = ctx.expect(self.input.as_deref());
451 let target_type = ctx.expect(self.r#type.as_ref());
452 write!(w, "({input})::{fb}{target_type}")
453 }
454}
455
456impl Textify for IfThen {
457 fn name() -> &'static str {
458 "IfThen"
459 }
460
461 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
465 write!(w, "if_then(")?;
466 for clause in &self.ifs {
467 let if_expr = ctx.expect(clause.r#if.as_ref());
468 let then_expr = ctx.expect(clause.then.as_ref());
469 write!(w, "{if_expr} -> {then_expr}, ")?;
470 }
471 let else_expr = ctx.expect(self.r#else.as_deref());
472 write!(w, "_ -> {else_expr})")
473 }
474}
475
476impl Textify for RexType {
477 fn name() -> &'static str {
478 "RexType"
479 }
480
481 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
482 match self {
483 RexType::Literal(literal) => literal.textify(ctx, w),
484 RexType::Selection(f) => f.textify(ctx, w),
485 RexType::ScalarFunction(s) => s.textify(ctx, w),
486 RexType::WindowFunction(_w) => write!(
487 w,
488 "{}",
489 ctx.failure(PlanError::unimplemented(
490 "RexType",
491 Some("WindowFunction"),
492 "WindowFunction textification not implemented",
493 ))
494 ),
495 RexType::IfThen(i) => i.textify(ctx, w),
496 RexType::SwitchExpression(_s) => write!(
497 w,
498 "{}",
499 ctx.failure(PlanError::unimplemented(
500 "RexType",
501 Some("SwitchExpression"),
502 "SwitchExpression textification not implemented",
503 ))
504 ),
505 RexType::SingularOrList(_s) => write!(
506 w,
507 "{}",
508 ctx.failure(PlanError::unimplemented(
509 "RexType",
510 Some("SingularOrList"),
511 "SingularOrList textification not implemented",
512 ))
513 ),
514 RexType::MultiOrList(_m) => write!(
515 w,
516 "{}",
517 ctx.failure(PlanError::unimplemented(
518 "RexType",
519 Some("MultiOrList"),
520 "MultiOrList textification not implemented",
521 ))
522 ),
523 RexType::Cast(c) => c.textify(ctx, w),
524 RexType::Subquery(_s) => write!(
525 w,
526 "{}",
527 ctx.failure(PlanError::unimplemented(
528 "RexType",
529 Some("Subquery"),
530 "Subquery textification not implemented",
531 ))
532 ),
533 RexType::Nested(_n) => write!(
534 w,
535 "{}",
536 ctx.failure(PlanError::unimplemented(
537 "RexType",
538 Some("Nested"),
539 "Nested textification not implemented",
540 ))
541 ),
542 RexType::DynamicParameter(_d) => write!(
543 w,
544 "{}",
545 ctx.failure(PlanError::unimplemented(
546 "RexType",
547 Some("DynamicParameter"),
548 "DynamicParameter textification not implemented",
549 ))
550 ),
551 #[allow(deprecated)]
552 RexType::Enum(_) => write!(
553 w,
554 "{}",
555 ctx.failure(PlanError::unimplemented(
556 "RexType",
557 Some("Enum"),
558 "Enum textification not implemented",
559 ))
560 ),
561 }
562 }
563}
564
565impl Textify for Expression {
566 fn name() -> &'static str {
567 "Expression"
568 }
569
570 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
571 write!(w, "{}", ctx.expect(self.rex_type.as_ref()))
572 }
573}
574
575impl Textify for AggregateFunction {
576 fn name() -> &'static str {
577 "AggregateFunction"
578 }
579
580 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
581 let name_and_anchor =
583 NamedAnchor::lookup(ctx, ExtensionKind::Function, self.function_reference);
584 let name_and_anchor = ctx.display(&name_and_anchor);
585
586 let args = ctx.separated(&self.arguments, ", ");
587 let options = ctx.separated(&self.options, ", ");
588 let between = if self.arguments.is_empty() || self.options.is_empty() {
589 ""
590 } else {
591 ", "
592 };
593
594 let output = OutputType(self.output_type.as_ref());
595 let output_type = ctx.optional(&output, ctx.options().fn_types);
596
597 write!(
598 w,
599 "{name_and_anchor}({args}{between}{options}){output_type}"
600 )
601 }
602}
603
604#[cfg(test)]
605mod tests {
606 use substrait::proto::Type;
607 use substrait::proto::expression::{cast, if_then};
608 use substrait::proto::r#type::{I16, I32, Kind, Nullability};
609
610 use super::*;
611 use crate::extensions::simple::{ExtensionKind, MissingReference};
612 use crate::fixtures::TestContext;
613 use crate::textify::foundation::{FormatError, FormatErrorType};
614
615 fn literal_bool(value: bool) -> Expression {
616 Expression {
617 rex_type: Some(RexType::Literal(expr::Literal {
618 nullable: false,
619 type_variation_reference: 0,
620 literal_type: Some(expr::literal::LiteralType::Boolean(value)),
621 })),
622 }
623 }
624
625 fn non_nullable_literal(lit: expr::literal::LiteralType) -> expr::Literal {
626 expr::Literal {
627 nullable: false,
628 type_variation_reference: 0,
629 literal_type: Some(lit),
630 }
631 }
632
633 #[test]
634 fn test_literal_textify() {
635 let ctx = TestContext::new();
636
637 let literal = non_nullable_literal(LiteralType::Boolean(true));
638 assert_eq!(ctx.textify_no_errors(&literal), "true");
639 }
640
641 fn nullable_literal(lit: expr::literal::LiteralType) -> expr::Literal {
642 expr::Literal {
643 nullable: true,
644 type_variation_reference: 0,
645 literal_type: Some(lit),
646 }
647 }
648
649 #[test]
650 fn test_nullable_boolean_literal_textify() {
651 let ctx = TestContext::new();
652 assert_eq!(
653 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Boolean(true))),
654 "true:boolean?"
655 );
656 assert_eq!(
657 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Boolean(
658 false
659 ))),
660 "false:boolean?"
661 );
662 }
663
664 #[test]
665 fn test_nullable_integer_literal_textify() {
666 let ctx = TestContext::new();
667 assert_eq!(
668 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::I32(78))),
669 "78:i32?"
670 );
671 assert_eq!(
672 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::I64(42))),
673 "42:i64?"
674 );
675 }
676
677 #[test]
678 fn test_nullable_float_literal_textify() {
679 let ctx = TestContext::new();
680 assert_eq!(
681 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Fp32(2.5))),
682 "2.5:fp32?"
683 );
684 assert_eq!(
685 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Fp64(3.19))),
686 "3.19:fp64?"
687 );
688 }
689
690 #[test]
691 fn test_expression_textify() {
692 let ctx = TestContext::new();
693
694 let expr_empty = Expression { rex_type: None }; let (s, errs) = ctx.textify(&expr_empty);
697 assert!(!errs.is_empty());
698 assert_eq!(s, "!{RexType}");
699
700 let expr_lit = Expression {
702 rex_type: Some(RexType::Literal(expr::Literal {
703 nullable: false,
704 type_variation_reference: 0,
705 literal_type: Some(expr::literal::LiteralType::Boolean(true)),
706 })),
707 };
708 assert_eq!(ctx.textify_no_errors(&expr_lit), "true");
709 }
710
711 #[test]
712 fn test_rextype_textify() {
713 let ctx = TestContext::new();
714
715 let func = RexType::ScalarFunction(ScalarFunction {
716 function_reference: 1000, arguments: vec![],
718 options: vec![],
719 output_type: None,
720 #[allow(deprecated)]
721 args: vec![],
722 });
723 let (s, errq) = ctx.textify(&func);
728 let errs: Vec<_> = errq.0;
729 match errs[0] {
730 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => {
731 assert_eq!(k, ExtensionKind::Function);
732 assert_eq!(a, 1000);
733 }
734 _ => panic!("Expected Lookup MissingAnchor: {}", errs[0]),
735 }
736 assert_eq!(s, "!{function}#1000()");
737
738 let ctx = ctx.with_urn(1, "first").with_function(1, 100, "first");
739 let func = RexType::ScalarFunction(ScalarFunction {
740 function_reference: 100,
741 arguments: vec![],
742 options: vec![],
743 output_type: None,
744 #[allow(deprecated)]
745 args: vec![],
746 });
747 let s = ctx.textify_no_errors(&func);
748 assert_eq!(s, "first()");
749
750 let options_show_anchor = Default::default();
752
753 let ctx = TestContext::new()
754 .with_options(options_show_anchor)
755 .with_urn(1, "somewhere_on_the_internet")
756 .with_urn(2, "somewhere_else")
757 .with_function(1, 231, "duplicated")
758 .with_function(2, 232, "duplicated");
759
760 let rex_dup = RexType::ScalarFunction(ScalarFunction {
761 function_reference: 231,
762 arguments: vec![FunctionArgument {
763 arg_type: Some(ArgType::Value(Expression {
764 rex_type: Some(RexType::Literal(expr::Literal {
765 nullable: false,
766 type_variation_reference: 0,
767 literal_type: Some(expr::literal::LiteralType::Boolean(true)),
768 })),
769 })),
770 }],
771 options: vec![],
772 output_type: None,
773 #[allow(deprecated)]
774 args: vec![],
775 });
776 let s = ctx.textify_no_errors(&rex_dup);
777 assert_eq!(s, "duplicated#231(true)");
778 }
779
780 #[test]
781 fn test_ifthen_textify() {
782 let ctx = TestContext::new();
783
784 let if_then = IfThen {
785 ifs: vec![
786 if_then::IfClause {
787 r#if: Some(literal_bool(true)),
788 then: Some(literal_bool(false)),
789 },
790 if_then::IfClause {
791 r#if: Some(literal_bool(false)),
792 then: Some(literal_bool(true)),
793 },
794 ],
795 r#else: Some(Box::new(literal_bool(true))),
796 };
797
798 let s = ctx.textify_no_errors(&if_then);
799 assert_eq!(s, "if_then(true -> false, false -> true, _ -> true)");
800 }
801
802 #[test]
803 fn test_ifthen_textify_missing_else() {
804 let ctx = TestContext::new();
805
806 let if_then = IfThen {
807 ifs: vec![if_then::IfClause {
808 r#if: Some(literal_bool(true)),
809 then: Some(literal_bool(false)),
810 }],
811 r#else: None,
812 };
813
814 let (s, errs) = ctx.textify(&if_then);
815 assert_eq!(s, "if_then(true -> false, _ -> !{Expression})");
816 assert_eq!(errs.0.len(), 1);
817 }
818
819 fn make_i32_type() -> Type {
820 Type {
821 kind: Some(Kind::I32(I32 {
822 nullability: Nullability::Required as i32,
823 type_variation_reference: 0,
824 })),
825 }
826 }
827
828 fn make_i16_type() -> Type {
829 Type {
830 kind: Some(Kind::I16(I16 {
831 nullability: Nullability::Required as i32,
832 type_variation_reference: 0,
833 })),
834 }
835 }
836
837 fn literal_i32(value: i32) -> Expression {
838 Expression {
839 rex_type: Some(RexType::Literal(expr::Literal {
840 nullable: false,
841 type_variation_reference: 0,
842 literal_type: Some(expr::literal::LiteralType::I32(value)),
843 })),
844 }
845 }
846
847 #[test]
848 fn test_cast_textify() {
849 let ctx = TestContext::new();
850 let cast = Cast {
851 r#type: Some(make_i16_type()),
852 input: Some(Box::new(literal_i32(78))),
853 failure_behavior: 0,
854 };
855 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::i16");
856 }
857
858 #[test]
859 fn test_cast_textify_via_rextype() {
860 let ctx = TestContext::new();
861 let rex = RexType::Cast(Box::new(Cast {
862 r#type: Some(make_i16_type()),
863 input: Some(Box::new(literal_i32(78))),
864 failure_behavior: 0,
865 }));
866 assert_eq!(ctx.textify_no_errors(&rex), "(78:i32)::i16");
867 }
868
869 #[test]
870 fn test_cast_textify_nested() {
871 let ctx = TestContext::new();
873 let inner_cast = Expression {
874 rex_type: Some(RexType::Cast(Box::new(Cast {
875 r#type: Some(make_i16_type()),
876 input: Some(Box::new(literal_i32(78))),
877 failure_behavior: 0,
878 }))),
879 };
880 let outer_cast = Cast {
881 r#type: Some(make_i32_type()),
882 input: Some(Box::new(inner_cast)),
883 failure_behavior: 0,
884 };
885 assert_eq!(ctx.textify_no_errors(&outer_cast), "((78:i32)::i16)::i32");
886 }
887
888 #[test]
889 fn test_cast_textify_return_null() {
890 let ctx = TestContext::new();
891 let cast = Cast {
892 r#type: Some(make_i16_type()),
893 input: Some(Box::new(literal_i32(78))),
894 failure_behavior: cast::FailureBehavior::ReturnNull as i32,
895 };
896 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::?i16");
897 }
898
899 #[test]
900 fn test_cast_textify_throw_exception() {
901 let ctx = TestContext::new();
902 let cast = Cast {
903 r#type: Some(make_i16_type()),
904 input: Some(Box::new(literal_i32(78))),
905 failure_behavior: cast::FailureBehavior::ThrowException as i32,
906 };
907 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::!i16");
908 }
909
910 #[test]
911 fn test_cast_textify_missing_input() {
912 let ctx = TestContext::new();
913 let cast = Cast {
914 r#type: Some(make_i16_type()),
915 input: None,
916 failure_behavior: 0,
917 };
918 let (s, errs) = ctx.textify(&cast);
919 assert_eq!(s, "(!{Expression})::i16");
920 match &errs.0[0] {
921 FormatError::Format(e) => {
922 assert_eq!(e.message, "Expression");
923 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
924 }
925 other => panic!("Expected Format(InvalidValue) for missing input, got: {other}"),
926 }
927 }
928
929 #[test]
930 fn test_cast_textify_missing_type() {
931 let ctx = TestContext::new();
932 let cast = Cast {
933 r#type: None,
934 input: Some(Box::new(literal_i32(78))),
935 failure_behavior: 0,
936 };
937 let (s, errs) = ctx.textify(&cast);
938 assert_eq!(s, "(78:i32)::!{Type}");
939 match &errs.0[0] {
940 FormatError::Format(e) => {
941 assert_eq!(e.message, "Type");
942 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
943 }
944 other => panic!("Expected Format(InvalidValue) for missing type, got: {other}"),
945 }
946 }
947
948 #[test]
949 fn test_cast_textify_invalid_failure_behavior() {
950 let ctx = TestContext::new();
951 let cast = Cast {
952 r#type: Some(make_i16_type()),
953 input: Some(Box::new(literal_i32(78))),
954 failure_behavior: 99,
955 };
956 let (s, errs) = ctx.textify(&cast);
957 assert_eq!(s, "(78:i32)::!{Cast}i16");
959 match &errs.0[0] {
960 FormatError::Format(e) => {
961 assert_eq!(e.message, "Cast");
962 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
963 }
964 other => {
965 panic!("Expected Format(InvalidValue) for invalid failure_behavior, got: {other}")
966 }
967 }
968 }
969}