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