Skip to main content

substrait_explain/textify/
expressions.rs

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