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