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::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
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61enum PrecisionFormatError {
62 UnsupportedPrecision,
64 OutOfRange,
67}
68
69impl PrecisionFormatError {
70 fn into_plan_error(self, variant: &'static str, precision: i32) -> PlanError {
71 let message = match self {
72 PrecisionFormatError::UnsupportedPrecision => {
73 format!("unsupported precision {precision} for {variant}")
74 }
75 PrecisionFormatError::OutOfRange => {
76 format!("value is out of range for {variant} at precision {precision}")
77 }
78 };
79 PlanError::invalid("LiteralType", Some(variant), message)
80 }
81}
82
83fn picosecond_truncation_warning(variant: &'static str) -> PlanError {
85 PlanError::invalid(
86 "LiteralType",
87 Some(variant),
88 "precision 12 (picoseconds) truncated to nanoseconds; sub-nanosecond precision lost",
89 )
90}
91
92fn write_precision_literal<S: Scope, W: fmt::Write>(
93 variant: &'static str,
94 precision: i32,
95 formatted: Result<String, PrecisionFormatError>,
96 ctx: &S,
97 w: &mut W,
98) -> fmt::Result {
99 match formatted {
100 Ok(s) => {
101 if precision == 12 {
102 ctx.push_error(picosecond_truncation_warning(variant).into());
103 }
104 write!(w, "'{}'", escaped(&s))
105 }
106 Err(e) => write!(w, "{}", ctx.failure(e.into_plan_error(variant, precision))),
107 }
108}
109
110pub fn textify_enum<S: Scope, W: fmt::Write>(s: &str, _ctx: &S, w: &mut W) -> fmt::Result {
113 write!(w, "&{}", Name(s))
114}
115
116fn days_to_date_string(days: i32) -> String {
118 let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
119 let date = epoch + chrono::Duration::days(days as i64);
120 date.format("%Y-%m-%d").to_string()
121}
122
123fn duration_from_precision_units(
126 value: i64,
127 precision: i32,
128) -> Result<chrono::Duration, PrecisionFormatError> {
129 match precision {
130 0 => chrono::Duration::try_seconds(value).ok_or(PrecisionFormatError::OutOfRange),
131 3 => chrono::Duration::try_milliseconds(value).ok_or(PrecisionFormatError::OutOfRange),
132 6 => Ok(chrono::Duration::microseconds(value)),
133 9 => Ok(chrono::Duration::nanoseconds(value)),
134 12 => Ok(chrono::Duration::nanoseconds(value / 1000)),
135 _ => Err(PrecisionFormatError::UnsupportedPrecision),
136 }
137}
138
139fn fractional_spec(precision: i32) -> &'static str {
144 match precision {
145 3 => "%.3f",
146 6 => "%.6f",
147 9 | 12 => "%.9f",
148 _ => "", }
150}
151
152fn precision_timestamp_to_string(
155 value: i64,
156 precision: i32,
157) -> Result<String, PrecisionFormatError> {
158 let duration = duration_from_precision_units(value, precision)?;
159 let epoch = DateTime::from_timestamp(0, 0).unwrap().naive_utc();
160 let datetime = epoch
161 .checked_add_signed(duration)
162 .ok_or(PrecisionFormatError::OutOfRange)?;
163
164 let format = format!("%Y-%m-%dT%H:%M:%S{}", fractional_spec(precision));
165 Ok(datetime.format(&format).to_string())
166}
167
168fn precision_time_to_string(value: i64, precision: i32) -> Result<String, PrecisionFormatError> {
177 if value < 0 {
178 return Err(PrecisionFormatError::OutOfRange);
179 }
180 let duration = duration_from_precision_units(value, precision)?;
181 if duration >= chrono::Duration::days(1) {
182 return Err(PrecisionFormatError::OutOfRange);
183 }
184 let midnight = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
185 let time = midnight + duration;
186
187 let format = format!("%H:%M:%S{}", fractional_spec(precision));
188 Ok(time.format(&format).to_string())
189}
190
191fn write_literal_value<S: Scope, W: fmt::Write>(
196 lit: &LiteralType,
197 ctx: &S,
198 w: &mut W,
199) -> fmt::Result {
200 match lit {
201 LiteralType::Boolean(b) => write!(w, "{b}"),
202 LiteralType::I8(i) | LiteralType::I16(i) | LiteralType::I32(i) => write!(w, "{i}"),
203 LiteralType::I64(i) => write!(w, "{i}"),
204 LiteralType::Fp32(f) => write!(w, "{f}"),
205 LiteralType::Fp64(f) => write!(w, "{f}"),
206 LiteralType::String(s) => write!(w, "'{}'", s.escape_debug()),
207 LiteralType::Binary(items) => textify_binary(items, ctx, w),
208 LiteralType::Date(days) => {
209 write!(w, "'{}'", escaped(&days_to_date_string(*days)))
210 }
211 #[allow(deprecated)]
212 LiteralType::Time(microseconds) => write_precision_literal(
213 "Time",
214 6,
215 precision_time_to_string(*microseconds, 6),
216 ctx,
217 w,
218 ),
219 #[allow(deprecated)]
220 LiteralType::Timestamp(microseconds) => write_precision_literal(
221 "Timestamp",
222 6,
223 precision_timestamp_to_string(*microseconds, 6),
224 ctx,
225 w,
226 ),
227 LiteralType::IntervalYearToMonth(_) => unimplemented_literal("IntervalYearToMonth", ctx, w),
228 LiteralType::IntervalDayToSecond(_) => unimplemented_literal("IntervalDayToSecond", ctx, w),
229 LiteralType::IntervalCompound(_) => unimplemented_literal("IntervalCompound", ctx, w),
230 LiteralType::FixedChar(_) => unimplemented_literal("FixedChar", ctx, w),
231 LiteralType::VarChar(_) => unimplemented_literal("VarChar", ctx, w),
232 LiteralType::FixedBinary(_) => unimplemented_literal("FixedBinary", ctx, w),
233 LiteralType::Decimal(_) => unimplemented_literal("Decimal", ctx, w),
234 LiteralType::PrecisionTime(p) => write_precision_literal(
235 "PrecisionTime",
236 p.precision,
237 precision_time_to_string(p.value, p.precision),
238 ctx,
239 w,
240 ),
241 LiteralType::PrecisionTimestamp(p) => write_precision_literal(
242 "PrecisionTimestamp",
243 p.precision,
244 precision_timestamp_to_string(p.value, p.precision),
245 ctx,
246 w,
247 ),
248 LiteralType::PrecisionTimestampTz(p) => write_precision_literal(
249 "PrecisionTimestampTz",
250 p.precision,
251 precision_timestamp_to_string(p.value, p.precision),
252 ctx,
253 w,
254 ),
255 LiteralType::Struct(_) => unimplemented_literal("Struct", ctx, w),
256 LiteralType::Map(_) => unimplemented_literal("Map", ctx, w),
257 #[allow(deprecated)]
258 LiteralType::TimestampTz(_) => unimplemented_literal("TimestampTz", ctx, w),
259 LiteralType::Uuid(_) => unimplemented_literal("Uuid", ctx, w),
260 LiteralType::Null(_) => write!(w, "null"),
261 LiteralType::List(_) => unimplemented_literal("List", ctx, w),
262 LiteralType::EmptyList(_) => unimplemented_literal("EmptyList", ctx, w),
263 LiteralType::EmptyMap(_) => unimplemented_literal("EmptyMap", ctx, w),
264 LiteralType::UserDefined(_) => unimplemented_literal("UserDefined", ctx, w),
265 }
266}
267
268fn write_literal_type_suffix<W: fmt::Write>(
272 lit: &LiteralType,
273 nullable: bool,
274 w: &mut W,
275) -> fmt::Result {
276 let (name, precision): (&'static str, Option<i32>) = match lit {
278 LiteralType::Boolean(_) => ("boolean", None),
279 LiteralType::I8(_) => ("i8", None),
280 LiteralType::I16(_) => ("i16", None),
281 LiteralType::I32(_) => ("i32", None),
282 LiteralType::I64(_) => ("i64", None),
283 LiteralType::Fp32(_) => ("fp32", None),
284 LiteralType::Fp64(_) => ("fp64", None),
285 LiteralType::String(_) => ("string", None),
286 LiteralType::Binary(_) => ("binary", None),
287 LiteralType::Date(_) => ("date", None),
288 #[allow(deprecated)]
289 LiteralType::Time(_) => ("time", None),
290 #[allow(deprecated)]
291 LiteralType::Timestamp(_) => ("timestamp", None),
292 LiteralType::PrecisionTimestamp(p) => ("precisiontimestamp", Some(p.precision)),
293 LiteralType::PrecisionTimestampTz(p) => ("precisiontimestamptz", Some(p.precision)),
294 LiteralType::PrecisionTime(p) => ("precisiontime", Some(p.precision)),
295 _ => return Ok(()),
296 };
297
298 write!(w, ":{name}")?;
299 if nullable {
300 write!(w, "?")?;
301 }
302 if let Some(p) = precision {
303 write!(w, "<{p}>")?;
304 }
305 Ok(())
306}
307
308fn is_default_for_syntax(lit: &LiteralType) -> bool {
321 matches!(
322 lit,
323 LiteralType::Boolean(_)
324 | LiteralType::String(_)
325 | LiteralType::Binary(_)
326 | LiteralType::I64(_)
327 | LiteralType::Fp64(_)
328 )
329}
330
331impl Textify for expr::Literal {
332 fn name() -> &'static str {
333 "Literal"
334 }
335
336 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
337 let Some(lit) = self.literal_type.as_ref() else {
338 return write!(
339 w,
340 "{}",
341 ctx.failure(PlanError::invalid(
342 "Literal",
343 Some("literal_type"),
344 "missing literal_type",
345 ))
346 );
347 };
348 write_literal_value(lit, ctx, w)?;
349 let show_suffix = match ctx.options().literal_types {
350 Visibility::Never => false,
351 Visibility::Always => true,
352 Visibility::Required => self.nullable || !is_default_for_syntax(lit),
353 };
354 if let LiteralType::Null(typ) = lit {
355 write!(w, ":{}", ctx.expect(Some(typ)))?;
356 return Ok(());
357 }
358 if show_suffix {
359 write_literal_type_suffix(lit, self.nullable, w)?;
360 }
361 Ok(())
362 }
363}
364
365pub struct Reference(pub i32);
366
367impl fmt::Display for Reference {
368 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369 write!(f, "${}", self.0)
370 }
371}
372
373impl From<Reference> for Expression {
374 fn from(r: Reference) -> Self {
375 Expression {
378 rex_type: Some(RexType::Selection(Box::new(FieldReference {
379 reference_type: Some(ReferenceType::DirectReference(ReferenceSegment {
380 reference_type: Some(reference_segment::ReferenceType::StructField(Box::new(
381 reference_segment::StructField {
382 field: r.0,
383 child: None,
384 },
385 ))),
386 })),
387 root_type: Some(RootType::RootReference(RootReference {})),
388 }))),
389 }
390 }
391}
392
393impl Textify for Reference {
394 fn name() -> &'static str {
395 "Reference"
396 }
397
398 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
399 write!(w, "{self}")
400 }
401}
402
403impl Textify for FieldReference {
404 fn name() -> &'static str {
405 "FieldReference"
406 }
407
408 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
409 match &self.root_type {
410 Some(RootType::RootReference(_)) => {}
411 None => {
412 return write!(
413 w,
414 "{}",
415 ctx.failure(PlanError::invalid(
416 "FieldReference",
417 Some("root_type"),
418 "Required field root_type is missing",
419 ))
420 );
421 }
422 Some(RootType::Expression(_)) => {
423 return write!(
424 w,
425 "{}",
426 ctx.failure(PlanError::unimplemented(
427 "FieldReference",
428 Some("root_type"),
429 "FieldReference textification not implemented for Expression root_type",
430 ))
431 );
432 }
433 Some(RootType::OuterReference(_)) => {
434 return write!(
435 w,
436 "{}",
437 ctx.failure(PlanError::unimplemented(
438 "FieldReference",
439 Some("root_type"),
440 "FieldReference textification not implemented for OuterReference root_type",
441 ))
442 );
443 }
444 Some(RootType::LambdaParameterReference(_)) => {
445 return write!(
446 w,
447 "{}",
448 ctx.failure(PlanError::unimplemented(
449 "FieldReference",
450 Some("root_type"),
451 "FieldReference textification not implemented for LambdaParameterReference root_type",
452 ))
453 );
454 }
455 }
456
457 let ref_type = match &self.reference_type {
458 None => {
459 return write!(
460 w,
461 "{}",
462 ctx.failure(PlanError::invalid(
463 "FieldReference",
464 Some("reference_type"),
465 "Required field reference_type is missing",
466 ))
467 );
468 }
469 Some(ReferenceType::DirectReference(r)) => r,
470 _ => {
471 return write!(
472 w,
473 "{}",
474 ctx.failure(PlanError::unimplemented(
475 "FieldReference",
476 Some("FieldReference"),
477 "FieldReference textification implemented only for StructField",
478 ))
479 );
480 }
481 };
482
483 match &ref_type.reference_type {
484 Some(reference_segment::ReferenceType::StructField(s)) => {
485 write!(w, "{}", Reference(s.field))
486 }
487 None => write!(
488 w,
489 "{}",
490 ctx.failure(PlanError::invalid(
491 "ReferenceSegment",
492 Some("reference_type"),
493 "Required field reference_type is missing",
494 ))
495 ),
496 _ => write!(
497 w,
498 "{}",
499 ctx.failure(PlanError::unimplemented(
500 "ReferenceSegment",
501 Some("reference_type"),
502 "ReferenceSegment textification implemented only for StructField",
503 ))
504 ),
505 }
506 }
507}
508
509#[derive(Debug, Clone, Copy)]
517pub struct FunctionInvocation<'a> {
518 pub function_reference: u32,
519 pub arguments: &'a [FunctionArgument],
520 pub options: &'a [FunctionOption],
521}
522
523impl<'a> From<&'a ScalarFunction> for FunctionInvocation<'a> {
524 fn from(f: &'a ScalarFunction) -> Self {
525 FunctionInvocation {
526 function_reference: f.function_reference,
527 arguments: &f.arguments,
528 options: &f.options,
529 }
530 }
531}
532
533impl<'a> From<&'a AggregateFunction> for FunctionInvocation<'a> {
534 fn from(f: &'a AggregateFunction) -> Self {
535 FunctionInvocation {
536 function_reference: f.function_reference,
537 arguments: &f.arguments,
538 options: &f.options,
539 }
540 }
541}
542
543impl Textify for FunctionInvocation<'_> {
544 fn name() -> &'static str {
545 "FunctionInvocation"
546 }
547
548 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
549 let name_and_anchor =
550 NamedAnchor::lookup(ctx, ExtensionKind::Function, self.function_reference);
551 let name_and_anchor = ctx.display(&name_and_anchor);
552
553 let args = ctx.separated(self.arguments, ", ");
554 let options = ctx.separated(self.options, ", ");
555 let between = if self.arguments.is_empty() || self.options.is_empty() {
556 ""
557 } else {
558 ", "
559 };
560
561 write!(w, "{name_and_anchor}({args}{between}{options})")
562 }
563}
564
565impl Textify for ScalarFunction {
566 fn name() -> &'static str {
567 "ScalarFunction"
568 }
569
570 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
571 let invocation = FunctionInvocation::from(self);
572 let output_type = OutputType(self.output_type.as_ref());
573
574 let invocation = ctx.display(&invocation);
575 let output_type = ctx.display(&output_type);
576
577 write!(w, "{invocation}{output_type}")
578 }
579}
580
581impl Textify for FunctionOption {
582 fn name() -> &'static str {
583 "FunctionOption"
584 }
585
586 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
587 write!(w, "{}⇒[", self.name)?;
588 let mut first = true;
589 for pref in self.preference.iter() {
590 if !first {
591 write!(w, ", ")?;
592 } else {
593 first = false;
594 }
595 write!(w, "{pref}")?;
596 }
597 write!(w, "]")?;
598 Ok(())
599 }
600}
601
602impl Textify for FunctionArgument {
603 fn name() -> &'static str {
604 "FunctionArgument"
605 }
606
607 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
608 write!(w, "{}", ctx.expect(self.arg_type.as_ref()))
609 }
610}
611
612impl Textify for ArgType {
613 fn name() -> &'static str {
614 "ArgType"
615 }
616
617 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
618 match self {
619 ArgType::Type(t) => t.textify(ctx, w),
620 ArgType::Value(v) => v.textify(ctx, w),
621 ArgType::Enum(e) => textify_enum(e, ctx, w),
622 }
623 }
624}
625
626impl Textify for Cast {
627 fn name() -> &'static str {
628 "Cast"
629 }
630
631 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
632 let failure_err;
633 let fb: &dyn fmt::Display = match cast::FailureBehavior::try_from(self.failure_behavior) {
634 Ok(cast::FailureBehavior::Unspecified) => &"",
635 Ok(cast::FailureBehavior::ReturnNull) => &"?",
636 Ok(cast::FailureBehavior::ThrowException) => &"!",
637 Err(_) => {
638 failure_err = ctx.failure(PlanError::invalid(
639 "Cast",
640 Some("failure_behavior"),
641 format!("Unknown failure_behavior value: {}", self.failure_behavior),
642 ));
643 &failure_err
644 }
645 };
646 let input = ctx.expect(self.input.as_deref());
647 let target_type = ctx.expect(self.r#type.as_ref());
648 write!(w, "({input})::{fb}{target_type}")
649 }
650}
651
652impl Textify for IfThen {
653 fn name() -> &'static str {
654 "IfThen"
655 }
656
657 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
661 write!(w, "if_then(")?;
662 for clause in &self.ifs {
663 let if_expr = ctx.expect(clause.r#if.as_ref());
664 let then_expr = ctx.expect(clause.then.as_ref());
665 write!(w, "{if_expr} -> {then_expr}, ")?;
666 }
667 let else_expr = ctx.expect(self.r#else.as_deref());
668 write!(w, "_ -> {else_expr})")
669 }
670}
671
672impl Textify for RexType {
673 fn name() -> &'static str {
674 "RexType"
675 }
676
677 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
678 match self {
679 RexType::Literal(literal) => literal.textify(ctx, w),
680 RexType::Selection(f) => f.textify(ctx, w),
681 RexType::ScalarFunction(s) => s.textify(ctx, w),
682 RexType::WindowFunction(_f) => write!(
683 w,
684 "{}",
685 ctx.failure(PlanError::unimplemented(
686 "RexType",
687 Some("WindowFunction"),
688 "WindowFunction textification not implemented",
689 ))
690 ),
691 RexType::IfThen(i) => i.textify(ctx, w),
692 RexType::SwitchExpression(_s) => write!(
693 w,
694 "{}",
695 ctx.failure(PlanError::unimplemented(
696 "RexType",
697 Some("SwitchExpression"),
698 "SwitchExpression textification not implemented",
699 ))
700 ),
701 RexType::SingularOrList(_s) => write!(
702 w,
703 "{}",
704 ctx.failure(PlanError::unimplemented(
705 "RexType",
706 Some("SingularOrList"),
707 "SingularOrList textification not implemented",
708 ))
709 ),
710 RexType::MultiOrList(_m) => write!(
711 w,
712 "{}",
713 ctx.failure(PlanError::unimplemented(
714 "RexType",
715 Some("MultiOrList"),
716 "MultiOrList textification not implemented",
717 ))
718 ),
719 RexType::Cast(c) => c.textify(ctx, w),
720 RexType::Subquery(_s) => write!(
721 w,
722 "{}",
723 ctx.failure(PlanError::unimplemented(
724 "RexType",
725 Some("Subquery"),
726 "Subquery textification not implemented",
727 ))
728 ),
729 RexType::Nested(_n) => write!(
730 w,
731 "{}",
732 ctx.failure(PlanError::unimplemented(
733 "RexType",
734 Some("Nested"),
735 "Nested textification not implemented",
736 ))
737 ),
738 RexType::DynamicParameter(_d) => write!(
739 w,
740 "{}",
741 ctx.failure(PlanError::unimplemented(
742 "RexType",
743 Some("DynamicParameter"),
744 "DynamicParameter textification not implemented",
745 ))
746 ),
747 #[allow(deprecated)]
748 RexType::Enum(_) => write!(
749 w,
750 "{}",
751 ctx.failure(PlanError::unimplemented(
752 "RexType",
753 Some("Enum"),
754 "Enum textification not implemented",
755 ))
756 ),
757 RexType::Lambda(_) => write!(
758 w,
759 "{}",
760 ctx.failure(PlanError::unimplemented(
761 "RexType",
762 Some("Lambda"),
763 "Lambda textification not implemented",
764 ))
765 ),
766 RexType::LambdaInvocation(_) => write!(
767 w,
768 "{}",
769 ctx.failure(PlanError::unimplemented(
770 "RexType",
771 Some("LambdaInvocation"),
772 "LambdaInvocation textification not implemented",
773 ))
774 ),
775 }
776 }
777}
778
779impl Textify for Expression {
780 fn name() -> &'static str {
781 "Expression"
782 }
783
784 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
785 write!(w, "{}", ctx.expect(self.rex_type.as_ref()))
786 }
787}
788
789impl Textify for AggregateFunction {
790 fn name() -> &'static str {
791 "AggregateFunction"
792 }
793
794 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
795 let invocation = FunctionInvocation::from(self);
796 let output_type = OutputType(self.output_type.as_ref());
797
798 let invocation = ctx.display(&invocation);
799 let output_type = ctx.display(&output_type);
800
801 write!(w, "{invocation}{output_type}")
802 }
803}
804
805#[cfg(test)]
806mod tests {
807 use substrait::proto::Type;
808 use substrait::proto::expression::{cast, if_then};
809 use substrait::proto::r#type::{Boolean, I16, I32, I64, Kind, Nullability, UserDefined};
810
811 use super::*;
812 use crate::extensions::simple::{ExtensionKind, MissingReference};
813 use crate::fixtures::TestContext;
814 use crate::textify::foundation::{FormatError, FormatErrorType};
815
816 fn literal_bool(value: bool) -> Expression {
817 Expression {
818 rex_type: Some(RexType::Literal(expr::Literal {
819 nullable: false,
820 type_variation_reference: 0,
821 literal_type: Some(expr::literal::LiteralType::Boolean(value)),
822 })),
823 }
824 }
825
826 fn non_nullable_literal(lit: expr::literal::LiteralType) -> expr::Literal {
827 expr::Literal {
828 nullable: false,
829 type_variation_reference: 0,
830 literal_type: Some(lit),
831 }
832 }
833
834 #[test]
835 fn test_literal_textify() {
836 let ctx = TestContext::new();
837
838 let literal = non_nullable_literal(LiteralType::Boolean(true));
839 assert_eq!(ctx.textify_no_errors(&literal), "true");
840 }
841
842 fn nullable_literal(lit: expr::literal::LiteralType) -> expr::Literal {
843 expr::Literal {
844 nullable: true,
845 type_variation_reference: 0,
846 literal_type: Some(lit),
847 }
848 }
849
850 #[test]
851 fn test_nullable_boolean_literal_textify() {
852 let ctx = TestContext::new();
853 assert_eq!(
854 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Boolean(true))),
855 "true:boolean?"
856 );
857 assert_eq!(
858 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Boolean(
859 false
860 ))),
861 "false:boolean?"
862 );
863 }
864
865 #[test]
866 fn test_nullable_integer_literal_textify() {
867 let ctx = TestContext::new();
868 assert_eq!(
869 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::I32(78))),
870 "78:i32?"
871 );
872 assert_eq!(
873 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::I64(42))),
874 "42:i64?"
875 );
876 }
877
878 #[test]
879 fn test_nullable_float_literal_textify() {
880 let ctx = TestContext::new();
881 assert_eq!(
882 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Fp32(2.5))),
883 "2.5:fp32?"
884 );
885 assert_eq!(
886 ctx.textify_no_errors(&nullable_literal(expr::literal::LiteralType::Fp64(3.19))),
887 "3.19:fp64?"
888 );
889 }
890
891 #[test]
892 fn test_precision_timestamp_to_string() {
893 assert_eq!(
894 precision_timestamp_to_string(10, 0),
895 Ok("1970-01-01T00:00:10".to_string())
896 );
897 assert_eq!(
898 precision_timestamp_to_string(123_456_789, 9),
899 Ok("1970-01-01T00:00:00.123456789".to_string())
900 );
901 assert_eq!(
904 precision_timestamp_to_string(123_456_789_500, 12),
905 Ok("1970-01-01T00:00:00.123456789".to_string())
906 );
907 assert_eq!(
908 precision_timestamp_to_string(0, 13),
909 Err(PrecisionFormatError::UnsupportedPrecision)
910 );
911 }
912
913 #[test]
914 fn test_precision_time_to_string() {
915 assert_eq!(precision_time_to_string(0, 0), Ok("00:00:00".to_string()));
916 assert_eq!(
917 precision_time_to_string(3_661_000_000, 6),
920 Ok("01:01:01.000000".to_string())
921 );
922 assert_eq!(
923 precision_time_to_string(3_661_000_000_000_500, 12),
926 Ok("01:01:01.000000000".to_string())
927 );
928 assert_eq!(
929 precision_time_to_string(0, 13),
930 Err(PrecisionFormatError::UnsupportedPrecision)
931 );
932 assert_eq!(
936 precision_time_to_string(-1, 12),
937 Err(PrecisionFormatError::OutOfRange)
938 );
939 }
940
941 #[test]
942 fn test_nullable_precision_timestamp_literal_textify() {
943 let ctx = TestContext::new();
944 assert_eq!(
945 ctx.textify_no_errors(&nullable_literal(
946 expr::literal::LiteralType::PrecisionTimestamp(expr::literal::PrecisionTimestamp {
947 precision: 6,
948 value: 1000,
949 })
950 )),
951 "'1970-01-01T00:00:00.001000':precisiontimestamp?<6>"
952 );
953 assert_eq!(
954 ctx.textify_no_errors(&nullable_literal(
955 expr::literal::LiteralType::PrecisionTimestampTz(
956 expr::literal::PrecisionTimestamp {
957 precision: 3,
958 value: 5,
959 }
960 )
961 )),
962 "'1970-01-01T00:00:00.005':precisiontimestamptz?<3>"
963 );
964 assert_eq!(
965 ctx.textify_no_errors(&nullable_literal(
966 expr::literal::LiteralType::PrecisionTime(expr::literal::PrecisionTime {
967 precision: 0,
968 value: 61,
969 })
970 )),
971 "'00:01:01':precisiontime?<0>"
972 );
973 }
974
975 #[test]
976 fn test_precision_time_literal_precision_12_best_effort() {
977 let ctx = TestContext::new();
978 let (s, errs) = ctx.textify(&non_nullable_literal(
979 expr::literal::LiteralType::PrecisionTime(expr::literal::PrecisionTime {
980 precision: 12,
981 value: 3_661_000_000_000_500,
982 }),
983 ));
984 assert_eq!(s, "'01:01:01.000000000':precisiontime<12>");
987 assert_eq!(errs.0.len(), 1);
988 assert!(errs.0[0].to_string().contains("truncated"));
989 }
990
991 #[test]
992 fn test_precision_timestamp_literal_supported_precision_no_warning() {
993 let ctx = TestContext::new();
994 let (_, errs) = ctx.textify(&non_nullable_literal(
997 expr::literal::LiteralType::PrecisionTimestamp(expr::literal::PrecisionTimestamp {
998 precision: 9,
999 value: 123_456_789,
1000 }),
1001 ));
1002 assert_eq!(errs.0.len(), 0);
1003 }
1004
1005 #[test]
1006 fn test_precision_timestamp_literal_unrecognized_precision_invalid() {
1007 let ctx = TestContext::new();
1008 let (s, errs) = ctx.textify(&non_nullable_literal(
1009 expr::literal::LiteralType::PrecisionTimestamp(expr::literal::PrecisionTimestamp {
1010 precision: 13,
1011 value: 0,
1012 }),
1013 ));
1014 assert_eq!(s, "!{LiteralType}:precisiontimestamp<13>");
1017 assert_eq!(errs.0.len(), 1);
1018 assert!(errs.0[0].to_string().contains("PrecisionTimestamp"));
1019 }
1020
1021 #[test]
1022 fn test_precision_timestamp_literal_out_of_range_does_not_panic() {
1023 let ctx = TestContext::new();
1024 let (s, errs) = ctx.textify(&non_nullable_literal(
1029 expr::literal::LiteralType::PrecisionTimestamp(expr::literal::PrecisionTimestamp {
1030 precision: 0,
1031 value: 9_000_000_000_000,
1032 }),
1033 ));
1034 assert_eq!(s, "!{LiteralType}:precisiontimestamp<0>");
1035 assert_eq!(errs.0.len(), 1);
1036 }
1037
1038 #[test]
1039 fn test_precision_time_literal_beyond_one_day_invalid() {
1040 let ctx = TestContext::new();
1041 let (s, errs) = ctx.textify(&non_nullable_literal(
1045 expr::literal::LiteralType::PrecisionTime(expr::literal::PrecisionTime {
1046 precision: 0,
1047 value: 86_460,
1048 }),
1049 ));
1050 assert_eq!(s, "!{LiteralType}:precisiontime<0>");
1051 assert_eq!(errs.0.len(), 1);
1052 }
1053
1054 #[test]
1055 fn test_precision_time_literal_unrecognized_precision_invalid() {
1056 let ctx = TestContext::new();
1057 let (s, errs) = ctx.textify(&non_nullable_literal(
1058 expr::literal::LiteralType::PrecisionTime(expr::literal::PrecisionTime {
1059 precision: 13,
1060 value: 0,
1061 }),
1062 ));
1063 assert_eq!(s, "!{LiteralType}:precisiontime<13>");
1064 assert_eq!(errs.0.len(), 1);
1065 assert!(errs.0[0].to_string().contains("PrecisionTime"));
1066 }
1067
1068 #[test]
1069 fn test_nullable_precision_timestamp_literal_precision_12_best_effort() {
1070 let ctx = TestContext::new();
1071 let (s, errs) = ctx.textify(&nullable_literal(
1074 expr::literal::LiteralType::PrecisionTimestamp(expr::literal::PrecisionTimestamp {
1075 precision: 12,
1076 value: 123_456_789_500,
1077 }),
1078 ));
1079 assert_eq!(s, "'1970-01-01T00:00:00.123456789':precisiontimestamp?<12>");
1082 assert_eq!(errs.0.len(), 1);
1083 assert!(errs.0[0].to_string().contains("truncated"));
1084 }
1085
1086 #[test]
1087 fn test_expression_textify() {
1088 let ctx = TestContext::new();
1089
1090 let expr_empty = Expression { rex_type: None }; let (s, errs) = ctx.textify(&expr_empty);
1093 assert!(!errs.is_empty());
1094 assert_eq!(s, "!{RexType}");
1095
1096 let expr_lit = Expression {
1098 rex_type: Some(RexType::Literal(expr::Literal {
1099 nullable: false,
1100 type_variation_reference: 0,
1101 literal_type: Some(expr::literal::LiteralType::Boolean(true)),
1102 })),
1103 };
1104 assert_eq!(ctx.textify_no_errors(&expr_lit), "true");
1105 }
1106
1107 #[test]
1108 fn test_rextype_textify() {
1109 let ctx = TestContext::new();
1110
1111 let func = RexType::ScalarFunction(ScalarFunction {
1112 function_reference: 1000, arguments: vec![],
1114 options: vec![],
1115 output_type: Some(Type {
1116 kind: Some(Kind::I64(I64 {
1117 nullability: Nullability::Required as i32,
1118 type_variation_reference: 0,
1119 })),
1120 }),
1121 #[allow(deprecated)]
1122 args: vec![],
1123 });
1124 let (s, errq) = ctx.textify(&func);
1125 let errs: Vec<_> = errq.0;
1126 match errs[0] {
1127 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => {
1128 assert_eq!(k, ExtensionKind::Function);
1129 assert_eq!(a, 1000);
1130 }
1131 _ => panic!("Expected Lookup MissingAnchor: {}", errs[0]),
1132 }
1133 assert_eq!(s, "!{function}#1000():i64");
1134
1135 let ctx = ctx.with_urn(1, "first").with_function(1, 100, "first");
1136 let func = RexType::ScalarFunction(ScalarFunction {
1137 function_reference: 100,
1138 arguments: vec![],
1139 options: vec![],
1140 output_type: Some(Type {
1141 kind: Some(Kind::I64(I64 {
1142 nullability: Nullability::Required as i32,
1143 type_variation_reference: 0,
1144 })),
1145 }),
1146 #[allow(deprecated)]
1147 args: vec![],
1148 });
1149 let s = ctx.textify_no_errors(&func);
1150 assert_eq!(s, "first():i64");
1151
1152 let options_show_anchor = Default::default();
1154
1155 let ctx = TestContext::new()
1156 .with_options(options_show_anchor)
1157 .with_urn(1, "somewhere_on_the_internet")
1158 .with_urn(2, "somewhere_else")
1159 .with_function(1, 231, "duplicated")
1160 .with_function(2, 232, "duplicated");
1161
1162 let rex_dup = RexType::ScalarFunction(ScalarFunction {
1163 function_reference: 231,
1164 arguments: vec![FunctionArgument {
1165 arg_type: Some(ArgType::Value(Expression {
1166 rex_type: Some(RexType::Literal(expr::Literal {
1167 nullable: false,
1168 type_variation_reference: 0,
1169 literal_type: Some(expr::literal::LiteralType::Boolean(true)),
1170 })),
1171 })),
1172 }],
1173 options: vec![],
1174 output_type: Some(Type {
1175 kind: Some(Kind::Bool(Boolean {
1176 nullability: Nullability::Required as i32,
1177 type_variation_reference: 0,
1178 })),
1179 }),
1180 #[allow(deprecated)]
1181 args: vec![],
1182 });
1183 let s = ctx.textify_no_errors(&rex_dup);
1184 assert_eq!(s, "duplicated#231(true):boolean");
1185 }
1186
1187 #[test]
1188 fn test_ifthen_textify() {
1189 let ctx = TestContext::new();
1190
1191 let if_then = IfThen {
1192 ifs: vec![
1193 if_then::IfClause {
1194 r#if: Some(literal_bool(true)),
1195 then: Some(literal_bool(false)),
1196 },
1197 if_then::IfClause {
1198 r#if: Some(literal_bool(false)),
1199 then: Some(literal_bool(true)),
1200 },
1201 ],
1202 r#else: Some(Box::new(literal_bool(true))),
1203 };
1204
1205 let s = ctx.textify_no_errors(&if_then);
1206 assert_eq!(s, "if_then(true -> false, false -> true, _ -> true)");
1207 }
1208
1209 #[test]
1210 fn test_ifthen_textify_missing_else() {
1211 let ctx = TestContext::new();
1212
1213 let if_then = IfThen {
1214 ifs: vec![if_then::IfClause {
1215 r#if: Some(literal_bool(true)),
1216 then: Some(literal_bool(false)),
1217 }],
1218 r#else: None,
1219 };
1220
1221 let (s, errs) = ctx.textify(&if_then);
1222 assert_eq!(s, "if_then(true -> false, _ -> !{Expression})");
1223 assert_eq!(errs.0.len(), 1);
1224 }
1225
1226 fn make_i32_type() -> Type {
1227 Type {
1228 kind: Some(Kind::I32(I32 {
1229 nullability: Nullability::Required as i32,
1230 type_variation_reference: 0,
1231 })),
1232 }
1233 }
1234
1235 fn make_i16_type() -> Type {
1236 Type {
1237 kind: Some(Kind::I16(I16 {
1238 nullability: Nullability::Required as i32,
1239 type_variation_reference: 0,
1240 })),
1241 }
1242 }
1243
1244 fn literal_i32(value: i32) -> Expression {
1245 Expression {
1246 rex_type: Some(RexType::Literal(expr::Literal {
1247 nullable: false,
1248 type_variation_reference: 0,
1249 literal_type: Some(expr::literal::LiteralType::I32(value)),
1250 })),
1251 }
1252 }
1253
1254 #[test]
1255 fn test_cast_textify() {
1256 let ctx = TestContext::new();
1257 let cast = Cast {
1258 r#type: Some(make_i16_type()),
1259 input: Some(Box::new(literal_i32(78))),
1260 failure_behavior: 0,
1261 };
1262 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::i16");
1263 }
1264
1265 #[test]
1266 fn test_cast_textify_via_rextype() {
1267 let ctx = TestContext::new();
1268 let rex = RexType::Cast(Box::new(Cast {
1269 r#type: Some(make_i16_type()),
1270 input: Some(Box::new(literal_i32(78))),
1271 failure_behavior: 0,
1272 }));
1273 assert_eq!(ctx.textify_no_errors(&rex), "(78:i32)::i16");
1274 }
1275
1276 #[test]
1277 fn test_cast_textify_nested() {
1278 let ctx = TestContext::new();
1280 let inner_cast = Expression {
1281 rex_type: Some(RexType::Cast(Box::new(Cast {
1282 r#type: Some(make_i16_type()),
1283 input: Some(Box::new(literal_i32(78))),
1284 failure_behavior: 0,
1285 }))),
1286 };
1287 let outer_cast = Cast {
1288 r#type: Some(make_i32_type()),
1289 input: Some(Box::new(inner_cast)),
1290 failure_behavior: 0,
1291 };
1292 assert_eq!(ctx.textify_no_errors(&outer_cast), "((78:i32)::i16)::i32");
1293 }
1294
1295 #[test]
1296 fn test_cast_textify_return_null() {
1297 let ctx = TestContext::new();
1298 let cast = Cast {
1299 r#type: Some(make_i16_type()),
1300 input: Some(Box::new(literal_i32(78))),
1301 failure_behavior: cast::FailureBehavior::ReturnNull as i32,
1302 };
1303 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::?i16");
1304 }
1305
1306 #[test]
1307 fn test_cast_textify_throw_exception() {
1308 let ctx = TestContext::new();
1309 let cast = Cast {
1310 r#type: Some(make_i16_type()),
1311 input: Some(Box::new(literal_i32(78))),
1312 failure_behavior: cast::FailureBehavior::ThrowException as i32,
1313 };
1314 assert_eq!(ctx.textify_no_errors(&cast), "(78:i32)::!i16");
1315 }
1316
1317 #[test]
1318 fn test_cast_textify_missing_input() {
1319 let ctx = TestContext::new();
1320 let cast = Cast {
1321 r#type: Some(make_i16_type()),
1322 input: None,
1323 failure_behavior: 0,
1324 };
1325 let (s, errs) = ctx.textify(&cast);
1326 assert_eq!(s, "(!{Expression})::i16");
1327 match &errs.0[0] {
1328 FormatError::Format(e) => {
1329 assert_eq!(e.message, "Expression");
1330 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1331 }
1332 other => panic!("Expected Format(InvalidValue) for missing input, got: {other}"),
1333 }
1334 }
1335
1336 #[test]
1337 fn test_cast_textify_missing_type() {
1338 let ctx = TestContext::new();
1339 let cast = Cast {
1340 r#type: None,
1341 input: Some(Box::new(literal_i32(78))),
1342 failure_behavior: 0,
1343 };
1344 let (s, errs) = ctx.textify(&cast);
1345 assert_eq!(s, "(78:i32)::!{Type}");
1346 match &errs.0[0] {
1347 FormatError::Format(e) => {
1348 assert_eq!(e.message, "Type");
1349 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1350 }
1351 other => panic!("Expected Format(InvalidValue) for missing type, got: {other}"),
1352 }
1353 }
1354
1355 fn struct_field_reference(field: i32) -> FieldReference {
1356 FieldReference {
1357 reference_type: Some(ReferenceType::DirectReference(ReferenceSegment {
1358 reference_type: Some(reference_segment::ReferenceType::StructField(Box::new(
1359 reference_segment::StructField { field, child: None },
1360 ))),
1361 })),
1362 root_type: Some(RootType::RootReference(RootReference {})),
1363 }
1364 }
1365
1366 #[test]
1367 fn test_field_reference_missing_root_type() {
1368 let ctx = TestContext::new();
1369 let mut fr = struct_field_reference(3);
1370 fr.root_type = None;
1371 let (s, errs) = ctx.textify(&fr);
1372 assert_eq!(s, "!{FieldReference}");
1373 match &errs.0[0] {
1374 FormatError::Format(e) => {
1375 assert_eq!(e.message, "FieldReference");
1376 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1377 }
1378 other => panic!("Expected Format(InvalidValue) for missing root_type, got: {other}"),
1379 }
1380 }
1381
1382 #[test]
1383 fn test_field_reference_root_reference() {
1384 let ctx = TestContext::new();
1385 let fr = struct_field_reference(3);
1386 assert_eq!(ctx.textify_no_errors(&fr), "$3");
1387 }
1388
1389 #[test]
1390 fn test_field_reference_outer_reference_unimplemented() {
1391 use substrait::proto::expression::field_reference;
1392
1393 let ctx = TestContext::new();
1394 let mut fr = struct_field_reference(3);
1395 fr.root_type = Some(RootType::OuterReference(field_reference::OuterReference {
1396 steps_out: 1,
1397 }));
1398 let (s, errs) = ctx.textify(&fr);
1399 assert_eq!(s, "!{FieldReference}");
1400 match &errs.0[0] {
1401 FormatError::Format(e) => {
1402 assert_eq!(e.message, "FieldReference");
1403 assert_eq!(e.error_type, FormatErrorType::Unimplemented);
1404 }
1405 other => panic!("Expected Format(Unimplemented) for OuterReference, got: {other}"),
1406 }
1407 }
1408
1409 #[test]
1410 fn test_field_reference_expression_unimplemented() {
1411 let ctx = TestContext::new();
1412 let mut fr = struct_field_reference(3);
1413 fr.root_type = Some(RootType::Expression(Box::new(literal_bool(true))));
1414 let (s, errs) = ctx.textify(&fr);
1415 assert_eq!(s, "!{FieldReference}");
1416 match &errs.0[0] {
1417 FormatError::Format(e) => {
1418 assert_eq!(e.message, "FieldReference");
1419 assert_eq!(e.error_type, FormatErrorType::Unimplemented);
1420 }
1421 other => panic!("Expected Format(Unimplemented) for Expression, got: {other}"),
1422 }
1423 }
1424
1425 #[test]
1426 fn test_cast_textify_invalid_failure_behavior() {
1427 let ctx = TestContext::new();
1428 let cast = Cast {
1429 r#type: Some(make_i16_type()),
1430 input: Some(Box::new(literal_i32(78))),
1431 failure_behavior: 99,
1432 };
1433 let (s, errs) = ctx.textify(&cast);
1434 assert_eq!(s, "(78:i32)::!{Cast}i16");
1436 match &errs.0[0] {
1437 FormatError::Format(e) => {
1438 assert_eq!(e.message, "Cast");
1439 assert_eq!(e.error_type, FormatErrorType::InvalidValue);
1440 }
1441 other => {
1442 panic!("Expected Format(InvalidValue) for invalid failure_behavior, got: {other}")
1443 }
1444 }
1445 }
1446
1447 #[test]
1448 fn test_cast_to_user_defined_type_textifies_without_u_prefix() {
1449 let ctx = TestContext::new()
1451 .with_urn(1, "urn:example:types")
1452 .with_type(1, 5, "u!json");
1453 let cast = Cast {
1454 r#type: Some(Type {
1455 kind: Some(Kind::UserDefined(UserDefined {
1456 type_variation_reference: 0,
1457 nullability: Nullability::Required as i32,
1458 type_reference: 5,
1459 type_parameters: vec![],
1460 })),
1461 }),
1462 input: Some(Box::new(literal_i32(1))),
1463 failure_behavior: 0,
1464 };
1465 assert_eq!(ctx.textify_no_errors(&cast), "(1:i32)::json");
1466 }
1467}