Skip to main content

substrait_explain/textify/
expressions.rs

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
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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61enum PrecisionFormatError {
62    /// `precision` isn't one of the precisions literals support (0, 3, 6, 9, 12).
63    UnsupportedPrecision,
64    /// `value` can't be represented at `precision`: it overflows the
65    /// representable range (timestamps) or falls outside a single day (times).
66    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
83/// Returns the diagnostic for truncating a picosecond value to nanoseconds.
84fn 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
110/// Write an enum value. Enums are written as `&<identifier>`, if the string is
111/// a valid identifier; otherwise, they are written as `&'<escaped_string>'`.
112pub fn textify_enum<S: Scope, W: fmt::Write>(s: &str, _ctx: &S, w: &mut W) -> fmt::Result {
113    write!(w, "&{}", Name(s))
114}
115
116/// Convert days since Unix epoch to date string
117fn 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
123/// Convert a value in `precision` units, to a `chrono::Duration`.
124/// Precision 12 (picoseconds) is truncated to nanoseconds: chrono can't represent sub-nanosecond resolution.
125fn 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
139/// The chrono fractional-seconds format specifier for a precision, or `""` for
140/// precision 0 (whole seconds, no fractional part). A fixed-width specifier is
141/// used so the rendered fractional digits match the declared precision exactly.
142/// Precision 12 renders at nanosecond width, matching the truncation in [`duration_from_precision_units`].
143fn fractional_spec(precision: i32) -> &'static str {
144    match precision {
145        3 => "%.3f",
146        6 => "%.6f",
147        9 | 12 => "%.9f",
148        _ => "", // precision 0 (or unsupported, already reported as an error)
149    }
150}
151
152/// Convert a value in precision units since the Unix epoch, to a timestamp string.
153/// Errors if `value` is out of chrono's representable date range.
154fn 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
168/// Convert a value in precision units since midnight, to a time-of-day string.
169/// Errors if `value` falls outside a single day: `NaiveTime + Duration` wraps
170/// modulo 24 hours, which would otherwise silently misrepresent the value.
171///
172/// The sign check is on `value` itself, not the `chrono::Duration` derived from
173/// it: at precision 12, `duration_from_precision_units` truncates towards zero,
174/// so a small negative `value` (e.g. `-1`) would otherwise round to a
175/// zero/non-negative duration and be wrongly accepted.
176fn 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
191/// Write just the value portion of a literal, with no type suffix or
192/// nullability marker.
193///
194/// For unimplemented types, writes an error token via `ctx.failure()`.
195fn 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
268/// The type suffix for a literal (e.g., `"i32"`, `"fp64"`, `"date"`).
269/// Returns `None` for unimplemented types whose [`write_literal_value`] already
270/// emitted an error token.
271fn write_literal_type_suffix<W: fmt::Write>(
272    lit: &LiteralType,
273    nullable: bool,
274    w: &mut W,
275) -> fmt::Result {
276    // (type name, precision parameter for parameterized types).
277    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
308/// Whether this type is the default interpretation for its value syntax.
309///
310/// Each literal value syntax has a default type that the parser assumes when
311/// no explicit type suffix is present:
312/// - `true`/`false` → `boolean`
313/// - bare integers (`42`) → `i64`
314/// - bare floats (`3.19`) → `fp64`
315/// - single-quoted strings (`'hello'`) → `string`
316/// - hex literals (`0x...`) → `binary`
317///
318/// Non-default types (e.g., `i32`, `fp32`, `date`) always need an explicit
319/// suffix to distinguish them from the default.
320fn 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        // XXX: Why is it so many layers to make a struct field reference? This is
376        // surprisingly complex
377        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/// The fields shared by every Substrait function call - `ScalarFunction`,
510/// `AggregateFunction`, and `WindowFunction` - that render as
511/// `name#anchor(args, options)`.
512///
513/// The remaining fields of each function message (the output type, and the
514/// aggregate/window-specific parts) are textified alongside this by the
515/// respective `Textify` implementations.
516#[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    // This method writes ifThen using the following convention of a comma separated sequence of 'if_clause -> then_clause, '
658    // followed by the final else clause denoted with '_'
659    // ex: true -> if_then(true || false -> true, _ -> false)
660    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        // Precision 12 (picoseconds) is truncated to nanoseconds (best-effort):
902        // the trailing 500 picoseconds below don't survive.
903        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            // 01:01:01 = 3661 seconds, in microseconds. The fractional part is
918            // rendered at the declared precision width (6 digits).
919            precision_time_to_string(3_661_000_000, 6),
920            Ok("01:01:01.000000".to_string())
921        );
922        assert_eq!(
923            // 01:01:01 = 3661 seconds, in picoseconds, plus 500 ps that get
924            // truncated away. Precision 12 renders at nanosecond width (9 digits).
925            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        // A negative value at precision 12 truncates towards zero when converted
933        // to a `chrono::Duration` (-1 / 1000 == 0), so the sign must be checked
934        // on the raw value, not the derived duration.
935        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        // Value truncated to nanosecond resolution (9 fractional digits), but
985        // the declared type `<12>` is preserved.
986        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        // Precisions chrono can represent exactly shouldn't trigger the
995        // precision-12 truncation warning.
996        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        // The value fails to render (unrecognized precision), but the suffix
1015        // is still written with the precision as-is.
1016        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        // 9e12 seconds since epoch is a valid i64 and a valid `chrono::Duration`,
1025        // but it's outside NaiveDateTime's representable range: adding it to the
1026        // epoch with `+` would panic. Textification should report a failure
1027        // token instead.
1028        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        // 86,460 seconds since midnight is more than a day; `NaiveTime + Duration`
1042        // wraps modulo 24 hours rather than erroring, which would otherwise
1043        // silently misrepresent the value as 00:01:00.
1044        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        // Picoseconds aren't representable, the textify best-effort approach is to
1072        // truncate to nanoseconds. The lost precision is reported via the error accumulator.
1073        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        // Value truncated to nanosecond resolution, but the declared type
1080        // `<12>` is preserved rather than rewritten to `<9>`.
1081        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        // Test empty expression
1091        let expr_empty = Expression { rex_type: None }; // Renamed to avoid conflict
1092        let (s, errs) = ctx.textify(&expr_empty);
1093        assert!(!errs.is_empty());
1094        assert_eq!(s, "!{RexType}");
1095
1096        // Test literal expression
1097        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, // Does not exist
1113            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        // Test for duplicated function name requiring anchor
1153        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        // ((78:i32)::i16)::i32 — cast of a cast
1279        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        // Error token is embedded inline — input and type are still written
1435        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        // A type stored as "u!json" normalizes to "json"; cast emits "::json".
1450        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}