1use std::fmt;
2
3use chrono::{DateTime, NaiveDate, NaiveTime};
4use expr::RexType;
5use substrait::proto::expression::field_reference::{ReferenceType, RootReference, RootType};
6use substrait::proto::expression::literal::interval_day_to_second::PrecisionMode;
7use substrait::proto::expression::literal::{IntervalDayToSecond, LiteralType};
8use substrait::proto::expression::{
9 Cast, FieldReference, IfThen, ReferenceSegment, ScalarFunction, cast, reference_segment,
10};
11use substrait::proto::function_argument::ArgType;
12use substrait::proto::{
13 AggregateFunction, Expression, FunctionArgument, FunctionOption, expression as expr,
14};
15
16use super::{PlanError, Scope, Textify, Visibility};
17use crate::extensions::simple::ExtensionKind;
18use crate::precision::SupportedPrecision;
19use crate::textify::types::{Name, NamedAnchor, OutputType, escaped};
20
21pub fn textify_binary<S: Scope, W: fmt::Write>(items: &[u8], ctx: &S, w: &mut W) -> fmt::Result {
34 if ctx.options().show_literal_binaries {
35 write!(w, "0x")?;
36 for &n in items {
37 write!(w, "{n:02x}")?;
38 }
39 } else {
40 write!(w, "{{binary}}")?;
41 }
42 Ok(())
43}
44
45fn unimplemented_literal<S: Scope, W: fmt::Write>(
47 variant: &'static str,
48 ctx: &S,
49 w: &mut W,
50) -> fmt::Result {
51 write!(
52 w,
53 "{}",
54 ctx.failure(PlanError::unimplemented(
55 "LiteralType",
56 Some(variant),
57 format!("{variant} literal textification not implemented"),
58 ))
59 )
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63enum PrecisionFormatError {
64 UnsupportedPrecision,
66 OutOfRange,
69}
70
71impl PrecisionFormatError {
72 fn into_plan_error(self, variant: &'static str, precision: i32) -> PlanError {
73 let message = match self {
74 PrecisionFormatError::UnsupportedPrecision => {
75 format!("unsupported precision {precision} for {variant}")
76 }
77 PrecisionFormatError::OutOfRange => {
78 format!("value is out of range for {variant} at precision {precision}")
79 }
80 };
81 PlanError::invalid("LiteralType", Some(variant), message)
82 }
83}
84
85fn picosecond_truncation_warning(variant: &'static str) -> PlanError {
87 PlanError::invalid(
88 "LiteralType",
89 Some(variant),
90 "precision 12 (picoseconds) truncated to nanoseconds; sub-nanosecond precision lost",
91 )
92}
93
94fn write_precision_literal<S: Scope, W: fmt::Write>(
95 variant: &'static str,
96 precision: i32,
97 formatted: Result<String, PrecisionFormatError>,
98 ctx: &S,
99 w: &mut W,
100) -> fmt::Result {
101 match formatted {
102 Ok(s) => {
103 if precision == 12 {
104 ctx.push_error(picosecond_truncation_warning(variant).into());
105 }
106 write!(w, "'{}'", escaped(&s))
107 }
108 Err(e) => write!(w, "{}", ctx.failure(e.into_plan_error(variant, precision))),
109 }
110}
111
112pub fn textify_enum<S: Scope, W: fmt::Write>(s: &str, _ctx: &S, w: &mut W) -> fmt::Result {
115 write!(w, "&{}", Name(s))
116}
117
118fn days_to_date_string(days: i32) -> String {
120 let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
121 let date = epoch + chrono::Duration::days(days as i64);
122 date.format("%Y-%m-%d").to_string()
123}
124
125fn duration_from_precision_units(
128 value: i64,
129 precision: i32,
130) -> Result<chrono::Duration, PrecisionFormatError> {
131 match precision {
132 0 => chrono::Duration::try_seconds(value).ok_or(PrecisionFormatError::OutOfRange),
133 3 => chrono::Duration::try_milliseconds(value).ok_or(PrecisionFormatError::OutOfRange),
134 6 => Ok(chrono::Duration::microseconds(value)),
135 9 => Ok(chrono::Duration::nanoseconds(value)),
136 12 => Ok(chrono::Duration::nanoseconds(value / 1000)),
137 _ => Err(PrecisionFormatError::UnsupportedPrecision),
138 }
139}
140
141fn fractional_spec(precision: i32) -> &'static str {
146 match precision {
147 3 => "%.3f",
148 6 => "%.6f",
149 9 | 12 => "%.9f",
150 _ => "", }
152}
153
154fn precision_timestamp_to_string(
157 value: i64,
158 precision: i32,
159) -> Result<String, PrecisionFormatError> {
160 let duration = duration_from_precision_units(value, precision)?;
161 let epoch = DateTime::from_timestamp(0, 0).unwrap().naive_utc();
162 let datetime = epoch
163 .checked_add_signed(duration)
164 .ok_or(PrecisionFormatError::OutOfRange)?;
165
166 let format = format!("%Y-%m-%dT%H:%M:%S{}", fractional_spec(precision));
167 Ok(datetime.format(&format).to_string())
168}
169
170fn precision_time_to_string(value: i64, precision: i32) -> Result<String, PrecisionFormatError> {
179 if value < 0 {
180 return Err(PrecisionFormatError::OutOfRange);
181 }
182 let duration = duration_from_precision_units(value, precision)?;
183 if duration >= chrono::Duration::days(1) {
184 return Err(PrecisionFormatError::OutOfRange);
185 }
186 let midnight = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
187 let time = midnight + duration;
188
189 let format = format!("%H:%M:%S{}", fractional_spec(precision));
190 Ok(time.format(&format).to_string())
191}
192
193fn interval_day_precision_units(interval: &IntervalDayToSecond) -> (i32, i64) {
201 #[allow(deprecated)]
202 match interval.precision_mode {
203 Some(PrecisionMode::Precision(p)) => (p, interval.subseconds),
204 Some(PrecisionMode::Microseconds(us)) => (6, us as i64),
205 None => (6, interval.subseconds),
206 }
207}
208
209fn write_interval_day_to_second_literal<S: Scope, W: fmt::Write>(
210 interval: &IntervalDayToSecond,
211 ctx: &S,
212 w: &mut W,
213) -> fmt::Result {
214 const MAX_DAYS: i64 = 3_650_000;
218
219 let (units, subseconds) = interval_day_precision_units(interval);
220 let Some(precision) = SupportedPrecision::from_units(units) else {
221 return write!(
222 w,
223 "{}",
224 ctx.failure(PlanError::invalid(
225 "LiteralType",
226 Some("IntervalDayToSecond.precision"),
227 format!(
228 "IntervalDayToSecond precision {units} has no duration unit to write it in; expected one of 0, 3, 6, 9, or 12"
229 ),
230 ))
231 );
232 };
233
234 let total_seconds = interval.days as i64 * 86_400 + interval.seconds as i64;
235 if !(-MAX_DAYS..=MAX_DAYS).contains(&(interval.days as i64))
236 || !(-MAX_DAYS * 86_400..=MAX_DAYS * 86_400).contains(&total_seconds)
237 {
238 ctx.push_error(
239 PlanError::invalid(
240 "LiteralType",
241 Some("IntervalDayToSecond.days"),
242 format!(
243 "IntervalDayToSecond days ({}) and seconds ({}) exceed the Substrait maximum of {MAX_DAYS} days",
244 interval.days, interval.seconds
245 ),
246 )
247 .into(),
248 );
249 }
250
251 let mut parts = Vec::new();
252 if interval.days != 0 {
253 parts.push(format!("{}d", interval.days));
254 }
255 if interval.seconds != 0 {
256 parts.push(format!("{}s", interval.seconds));
257 }
258 if subseconds != 0 {
259 let Some(unit) = precision.subsecond_unit() else {
262 return write!(
263 w,
264 "{}",
265 ctx.failure(PlanError::invalid(
266 "LiteralType",
267 Some("IntervalDayToSecond.subseconds"),
268 format!(
269 "IntervalDayToSecond subseconds ({subseconds}) must be 0 at precision 0; there is no sub-second unit to write it in"
270 ),
271 ))
272 );
273 };
274
275 let subsecond_bound = 10_i64.pow(precision.units() as u32);
279 if subseconds <= -subsecond_bound || subseconds >= subsecond_bound {
280 ctx.push_error(
281 PlanError::invalid(
282 "LiteralType",
283 Some("IntervalDayToSecond.subseconds"),
284 format!(
285 "IntervalDayToSecond subseconds ({subseconds}) is a whole second or more at precision {precision}; whole seconds belong in the seconds field"
286 ),
287 )
288 .into(),
289 );
290 }
291
292 parts.push(format!("{subseconds}{unit}"));
293 }
294
295 let duration = if parts.is_empty() {
296 "0s".to_string()
297 } else {
298 parts.join(" ")
299 };
300
301 write!(w, "'{}'", escaped(&duration))
302}
303
304fn write_literal_value<S: Scope, W: fmt::Write>(
309 lit: &LiteralType,
310 ctx: &S,
311 w: &mut W,
312) -> fmt::Result {
313 match lit {
314 LiteralType::Boolean(b) => write!(w, "{b}"),
315 LiteralType::I8(i) | LiteralType::I16(i) | LiteralType::I32(i) => write!(w, "{i}"),
316 LiteralType::I64(i) => write!(w, "{i}"),
317 LiteralType::Fp32(f) => write!(w, "{f}"),
318 LiteralType::Fp64(f) => write!(w, "{f}"),
319 LiteralType::String(s) => write!(w, "'{}'", s.escape_debug()),
320 LiteralType::Binary(items) => textify_binary(items, ctx, w),
321 LiteralType::Date(days) => {
322 write!(w, "'{}'", escaped(&days_to_date_string(*days)))
323 }
324 #[allow(deprecated)]
325 LiteralType::Time(microseconds) => write_precision_literal(
326 "Time",
327 6,
328 precision_time_to_string(*microseconds, 6),
329 ctx,
330 w,
331 ),
332 #[allow(deprecated)]
333 LiteralType::Timestamp(microseconds) => write_precision_literal(
334 "Timestamp",
335 6,
336 precision_timestamp_to_string(*microseconds, 6),
337 ctx,
338 w,
339 ),
340 LiteralType::IntervalYearToMonth(_) => unimplemented_literal("IntervalYearToMonth", ctx, w),
341 LiteralType::IntervalDayToSecond(interval) => {
342 write_interval_day_to_second_literal(interval, ctx, w)
343 }
344 LiteralType::IntervalCompound(_) => unimplemented_literal("IntervalCompound", ctx, w),
345 LiteralType::FixedChar(_) => unimplemented_literal("FixedChar", ctx, w),
346 LiteralType::VarChar(_) => unimplemented_literal("VarChar", ctx, w),
347 LiteralType::FixedBinary(_) => unimplemented_literal("FixedBinary", ctx, w),
348 LiteralType::Decimal(_) => unimplemented_literal("Decimal", ctx, w),
349 LiteralType::PrecisionTime(p) => write_precision_literal(
350 "PrecisionTime",
351 p.precision,
352 precision_time_to_string(p.value, p.precision),
353 ctx,
354 w,
355 ),
356 LiteralType::PrecisionTimestamp(p) => write_precision_literal(
357 "PrecisionTimestamp",
358 p.precision,
359 precision_timestamp_to_string(p.value, p.precision),
360 ctx,
361 w,
362 ),
363 LiteralType::PrecisionTimestampTz(p) => write_precision_literal(
364 "PrecisionTimestampTz",
365 p.precision,
366 precision_timestamp_to_string(p.value, p.precision),
367 ctx,
368 w,
369 ),
370 LiteralType::Struct(_) => unimplemented_literal("Struct", ctx, w),
371 LiteralType::Map(_) => unimplemented_literal("Map", ctx, w),
372 #[allow(deprecated)]
373 LiteralType::TimestampTz(_) => unimplemented_literal("TimestampTz", ctx, w),
374 LiteralType::Uuid(_) => unimplemented_literal("Uuid", ctx, w),
375 LiteralType::Null(_) => write!(w, "null"),
376 LiteralType::List(_) => unimplemented_literal("List", ctx, w),
377 LiteralType::EmptyList(_) => unimplemented_literal("EmptyList", ctx, w),
378 LiteralType::EmptyMap(_) => unimplemented_literal("EmptyMap", ctx, w),
379 LiteralType::UserDefined(_) => unimplemented_literal("UserDefined", ctx, w),
380 }
381}
382
383fn write_literal_type_suffix<W: fmt::Write>(
391 lit: &LiteralType,
392 nullable: bool,
393 w: &mut W,
394) -> fmt::Result {
395 let (name, precision): (&'static str, Option<i32>) = match lit {
397 LiteralType::Boolean(_) => ("boolean", None),
398 LiteralType::I8(_) => ("i8", None),
399 LiteralType::I16(_) => ("i16", None),
400 LiteralType::I32(_) => ("i32", None),
401 LiteralType::I64(_) => ("i64", None),
402 LiteralType::Fp32(_) => ("fp32", None),
403 LiteralType::Fp64(_) => ("fp64", None),
404 LiteralType::String(_) => ("string", None),
405 LiteralType::Binary(_) => ("binary", None),
406 LiteralType::Date(_) => ("date", None),
407 #[allow(deprecated)]
408 LiteralType::Time(_) => ("time", None),
409 #[allow(deprecated)]
410 LiteralType::Timestamp(_) => ("timestamp", None),
411 LiteralType::PrecisionTimestamp(p) => ("precisiontimestamp", Some(p.precision)),
412 LiteralType::PrecisionTimestampTz(p) => ("precisiontimestamptz", Some(p.precision)),
413 LiteralType::PrecisionTime(p) => ("precisiontime", Some(p.precision)),
414 LiteralType::IntervalDayToSecond(interval) => (
417 "interval_day",
418 Some(interval_day_precision_units(interval).0),
419 ),
420 _ => return Ok(()),
421 };
422
423 write!(w, ":{name}")?;
424 if nullable {
425 write!(w, "?")?;
426 }
427 if let Some(p) = precision {
428 write!(w, "<{p}>")?;
429 }
430 Ok(())
431}
432
433fn is_default_for_syntax(lit: &LiteralType) -> bool {
446 matches!(
447 lit,
448 LiteralType::Boolean(_)
449 | LiteralType::String(_)
450 | LiteralType::Binary(_)
451 | LiteralType::I64(_)
452 | LiteralType::Fp64(_)
453 )
454}
455
456impl Textify for expr::Literal {
457 fn name() -> &'static str {
458 "Literal"
459 }
460
461 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
462 let Some(lit) = self.literal_type.as_ref() else {
463 return write!(
464 w,
465 "{}",
466 ctx.failure(PlanError::invalid(
467 "Literal",
468 Some("literal_type"),
469 "missing literal_type",
470 ))
471 );
472 };
473 write_literal_value(lit, ctx, w)?;
474 let show_suffix = match ctx.options().literal_types {
475 Visibility::Never => false,
476 Visibility::Always => true,
477 Visibility::Required => self.nullable || !is_default_for_syntax(lit),
478 };
479 if let LiteralType::Null(typ) = lit {
480 write!(w, ":{}", ctx.expect(Some(typ)))?;
481 return Ok(());
482 }
483 if show_suffix {
484 write_literal_type_suffix(lit, self.nullable, w)?;
485 }
486 Ok(())
487 }
488}
489
490pub struct Reference(pub i32);
491
492impl fmt::Display for Reference {
493 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
494 write!(f, "${}", self.0)
495 }
496}
497
498impl From<Reference> for Expression {
499 fn from(r: Reference) -> Self {
500 Expression {
503 rex_type: Some(RexType::Selection(Box::new(FieldReference {
504 reference_type: Some(ReferenceType::DirectReference(ReferenceSegment {
505 reference_type: Some(reference_segment::ReferenceType::StructField(Box::new(
506 reference_segment::StructField {
507 field: r.0,
508 child: None,
509 },
510 ))),
511 })),
512 root_type: Some(RootType::RootReference(RootReference {})),
513 }))),
514 }
515 }
516}
517
518impl Textify for Reference {
519 fn name() -> &'static str {
520 "Reference"
521 }
522
523 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
524 write!(w, "{self}")
525 }
526}
527
528impl Textify for FieldReference {
529 fn name() -> &'static str {
530 "FieldReference"
531 }
532
533 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
534 match &self.root_type {
535 Some(RootType::RootReference(_)) => {}
536 None => {
537 return write!(
538 w,
539 "{}",
540 ctx.failure(PlanError::invalid(
541 "FieldReference",
542 Some("root_type"),
543 "Required field root_type is missing",
544 ))
545 );
546 }
547 Some(RootType::Expression(_)) => {
548 return write!(
549 w,
550 "{}",
551 ctx.failure(PlanError::unimplemented(
552 "FieldReference",
553 Some("root_type"),
554 "FieldReference textification not implemented for Expression root_type",
555 ))
556 );
557 }
558 Some(RootType::OuterReference(_)) => {
559 return write!(
560 w,
561 "{}",
562 ctx.failure(PlanError::unimplemented(
563 "FieldReference",
564 Some("root_type"),
565 "FieldReference textification not implemented for OuterReference root_type",
566 ))
567 );
568 }
569 Some(RootType::LambdaParameterReference(_)) => {
570 return write!(
571 w,
572 "{}",
573 ctx.failure(PlanError::unimplemented(
574 "FieldReference",
575 Some("root_type"),
576 "FieldReference textification not implemented for LambdaParameterReference root_type",
577 ))
578 );
579 }
580 }
581
582 let ref_type = match &self.reference_type {
583 None => {
584 return write!(
585 w,
586 "{}",
587 ctx.failure(PlanError::invalid(
588 "FieldReference",
589 Some("reference_type"),
590 "Required field reference_type is missing",
591 ))
592 );
593 }
594 Some(ReferenceType::DirectReference(r)) => r,
595 _ => {
596 return write!(
597 w,
598 "{}",
599 ctx.failure(PlanError::unimplemented(
600 "FieldReference",
601 Some("FieldReference"),
602 "FieldReference textification implemented only for StructField",
603 ))
604 );
605 }
606 };
607
608 match &ref_type.reference_type {
609 Some(reference_segment::ReferenceType::StructField(s)) => {
610 write!(w, "{}", Reference(s.field))
611 }
612 None => write!(
613 w,
614 "{}",
615 ctx.failure(PlanError::invalid(
616 "ReferenceSegment",
617 Some("reference_type"),
618 "Required field reference_type is missing",
619 ))
620 ),
621 _ => write!(
622 w,
623 "{}",
624 ctx.failure(PlanError::unimplemented(
625 "ReferenceSegment",
626 Some("reference_type"),
627 "ReferenceSegment textification implemented only for StructField",
628 ))
629 ),
630 }
631 }
632}
633
634#[derive(Debug, Clone, Copy)]
642pub struct FunctionInvocation<'a> {
643 pub function_reference: u32,
644 pub arguments: &'a [FunctionArgument],
645 pub options: &'a [FunctionOption],
646}
647
648impl<'a> From<&'a ScalarFunction> for FunctionInvocation<'a> {
649 fn from(f: &'a ScalarFunction) -> Self {
650 FunctionInvocation {
651 function_reference: f.function_reference,
652 arguments: &f.arguments,
653 options: &f.options,
654 }
655 }
656}
657
658impl<'a> From<&'a AggregateFunction> for FunctionInvocation<'a> {
659 fn from(f: &'a AggregateFunction) -> Self {
660 FunctionInvocation {
661 function_reference: f.function_reference,
662 arguments: &f.arguments,
663 options: &f.options,
664 }
665 }
666}
667
668impl Textify for FunctionInvocation<'_> {
669 fn name() -> &'static str {
670 "FunctionInvocation"
671 }
672
673 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
674 let name_and_anchor =
675 NamedAnchor::lookup(ctx, ExtensionKind::Function, self.function_reference);
676 let name_and_anchor = ctx.display(&name_and_anchor);
677
678 let args = ctx.separated(self.arguments, ", ");
679 let options = ctx.separated(self.options, ", ");
680 let between = if self.arguments.is_empty() || self.options.is_empty() {
681 ""
682 } else {
683 ", "
684 };
685
686 write!(w, "{name_and_anchor}({args}{between}{options})")
687 }
688}
689
690impl Textify for ScalarFunction {
691 fn name() -> &'static str {
692 "ScalarFunction"
693 }
694
695 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
696 let invocation = FunctionInvocation::from(self);
697 let output_type = OutputType(self.output_type.as_ref());
698
699 let invocation = ctx.display(&invocation);
700 let output_type = ctx.display(&output_type);
701
702 write!(w, "{invocation}{output_type}")
703 }
704}
705
706impl Textify for FunctionOption {
707 fn name() -> &'static str {
708 "FunctionOption"
709 }
710
711 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
712 write!(w, "{}⇒[", self.name)?;
713 let mut first = true;
714 for pref in self.preference.iter() {
715 if !first {
716 write!(w, ", ")?;
717 } else {
718 first = false;
719 }
720 write!(w, "{pref}")?;
721 }
722 write!(w, "]")?;
723 Ok(())
724 }
725}
726
727impl Textify for FunctionArgument {
728 fn name() -> &'static str {
729 "FunctionArgument"
730 }
731
732 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
733 write!(w, "{}", ctx.expect(self.arg_type.as_ref()))
734 }
735}
736
737impl Textify for ArgType {
738 fn name() -> &'static str {
739 "ArgType"
740 }
741
742 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
743 match self {
744 ArgType::Type(t) => t.textify(ctx, w),
745 ArgType::Value(v) => v.textify(ctx, w),
746 ArgType::Enum(e) => textify_enum(e, ctx, w),
747 }
748 }
749}
750
751impl Textify for Cast {
752 fn name() -> &'static str {
753 "Cast"
754 }
755
756 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
757 let failure_err;
758 let fb: &dyn fmt::Display = match cast::FailureBehavior::try_from(self.failure_behavior) {
759 Ok(cast::FailureBehavior::Unspecified) => &"",
760 Ok(cast::FailureBehavior::ReturnNull) => &"?",
761 Ok(cast::FailureBehavior::ThrowException) => &"!",
762 Err(_) => {
763 failure_err = ctx.failure(PlanError::invalid(
764 "Cast",
765 Some("failure_behavior"),
766 format!("Unknown failure_behavior value: {}", self.failure_behavior),
767 ));
768 &failure_err
769 }
770 };
771 let input = ctx.expect(self.input.as_deref());
772 let target_type = ctx.expect(self.r#type.as_ref());
773 write!(w, "({input})::{fb}{target_type}")
774 }
775}
776
777impl Textify for IfThen {
778 fn name() -> &'static str {
779 "IfThen"
780 }
781
782 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
786 write!(w, "if_then(")?;
787 for clause in &self.ifs {
788 let if_expr = ctx.expect(clause.r#if.as_ref());
789 let then_expr = ctx.expect(clause.then.as_ref());
790 write!(w, "{if_expr} -> {then_expr}, ")?;
791 }
792 let else_expr = ctx.expect(self.r#else.as_deref());
793 write!(w, "_ -> {else_expr})")
794 }
795}
796
797impl Textify for RexType {
798 fn name() -> &'static str {
799 "RexType"
800 }
801
802 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
803 match self {
804 RexType::Literal(literal) => literal.textify(ctx, w),
805 RexType::Selection(f) => f.textify(ctx, w),
806 RexType::ScalarFunction(s) => s.textify(ctx, w),
807 RexType::WindowFunction(_f) => write!(
808 w,
809 "{}",
810 ctx.failure(PlanError::unimplemented(
811 "RexType",
812 Some("WindowFunction"),
813 "WindowFunction textification not implemented",
814 ))
815 ),
816 RexType::IfThen(i) => i.textify(ctx, w),
817 RexType::SwitchExpression(_s) => write!(
818 w,
819 "{}",
820 ctx.failure(PlanError::unimplemented(
821 "RexType",
822 Some("SwitchExpression"),
823 "SwitchExpression textification not implemented",
824 ))
825 ),
826 RexType::SingularOrList(_s) => write!(
827 w,
828 "{}",
829 ctx.failure(PlanError::unimplemented(
830 "RexType",
831 Some("SingularOrList"),
832 "SingularOrList textification not implemented",
833 ))
834 ),
835 RexType::MultiOrList(_m) => write!(
836 w,
837 "{}",
838 ctx.failure(PlanError::unimplemented(
839 "RexType",
840 Some("MultiOrList"),
841 "MultiOrList textification not implemented",
842 ))
843 ),
844 RexType::Cast(c) => c.textify(ctx, w),
845 RexType::Subquery(_s) => write!(
846 w,
847 "{}",
848 ctx.failure(PlanError::unimplemented(
849 "RexType",
850 Some("Subquery"),
851 "Subquery textification not implemented",
852 ))
853 ),
854 RexType::Nested(_n) => write!(
855 w,
856 "{}",
857 ctx.failure(PlanError::unimplemented(
858 "RexType",
859 Some("Nested"),
860 "Nested textification not implemented",
861 ))
862 ),
863 RexType::DynamicParameter(_d) => write!(
864 w,
865 "{}",
866 ctx.failure(PlanError::unimplemented(
867 "RexType",
868 Some("DynamicParameter"),
869 "DynamicParameter textification not implemented",
870 ))
871 ),
872 #[allow(deprecated)]
873 RexType::Enum(_) => write!(
874 w,
875 "{}",
876 ctx.failure(PlanError::unimplemented(
877 "RexType",
878 Some("Enum"),
879 "Enum textification not implemented",
880 ))
881 ),
882 RexType::Lambda(_) => write!(
883 w,
884 "{}",
885 ctx.failure(PlanError::unimplemented(
886 "RexType",
887 Some("Lambda"),
888 "Lambda textification not implemented",
889 ))
890 ),
891 RexType::LambdaInvocation(_) => write!(
892 w,
893 "{}",
894 ctx.failure(PlanError::unimplemented(
895 "RexType",
896 Some("LambdaInvocation"),
897 "LambdaInvocation textification not implemented",
898 ))
899 ),
900 }
901 }
902}
903
904impl Textify for Expression {
905 fn name() -> &'static str {
906 "Expression"
907 }
908
909 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
910 write!(w, "{}", ctx.expect(self.rex_type.as_ref()))
911 }
912}
913
914impl Textify for AggregateFunction {
915 fn name() -> &'static str {
916 "AggregateFunction"
917 }
918
919 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
920 let invocation = FunctionInvocation::from(self);
921 let output_type = OutputType(self.output_type.as_ref());
922
923 let invocation = ctx.display(&invocation);
924 let output_type = ctx.display(&output_type);
925
926 write!(w, "{invocation}{output_type}")
927 }
928}
929
930#[cfg(test)]
931mod tests {
932 use substrait::proto::Type;
933 use substrait::proto::expression::{cast, field_reference, if_then};
934 use substrait::proto::r#type::{Boolean, I16, I32, I64, Kind, Nullability, UserDefined};
935
936 use super::*;
937 use crate::extensions::simple::{ExtensionKind, MissingReference};
938 use crate::fixtures::TestContext;
939 use crate::textify::foundation::{FormatError, FormatErrorType};
940
941 fn literal_bool(value: bool) -> Expression {
942 Expression {
943 rex_type: Some(RexType::Literal(expr::Literal {
944 nullable: false,
945 type_variation_reference: 0,
946 literal_type: Some(expr::literal::LiteralType::Boolean(value)),
947 })),
948 }
949 }
950
951 fn non_nullable_literal(lit: expr::literal::LiteralType) -> expr::Literal {
952 expr::Literal {
953 nullable: false,
954 type_variation_reference: 0,
955 literal_type: Some(lit),
956 }
957 }
958
959 #[test]
960 fn test_literal_textify() {
961 let ctx = TestContext::new();
962
963 let literal = non_nullable_literal(LiteralType::Boolean(true));
964 assert_eq!(ctx.textify_no_errors(&literal), "true");
965 }
966
967 fn interval_day_literal(
968 days: i32,
969 seconds: i32,
970 subseconds: i64,
971 precision: i32,
972 ) -> expr::Literal {
973 non_nullable_literal(LiteralType::IntervalDayToSecond(
974 expr::literal::IntervalDayToSecond {
975 days,
976 seconds,
977 subseconds,
978 precision_mode: Some(PrecisionMode::Precision(precision)),
979 },
980 ))
981 }
982
983 #[test]
984 fn test_interval_day_literal_textify() {
985 let ctx = TestContext::new();
986
987 for (literal, expected) in [
988 (interval_day_literal(5, 0, 0, 6), "'5d':interval_day<6>"),
991 (interval_day_literal(5, 0, 0, 0), "'5d':interval_day<0>"),
992 (interval_day_literal(4, 5, 0, 9), "'4d 5s':interval_day<9>"),
993 (
994 interval_day_literal(0, 0, 123, 3),
995 "'123ms':interval_day<3>",
996 ),
997 (
998 interval_day_literal(0, 0, 123_456, 6),
999 "'123456us':interval_day<6>",
1000 ),
1001 (
1002 interval_day_literal(5, 3, 123_456_789, 9),
1003 "'5d 3s 123456789ns':interval_day<9>",
1004 ),
1005 (
1006 interval_day_literal(0, 0, -500, 12),
1007 "'-500ps':interval_day<12>",
1008 ),
1009 (interval_day_literal(0, 0, 0, 0), "'0s':interval_day<0>"),
1011 (interval_day_literal(0, 0, 0, 9), "'0s':interval_day<9>"),
1012 ] {
1013 assert_eq!(ctx.textify_no_errors(&literal), expected);
1014 }
1015 }
1016
1017 #[test]
1018 fn test_interval_day_literal_textify_nullable() {
1019 let ctx = TestContext::new();
1020 let literal = nullable_literal(LiteralType::IntervalDayToSecond(
1021 expr::literal::IntervalDayToSecond {
1022 days: 5,
1023 seconds: 0,
1024 subseconds: 0,
1025 precision_mode: Some(PrecisionMode::Precision(6)),
1026 },
1027 ));
1028 assert_eq!(ctx.textify_no_errors(&literal), "'5d':interval_day?<6>");
1030 }
1031
1032 #[test]
1033 fn test_interval_day_literal_textify_precision_modes() {
1034 let ctx = TestContext::new();
1035
1036 #[allow(deprecated)]
1040 let deprecated_microseconds = non_nullable_literal(LiteralType::IntervalDayToSecond(
1041 expr::literal::IntervalDayToSecond {
1042 days: 5,
1043 seconds: 0,
1044 subseconds: 0,
1045 precision_mode: Some(PrecisionMode::Microseconds(123)),
1046 },
1047 ));
1048 assert_eq!(
1049 ctx.textify_no_errors(&deprecated_microseconds),
1050 "'5d 123us':interval_day<6>"
1051 );
1052
1053 let unset = non_nullable_literal(LiteralType::IntervalDayToSecond(
1054 expr::literal::IntervalDayToSecond {
1055 days: 5,
1056 seconds: 0,
1057 subseconds: 123,
1058 precision_mode: None,
1059 },
1060 ));
1061 assert_eq!(ctx.textify_no_errors(&unset), "'5d 123us':interval_day<6>");
1062 }
1063
1064 #[test]
1065 fn test_interval_day_literal_textify_invalid_precision_is_error() {
1066 let ctx = TestContext::new();
1067 for precision in [4, 13, -1] {
1071 let literal = interval_day_literal(0, 0, 2, precision);
1072
1073 let (text, errors) = ctx.textify(&literal);
1074
1075 assert_eq!(text, format!("!{{LiteralType}}:interval_day<{precision}>"));
1076 match errors.first() {
1077 FormatError::Format(e) => {
1078 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1079 assert_eq!(e.lookup.as_deref(), Some("IntervalDayToSecond.precision"));
1080 }
1081 other => panic!("expected FormatError::Format, got: {other:?}"),
1082 }
1083 }
1084 }
1085
1086 #[test]
1087 fn test_interval_day_literal_textify_subseconds_at_precision_zero_is_error() {
1088 let ctx = TestContext::new();
1089 let literal = interval_day_literal(0, 0, 2, 0);
1092
1093 let (text, errors) = ctx.textify(&literal);
1094
1095 assert_eq!(text, "!{LiteralType}:interval_day<0>");
1096 match errors.first() {
1097 FormatError::Format(e) => {
1098 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1099 assert_eq!(e.lookup.as_deref(), Some("IntervalDayToSecond.subseconds"));
1100 }
1101 other => panic!("expected FormatError::Format, got: {other:?}"),
1102 }
1103 }
1104
1105 #[test]
1108 fn test_interval_day_literal_textify_out_of_range_is_written_with_warning() {
1109 let ctx = TestContext::new();
1110
1111 for (literal, expected, field) in [
1112 (
1114 interval_day_literal(3_650_001, 0, 0, 0),
1115 "'3650001d':interval_day<0>",
1116 "IntervalDayToSecond.days",
1117 ),
1118 (
1120 interval_day_literal(3_649_999, 100_000, 0, 0),
1121 "'3649999d 100000s':interval_day<0>",
1122 "IntervalDayToSecond.days",
1123 ),
1124 (
1126 interval_day_literal(0, 0, 1_000_000_000, 9),
1127 "'1000000000ns':interval_day<9>",
1128 "IntervalDayToSecond.subseconds",
1129 ),
1130 ] {
1131 let (text, errors) = ctx.textify(&literal);
1132
1133 assert_eq!(text, expected);
1134 match errors.first() {
1135 FormatError::Format(e) => {
1136 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1137 assert_eq!(e.lookup.as_deref(), Some(field));
1138 }
1139 other => panic!("expected FormatError::Format, got: {other:?}"),
1140 }
1141 }
1142 }
1143
1144 fn nullable_literal(lit: expr::literal::LiteralType) -> expr::Literal {
1145 expr::Literal {
1146 nullable: true,
1147 type_variation_reference: 0,
1148 literal_type: Some(lit),
1149 }
1150 }
1151
1152 #[test]
1153 fn test_nullable_boolean_literal_textify() {
1154 let ctx = TestContext::new();
1155 assert_eq!(
1156 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Boolean(true))),
1157 "true:boolean?"
1158 );
1159 assert_eq!(
1160 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Boolean(
1161 false
1162 ))),
1163 "false:boolean?"
1164 );
1165 }
1166
1167 #[test]
1168 fn test_nullable_integer_literal_textify() {
1169 let ctx = TestContext::new();
1170 assert_eq!(
1171 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::I32(78))),
1172 "78:i32?"
1173 );
1174 assert_eq!(
1175 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::I64(42))),
1176 "42:i64?"
1177 );
1178 }
1179
1180 #[test]
1181 fn test_nullable_float_literal_textify() {
1182 let ctx = TestContext::new();
1183 assert_eq!(
1184 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Fp32(2.5))),
1185 "2.5:fp32?"
1186 );
1187 assert_eq!(
1188 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Fp64(3.19))),
1189 "3.19:fp64?"
1190 );
1191 }
1192
1193 #[test]
1194 fn test_precision_timestamp_to_string() {
1195 assert_eq!(
1196 precision_timestamp_to_string(10, 0),
1197 Ok("1970-01-01T00:00:10".to_string())
1198 );
1199 assert_eq!(
1200 precision_timestamp_to_string(123_456_789, 9),
1201 Ok("1970-01-01T00:00:00.123456789".to_string())
1202 );
1203 assert_eq!(
1206 precision_timestamp_to_string(123_456_789_500, 12),
1207 Ok("1970-01-01T00:00:00.123456789".to_string())
1208 );
1209 assert_eq!(
1210 precision_timestamp_to_string(0, 13),
1211 Err(PrecisionFormatError::UnsupportedPrecision)
1212 );
1213 }
1214
1215 #[test]
1216 fn test_precision_time_to_string() {
1217 assert_eq!(precision_time_to_string(0, 0), Ok("00:00:00".to_string()));
1218 assert_eq!(
1219 precision_time_to_string(3_661_000_000, 6),
1222 Ok("01:01:01.000000".to_string())
1223 );
1224 assert_eq!(
1225 precision_time_to_string(3_661_000_000_000_500, 12),
1228 Ok("01:01:01.000000000".to_string())
1229 );
1230 assert_eq!(
1231 precision_time_to_string(0, 13),
1232 Err(PrecisionFormatError::UnsupportedPrecision)
1233 );
1234 assert_eq!(
1238 precision_time_to_string(-1, 12),
1239 Err(PrecisionFormatError::OutOfRange)
1240 );
1241 }
1242
1243 #[test]
1244 fn test_nullable_precision_timestamp_literal_textify() {
1245 let ctx = TestContext::new();
1246 assert_eq!(
1247 ctx.textify_no_errors(&nullable_literal(
1248 expr::literal::LiteralType::PrecisionTimestamp(expr::literal::PrecisionTimestamp {
1249 precision: 6,
1250 value: 1000,
1251 })
1252 )),
1253 "'1970-01-01T00:00:00.001000':precisiontimestamp?<6>"
1254 );
1255 assert_eq!(
1256 ctx.textify_no_errors(&nullable_literal(
1257 expr::literal::LiteralType::PrecisionTimestampTz(
1258 expr::literal::PrecisionTimestamp {
1259 precision: 3,
1260 value: 5,
1261 }
1262 )
1263 )),
1264 "'1970-01-01T00:00:00.005':precisiontimestamptz?<3>"
1265 );
1266 assert_eq!(
1267 ctx.textify_no_errors(&nullable_literal(
1268 expr::literal::LiteralType::PrecisionTime(expr::literal::PrecisionTime {
1269 precision: 0,
1270 value: 61,
1271 })
1272 )),
1273 "'00:01:01':precisiontime?<0>"
1274 );
1275 }
1276
1277 #[test]
1278 fn test_precision_time_literal_precision_12_best_effort() {
1279 let ctx = TestContext::new();
1280 let (s, errs) = ctx.textify(&non_nullable_literal(
1281 expr::literal::LiteralType::PrecisionTime(expr::literal::PrecisionTime {
1282 precision: 12,
1283 value: 3_661_000_000_000_500,
1284 }),
1285 ));
1286 assert_eq!(s, "'01:01:01.000000000':precisiontime<12>");
1289 assert_eq!(errs.0.len(), 1);
1290 assert!(errs.0[0].to_string().contains("truncated"));
1291 }
1292
1293 #[test]
1294 fn test_precision_timestamp_literal_supported_precision_no_warning() {
1295 let ctx = TestContext::new();
1296 let (_, errs) = ctx.textify(&non_nullable_literal(
1299 expr::literal::LiteralType::PrecisionTimestamp(expr::literal::PrecisionTimestamp {
1300 precision: 9,
1301 value: 123_456_789,
1302 }),
1303 ));
1304 assert_eq!(errs.0.len(), 0);
1305 }
1306
1307 #[test]
1308 fn test_precision_timestamp_literal_unrecognized_precision_invalid() {
1309 let ctx = TestContext::new();
1310 let (s, errs) = ctx.textify(&non_nullable_literal(
1311 expr::literal::LiteralType::PrecisionTimestamp(expr::literal::PrecisionTimestamp {
1312 precision: 13,
1313 value: 0,
1314 }),
1315 ));
1316 assert_eq!(s, "!{LiteralType}:precisiontimestamp<13>");
1319 assert_eq!(errs.0.len(), 1);
1320 assert!(errs.0[0].to_string().contains("PrecisionTimestamp"));
1321 }
1322
1323 #[test]
1324 fn test_precision_timestamp_literal_out_of_range_does_not_panic() {
1325 let ctx = TestContext::new();
1326 let (s, errs) = ctx.textify(&non_nullable_literal(
1331 expr::literal::LiteralType::PrecisionTimestamp(expr::literal::PrecisionTimestamp {
1332 precision: 0,
1333 value: 9_000_000_000_000,
1334 }),
1335 ));
1336 assert_eq!(s, "!{LiteralType}:precisiontimestamp<0>");
1337 assert_eq!(errs.0.len(), 1);
1338 }
1339
1340 #[test]
1341 fn test_precision_time_literal_beyond_one_day_invalid() {
1342 let ctx = TestContext::new();
1343 let (s, errs) = ctx.textify(&non_nullable_literal(
1347 expr::literal::LiteralType::PrecisionTime(expr::literal::PrecisionTime {
1348 precision: 0,
1349 value: 86_460,
1350 }),
1351 ));
1352 assert_eq!(s, "!{LiteralType}:precisiontime<0>");
1353 assert_eq!(errs.0.len(), 1);
1354 }
1355
1356 #[test]
1357 fn test_precision_time_literal_unrecognized_precision_invalid() {
1358 let ctx = TestContext::new();
1359 let (s, errs) = ctx.textify(&non_nullable_literal(
1360 expr::literal::LiteralType::PrecisionTime(expr::literal::PrecisionTime {
1361 precision: 13,
1362 value: 0,
1363 }),
1364 ));
1365 assert_eq!(s, "!{LiteralType}:precisiontime<13>");
1366 assert_eq!(errs.0.len(), 1);
1367 assert!(errs.0[0].to_string().contains("PrecisionTime"));
1368 }
1369
1370 #[test]
1371 fn test_nullable_precision_timestamp_literal_precision_12_best_effort() {
1372 let ctx = TestContext::new();
1373 let (s, errs) = ctx.textify(&nullable_literal(
1376 expr::literal::LiteralType::PrecisionTimestamp(expr::literal::PrecisionTimestamp {
1377 precision: 12,
1378 value: 123_456_789_500,
1379 }),
1380 ));
1381 assert_eq!(s, "'1970-01-01T00:00:00.123456789':precisiontimestamp?<12>");
1384 assert_eq!(errs.0.len(), 1);
1385 assert!(errs.0[0].to_string().contains("truncated"));
1386 }
1387
1388 #[test]
1389 fn test_expression_textify() {
1390 let ctx = TestContext::new();
1391
1392 let expr_empty = Expression { rex_type: None }; let (s, errs) = ctx.textify(&expr_empty);
1395 assert!(!errs.is_empty());
1396 assert_eq!(s, "!{RexType}");
1397
1398 let expr_lit = Expression {
1400 rex_type: Some(RexType::Literal(expr::Literal {
1401 nullable: false,
1402 type_variation_reference: 0,
1403 literal_type: Some(expr::literal::LiteralType::Boolean(true)),
1404 })),
1405 };
1406 assert_eq!(ctx.textify_no_errors(&expr_lit), "true");
1407 }
1408
1409 #[test]
1410 fn test_rextype_textify() {
1411 let ctx = TestContext::new();
1412
1413 let func = RexType::ScalarFunction(ScalarFunction {
1414 function_reference: 1000, arguments: vec![],
1416 options: vec![],
1417 output_type: Some(Type {
1418 kind: Some(Kind::I64(I64 {
1419 nullability: Nullability::Required as i32,
1420 type_variation_reference: 0,
1421 })),
1422 }),
1423 #[allow(deprecated)]
1424 args: vec![],
1425 });
1426 let (s, errq) = ctx.textify(&func);
1427 let errs: Vec<_> = errq.0;
1428 match errs[0] {
1429 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => {
1430 assert_eq!(k, ExtensionKind::Function);
1431 assert_eq!(a, 1000);
1432 }
1433 _ => panic!("Expected Lookup MissingAnchor: {}", errs[0]),
1434 }
1435 assert_eq!(s, "!{function}#1000():i64");
1436
1437 let ctx = ctx.with_urn(1, "first").with_function(1, 100, "first");
1438 let func = RexType::ScalarFunction(ScalarFunction {
1439 function_reference: 100,
1440 arguments: vec![],
1441 options: vec![],
1442 output_type: Some(Type {
1443 kind: Some(Kind::I64(I64 {
1444 nullability: Nullability::Required as i32,
1445 type_variation_reference: 0,
1446 })),
1447 }),
1448 #[allow(deprecated)]
1449 args: vec![],
1450 });
1451 let s = ctx.textify_no_errors(&func);
1452 assert_eq!(s, "first():i64");
1453
1454 let options_show_anchor = Default::default();
1456
1457 let ctx = TestContext::new()
1458 .with_options(options_show_anchor)
1459 .with_urn(1, "somewhere_on_the_internet")
1460 .with_urn(2, "somewhere_else")
1461 .with_function(1, 231, "duplicated")
1462 .with_function(2, 232, "duplicated");
1463
1464 let rex_dup = RexType::ScalarFunction(ScalarFunction {
1465 function_reference: 231,
1466 arguments: vec![FunctionArgument {
1467 arg_type: Some(ArgType::Value(Expression {
1468 rex_type: Some(RexType::Literal(expr::Literal {
1469 nullable: false,
1470 type_variation_reference: 0,
1471 literal_type: Some(expr::literal::LiteralType::Boolean(true)),
1472 })),
1473 })),
1474 }],
1475 options: vec![],
1476 output_type: Some(Type {
1477 kind: Some(Kind::Bool(Boolean {
1478 nullability: Nullability::Required as i32,
1479 type_variation_reference: 0,
1480 })),
1481 }),
1482 #[allow(deprecated)]
1483 args: vec![],
1484 });
1485 let s = ctx.textify_no_errors(&rex_dup);
1486 assert_eq!(s, "duplicated#231(true):boolean");
1487 }
1488
1489 #[test]
1490 fn test_ifthen_textify() {
1491 let ctx = TestContext::new();
1492
1493 let if_then = IfThen {
1494 ifs: vec![
1495 if_then::IfClause {
1496 r#if: Some(literal_bool(true)),
1497 then: Some(literal_bool(false)),
1498 },
1499 if_then::IfClause {
1500 r#if: Some(literal_bool(false)),
1501 then: Some(literal_bool(true)),
1502 },
1503 ],
1504 r#else: Some(Box::new(literal_bool(true))),
1505 };
1506
1507 let s = ctx.textify_no_errors(&if_then);
1508 assert_eq!(s, "if_then(true -> false, false -> true, _ -> true)");
1509 }
1510
1511 #[test]
1512 fn test_ifthen_textify_missing_else() {
1513 let ctx = TestContext::new();
1514
1515 let if_then = IfThen {
1516 ifs: vec![if_then::IfClause {
1517 r#if: Some(literal_bool(true)),
1518 then: Some(literal_bool(false)),
1519 }],
1520 r#else: None,
1521 };
1522
1523 let (s, errs) = ctx.textify(&if_then);
1524 assert_eq!(s, "if_then(true -> false, _ -> !{Expression})");
1525 assert_eq!(errs.0.len(), 1);
1526 }
1527
1528 fn make_i32_type() -> Type {
1529 Type {
1530 kind: Some(Kind::I32(I32 {
1531 nullability: Nullability::Required as i32,
1532 type_variation_reference: 0,
1533 })),
1534 }
1535 }
1536
1537 fn make_i16_type() -> Type {
1538 Type {
1539 kind: Some(Kind::I16(I16 {
1540 nullability: Nullability::Required as i32,
1541 type_variation_reference: 0,
1542 })),
1543 }
1544 }
1545
1546 fn literal_i32(value: i32) -> Expression {
1547 Expression {
1548 rex_type: Some(RexType::Literal(expr::Literal {
1549 nullable: false,
1550 type_variation_reference: 0,
1551 literal_type: Some(expr::literal::LiteralType::I32(value)),
1552 })),
1553 }
1554 }
1555
1556 #[test]
1557 fn test_cast_textify() {
1558 let ctx = TestContext::new();
1559 let cast = Cast {
1560 r#type: Some(make_i16_type()),
1561 input: Some(Box::new(literal_i32(78))),
1562 failure_behavior: 0,
1563 };
1564 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::i16");
1565 }
1566
1567 #[test]
1568 fn test_cast_textify_via_rextype() {
1569 let ctx = TestContext::new();
1570 let rex = RexType::Cast(Box::new(Cast {
1571 r#type: Some(make_i16_type()),
1572 input: Some(Box::new(literal_i32(78))),
1573 failure_behavior: 0,
1574 }));
1575 assert_eq!(ctx.textify_no_errors(&rex), "(78:i32)::i16");
1576 }
1577
1578 #[test]
1579 fn test_cast_textify_nested() {
1580 let ctx = TestContext::new();
1582 let inner_cast = Expression {
1583 rex_type: Some(RexType::Cast(Box::new(Cast {
1584 r#type: Some(make_i16_type()),
1585 input: Some(Box::new(literal_i32(78))),
1586 failure_behavior: 0,
1587 }))),
1588 };
1589 let outer_cast = Cast {
1590 r#type: Some(make_i32_type()),
1591 input: Some(Box::new(inner_cast)),
1592 failure_behavior: 0,
1593 };
1594 assert_eq!(ctx.textify_no_errors(&outer_cast), "((78:i32)::i16)::i32");
1595 }
1596
1597 #[test]
1598 fn test_cast_textify_return_null() {
1599 let ctx = TestContext::new();
1600 let cast = Cast {
1601 r#type: Some(make_i16_type()),
1602 input: Some(Box::new(literal_i32(78))),
1603 failure_behavior: cast::FailureBehavior::ReturnNull as i32,
1604 };
1605 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::?i16");
1606 }
1607
1608 #[test]
1609 fn test_cast_textify_throw_exception() {
1610 let ctx = TestContext::new();
1611 let cast = Cast {
1612 r#type: Some(make_i16_type()),
1613 input: Some(Box::new(literal_i32(78))),
1614 failure_behavior: cast::FailureBehavior::ThrowException as i32,
1615 };
1616 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::!i16");
1617 }
1618
1619 #[test]
1620 fn test_cast_textify_missing_input() {
1621 let ctx = TestContext::new();
1622 let cast = Cast {
1623 r#type: Some(make_i16_type()),
1624 input: None,
1625 failure_behavior: 0,
1626 };
1627 let (s, errs) = ctx.textify(&cast);
1628 assert_eq!(s, "(!{Expression})::i16");
1629 match &errs.0[0] {
1630 FormatError::Format(e) => {
1631 assert_eq!(e.message, "Expression");
1632 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1633 }
1634 other => panic!("Expected Format(InvalidValue) for missing input, got: {other}"),
1635 }
1636 }
1637
1638 #[test]
1639 fn test_cast_textify_missing_type() {
1640 let ctx = TestContext::new();
1641 let cast = Cast {
1642 r#type: None,
1643 input: Some(Box::new(literal_i32(78))),
1644 failure_behavior: 0,
1645 };
1646 let (s, errs) = ctx.textify(&cast);
1647 assert_eq!(s, "(78:i32)::!{Type}");
1648 match &errs.0[0] {
1649 FormatError::Format(e) => {
1650 assert_eq!(e.message, "Type");
1651 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1652 }
1653 other => panic!("Expected Format(InvalidValue) for missing type, got: {other}"),
1654 }
1655 }
1656
1657 fn struct_field_reference(field: i32) -> FieldReference {
1658 FieldReference {
1659 reference_type: Some(ReferenceType::DirectReference(ReferenceSegment {
1660 reference_type: Some(reference_segment::ReferenceType::StructField(Box::new(
1661 reference_segment::StructField { field, child: None },
1662 ))),
1663 })),
1664 root_type: Some(RootType::RootReference(RootReference {})),
1665 }
1666 }
1667
1668 #[test]
1669 fn test_field_reference_missing_root_type() {
1670 let ctx = TestContext::new();
1671 let mut fr = struct_field_reference(3);
1672 fr.root_type = None;
1673 let (s, errs) = ctx.textify(&fr);
1674 assert_eq!(s, "!{FieldReference}");
1675 match &errs.0[0] {
1676 FormatError::Format(e) => {
1677 assert_eq!(e.message, "FieldReference");
1678 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1679 }
1680 other => panic!("Expected Format(InvalidValue) for missing root_type, got: {other}"),
1681 }
1682 }
1683
1684 #[test]
1685 fn test_field_reference_root_reference() {
1686 let ctx = TestContext::new();
1687 let fr = struct_field_reference(3);
1688 assert_eq!(ctx.textify_no_errors(&fr), "$3");
1689 }
1690
1691 #[test]
1692 fn test_field_reference_outer_reference_unimplemented() {
1693 let ctx = TestContext::new();
1694 let mut fr = struct_field_reference(3);
1695 fr.root_type = Some(RootType::OuterReference(field_reference::OuterReference {
1696 steps_out: 1,
1697 }));
1698 let (s, errs) = ctx.textify(&fr);
1699 assert_eq!(s, "!{FieldReference}");
1700 match &errs.0[0] {
1701 FormatError::Format(e) => {
1702 assert_eq!(e.message, "FieldReference");
1703 assert_eq!(e.error_type, FormatErrorType::Unimplemented);
1704 }
1705 other => panic!("Expected Format(Unimplemented) for OuterReference, got: {other}"),
1706 }
1707 }
1708
1709 #[test]
1710 fn test_field_reference_expression_unimplemented() {
1711 let ctx = TestContext::new();
1712 let mut fr = struct_field_reference(3);
1713 fr.root_type = Some(RootType::Expression(Box::new(literal_bool(true))));
1714 let (s, errs) = ctx.textify(&fr);
1715 assert_eq!(s, "!{FieldReference}");
1716 match &errs.0[0] {
1717 FormatError::Format(e) => {
1718 assert_eq!(e.message, "FieldReference");
1719 assert_eq!(e.error_type, FormatErrorType::Unimplemented);
1720 }
1721 other => panic!("Expected Format(Unimplemented) for Expression, got: {other}"),
1722 }
1723 }
1724
1725 #[test]
1726 fn test_cast_textify_invalid_failure_behavior() {
1727 let ctx = TestContext::new();
1728 let cast = Cast {
1729 r#type: Some(make_i16_type()),
1730 input: Some(Box::new(literal_i32(78))),
1731 failure_behavior: 99,
1732 };
1733 let (s, errs) = ctx.textify(&cast);
1734 assert_eq!(s, "(78:i32)::!{Cast}i16");
1736 match &errs.0[0] {
1737 FormatError::Format(e) => {
1738 assert_eq!(e.message, "Cast");
1739 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1740 }
1741 other => {
1742 panic!("Expected Format(InvalidValue) for invalid failure_behavior, got: {other}")
1743 }
1744 }
1745 }
1746
1747 #[test]
1748 fn test_cast_to_user_defined_type_textifies_without_u_prefix() {
1749 let ctx = TestContext::new()
1751 .with_urn(1, "urn:example:types")
1752 .with_type(1, 5, "u!json");
1753 let cast = Cast {
1754 r#type: Some(Type {
1755 kind: Some(Kind::UserDefined(UserDefined {
1756 type_variation_reference: 0,
1757 nullability: Nullability::Required as i32,
1758 type_reference: 5,
1759 type_parameters: vec![],
1760 })),
1761 }),
1762 input: Some(Box::new(literal_i32(1))),
1763 failure_behavior: 0,
1764 };
1765 assert_eq!(ctx.textify_no_errors(&cast), "(1:i32)::json");
1766 }
1767}