substrait_explain/textify/
expressions.rs

1use std::fmt::{self};
2
3use chrono::{DateTime, NaiveDate};
4use expr::RexType;
5use substrait::proto::expression::field_reference::ReferenceType;
6use substrait::proto::expression::literal::LiteralType;
7use substrait::proto::expression::{
8    FieldReference, ReferenceSegment, ScalarFunction, reference_segment,
9};
10use substrait::proto::function_argument::ArgType;
11use substrait::proto::r#type::{self as ptype, Kind, Nullability};
12use substrait::proto::{
13    AggregateFunction, Expression, FunctionArgument, FunctionOption, expression as expr,
14};
15
16use super::{PlanError, Scope, Textify, Visibility};
17use crate::extensions::SimpleExtensions;
18use crate::extensions::simple::ExtensionKind;
19use crate::textify::types::{Name, NamedAnchor, OutputType, escaped};
20
21// …(…) for function call
22// […] for variant
23// <…> for parameters
24// !{…} for missing value
25
26// $… for field reference
27// #… for anchor
28// @… for URN anchor
29// …::… for cast
30// …:… for specifying type
31// &… for enum
32
33pub fn textify_binary<S: Scope, W: fmt::Write>(items: &[u8], ctx: &S, w: &mut W) -> fmt::Result {
34    if ctx.options().show_literal_binaries {
35        write!(w, "0x")?;
36        for &n in items {
37            write!(w, "{n:02x}")?;
38        }
39    } else {
40        write!(w, "{{binary}}")?;
41    }
42    Ok(())
43}
44
45pub fn textify_literal_from_string<S: Scope, W: fmt::Write>(
46    s: &str,
47    t: Kind,
48    ctx: &S,
49    w: &mut W,
50) -> fmt::Result {
51    write!(w, "\'{}\':{}", escaped(s), ctx.display(&t))
52}
53
54/// Write an enum value. Enums are written as `&<identifier>`, if the string is
55/// a valid identifier; otherwise, they are written as `&'<escaped_string>'`.
56pub fn textify_enum<S: Scope, W: fmt::Write>(s: &str, _ctx: &S, w: &mut W) -> fmt::Result {
57    write!(w, "&{}", Name(s))
58}
59
60pub fn timestamp_to_string(t: i64) -> String {
61    let ts = chrono::DateTime::from_timestamp_nanos(t);
62    ts.to_rfc3339()
63}
64
65/// Convert days since Unix epoch to date string
66fn days_to_date_string(days: i32) -> String {
67    let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
68    let date = epoch + chrono::Duration::days(days as i64);
69    date.format("%Y-%m-%d").to_string()
70}
71
72/// Convert microseconds since midnight to time string
73fn microseconds_to_time_string(microseconds: i64) -> String {
74    let total_seconds = microseconds / 1_000_000;
75    let remaining_microseconds = microseconds % 1_000_000;
76
77    let hours = total_seconds / 3600;
78    let minutes = (total_seconds % 3600) / 60;
79    let seconds = total_seconds % 60;
80
81    if remaining_microseconds == 0 {
82        format!("{hours:02}:{minutes:02}:{seconds:02}")
83    } else {
84        // Convert microseconds to fractional seconds
85        let fractional = remaining_microseconds as f64 / 1_000_000.0;
86        format!("{hours:02}:{minutes:02}:{seconds:02}{fractional:.6}")
87            .trim_end_matches('0')
88            .trim_end_matches('.')
89            .to_string()
90    }
91}
92
93/// Convert microseconds since Unix epoch to timestamp string
94fn microseconds_to_timestamp_string(microseconds: i64) -> String {
95    let epoch = DateTime::from_timestamp(0, 0).unwrap().naive_utc();
96    let duration = chrono::Duration::microseconds(microseconds);
97    let datetime = epoch + duration;
98
99    // Format with fractional seconds, then clean up trailing zeros
100    let formatted = datetime.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
101
102    // If there are fractional seconds, trim trailing zeros and dot if needed
103    if formatted.contains('.') {
104        formatted
105            .trim_end_matches('0')
106            .trim_end_matches('.')
107            .to_string()
108    } else {
109        formatted
110    }
111}
112
113trait Kinded {
114    fn kind(&self, ctx: &SimpleExtensions) -> Option<Kind>;
115}
116
117impl Kinded for LiteralType {
118    fn kind(&self, _ctx: &SimpleExtensions) -> Option<Kind> {
119        match self {
120            LiteralType::Boolean(_) => Some(Kind::Bool(ptype::Boolean {
121                type_variation_reference: 0,
122                nullability: Nullability::Required.into(),
123            })),
124            LiteralType::I8(_) => Some(Kind::I8(ptype::I8 {
125                type_variation_reference: 0,
126                nullability: Nullability::Required.into(),
127            })),
128            LiteralType::I16(_) => Some(Kind::I16(ptype::I16 {
129                type_variation_reference: 0,
130                nullability: Nullability::Required.into(),
131            })),
132            LiteralType::I32(_) => Some(Kind::I32(ptype::I32 {
133                type_variation_reference: 0,
134                nullability: Nullability::Required.into(),
135            })),
136            LiteralType::I64(_) => Some(Kind::I64(ptype::I64 {
137                type_variation_reference: 0,
138                nullability: Nullability::Required.into(),
139            })),
140            LiteralType::Fp32(_) => Some(Kind::Fp32(ptype::Fp32 {
141                type_variation_reference: 0,
142                nullability: Nullability::Required.into(),
143            })),
144            LiteralType::Fp64(_) => Some(Kind::Fp64(ptype::Fp64 {
145                type_variation_reference: 0,
146                nullability: Nullability::Required.into(),
147            })),
148            LiteralType::String(_) => Some(Kind::String(ptype::String {
149                type_variation_reference: 0,
150                nullability: Nullability::Required.into(),
151            })),
152            LiteralType::Binary(_) => Some(Kind::Binary(ptype::Binary {
153                type_variation_reference: 0,
154                nullability: Nullability::Required.into(),
155            })),
156            #[allow(deprecated)]
157            LiteralType::Timestamp(_) => Some(Kind::Timestamp(ptype::Timestamp {
158                type_variation_reference: 0,
159                nullability: Nullability::Required.into(),
160            })),
161            LiteralType::Date(_) => Some(Kind::Date(ptype::Date {
162                type_variation_reference: 0,
163                nullability: Nullability::Required.into(),
164            })),
165            LiteralType::Time(_) => Some(Kind::Time(ptype::Time {
166                type_variation_reference: 0,
167                nullability: Nullability::Required.into(),
168            })),
169            LiteralType::IntervalYearToMonth(_) => Some(Kind::IntervalYear(ptype::IntervalYear {
170                type_variation_reference: 0,
171                nullability: Nullability::Required.into(),
172            })),
173            LiteralType::IntervalDayToSecond(i) => {
174                let precision = match i.precision_mode {
175                    #[allow(deprecated)]
176                    Some(expr::literal::interval_day_to_second::PrecisionMode::Microseconds(
177                        _m,
178                    )) => Some(6),
179                    Some(expr::literal::interval_day_to_second::PrecisionMode::Precision(p)) => {
180                        Some(p)
181                    }
182                    // Unset precision is 0; protobuf defaults to 0
183                    None => None,
184                };
185
186                Some(Kind::IntervalDay(ptype::IntervalDay {
187                    type_variation_reference: 0,
188                    nullability: Nullability::Required.into(),
189                    precision,
190                }))
191            }
192            LiteralType::IntervalCompound(_) => todo!(),
193            LiteralType::FixedChar(_) => todo!(),
194            LiteralType::VarChar(_c) => todo!(),
195            LiteralType::FixedBinary(_b) => todo!(),
196            LiteralType::Decimal(_d) => todo!(),
197            LiteralType::PrecisionTime(_t) => todo!(),
198            LiteralType::PrecisionTimestamp(t) => {
199                Some(Kind::PrecisionTimestamp(ptype::PrecisionTimestamp {
200                    type_variation_reference: 0,
201                    nullability: Nullability::Required.into(),
202                    precision: t.precision,
203                }))
204            }
205            LiteralType::PrecisionTimestampTz(_t) => todo!(),
206            LiteralType::Struct(_s) => todo!(),
207            LiteralType::Map(_m) => todo!(),
208            #[allow(deprecated)]
209            LiteralType::TimestampTz(_t) => todo!(),
210            LiteralType::Uuid(_u) => todo!(),
211            LiteralType::Null(_n) => todo!(),
212            LiteralType::List(_l) => todo!(),
213            LiteralType::EmptyList(_l) => todo!(),
214            LiteralType::EmptyMap(_m) => todo!(),
215            LiteralType::UserDefined(_u) => todo!(),
216        }
217    }
218}
219
220impl Textify for LiteralType {
221    fn name() -> &'static str {
222        "LiteralType"
223    }
224
225    fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
226        match self {
227            LiteralType::Boolean(true) => write!(w, "true")?,
228            LiteralType::Boolean(false) => write!(w, "false")?,
229            LiteralType::I8(i) => write!(w, "{i}:i8")?,
230            LiteralType::I16(i) => write!(w, "{i}:i16")?,
231            LiteralType::I32(i) => write!(w, "{i}:i32")?,
232            LiteralType::I64(i) => {
233                match ctx.options().literal_types {
234                    Visibility::Always => write!(w, "{i}:i64")?,
235                    Visibility::Required => write!(w, "{i}")?, // I64 is the default, no suffix needed
236                    Visibility::Never => write!(w, "{i}")?,
237                }
238            }
239            LiteralType::Fp32(f) => write!(w, "{f}:fp32")?,
240            LiteralType::Fp64(f) => {
241                match ctx.options().literal_types {
242                    Visibility::Always => write!(w, "{f}:fp64")?,
243                    Visibility::Required => write!(w, "{f}")?, // Fp64 is the default, no suffix needed
244                    Visibility::Never => write!(w, "{f}")?,
245                }
246            }
247            LiteralType::String(s) => write!(w, "'{}'", s.escape_debug())?,
248            LiteralType::Binary(items) => textify_binary(items, ctx, w)?,
249            #[allow(deprecated)]
250            LiteralType::Timestamp(microseconds) => {
251                let k = match self.kind(ctx.extensions()) {
252                    Some(k) => k,
253                    None => {
254                        let err = PlanError::internal(
255                            "LiteralType",
256                            Some("Timestamp"),
257                            format!("No kind found for {self:?}"),
258                        );
259                        write!(w, "{}", ctx.failure(err))?;
260                        return Ok(());
261                    }
262                };
263                let s = microseconds_to_timestamp_string(*microseconds);
264                textify_literal_from_string(&s, k, ctx, w)?
265            }
266            LiteralType::Date(days) => {
267                let k = match self.kind(ctx.extensions()) {
268                    Some(k) => k,
269                    None => {
270                        let err = PlanError::internal(
271                            "LiteralType",
272                            Some("Date"),
273                            format!("No kind found for {self:?}"),
274                        );
275                        write!(w, "{}", ctx.failure(err))?;
276                        return Ok(());
277                    }
278                };
279                let s = days_to_date_string(*days);
280                textify_literal_from_string(&s, k, ctx, w)?
281            }
282            LiteralType::Time(microseconds) => {
283                let k = match self.kind(ctx.extensions()) {
284                    Some(k) => k,
285                    None => {
286                        let err = PlanError::internal(
287                            "LiteralType",
288                            Some("Time"),
289                            format!("No kind found for {self:?}"),
290                        );
291                        write!(w, "{}", ctx.failure(err))?;
292                        return Ok(());
293                    }
294                };
295                let s = microseconds_to_time_string(*microseconds);
296                textify_literal_from_string(&s, k, ctx, w)?
297            }
298            LiteralType::IntervalYearToMonth(_i) => {
299                return write!(
300                    w,
301                    "{}",
302                    ctx.failure(PlanError::unimplemented(
303                        "LiteralType",
304                        Some("IntervalYearToMonth"),
305                        "IntervalYearToMonth literal textification not implemented",
306                    ))
307                );
308            }
309            LiteralType::IntervalDayToSecond(_i) => {
310                return write!(
311                    w,
312                    "{}",
313                    ctx.failure(PlanError::unimplemented(
314                        "LiteralType",
315                        Some("IntervalDayToSecond"),
316                        "IntervalDayToSecond literal textification not implemented",
317                    ))
318                );
319            }
320            LiteralType::IntervalCompound(_i) => {
321                return write!(
322                    w,
323                    "{}",
324                    ctx.failure(PlanError::unimplemented(
325                        "LiteralType",
326                        Some("IntervalCompound"),
327                        "IntervalCompound literal textification not implemented",
328                    ))
329                );
330            }
331            LiteralType::FixedChar(_c) => {
332                return write!(
333                    w,
334                    "{}",
335                    ctx.failure(PlanError::unimplemented(
336                        "LiteralType",
337                        Some("FixedChar"),
338                        "FixedChar literal textification not implemented",
339                    ))
340                );
341            }
342            LiteralType::VarChar(_c) => {
343                return write!(
344                    w,
345                    "{}",
346                    ctx.failure(PlanError::unimplemented(
347                        "LiteralType",
348                        Some("VarChar"),
349                        "VarChar literal textification not implemented",
350                    ))
351                );
352            }
353            LiteralType::FixedBinary(_i) => {
354                return write!(
355                    w,
356                    "{}",
357                    ctx.failure(PlanError::unimplemented(
358                        "LiteralType",
359                        Some("FixedBinary"),
360                        "FixedBinary literal textification not implemented",
361                    ))
362                );
363            }
364            LiteralType::Decimal(_d) => {
365                return write!(
366                    w,
367                    "{}",
368                    ctx.failure(PlanError::unimplemented(
369                        "LiteralType",
370                        Some("Decimal"),
371                        "Decimal literal textification not implemented",
372                    ))
373                );
374            }
375            LiteralType::PrecisionTime(_p) => {
376                return write!(
377                    w,
378                    "{}",
379                    ctx.failure(PlanError::unimplemented(
380                        "LiteralType",
381                        Some("PrecisionTime"),
382                        "PrecisionTime literal textification not implemented",
383                    ))
384                );
385            }
386            LiteralType::PrecisionTimestamp(_p) => {
387                return write!(
388                    w,
389                    "{}",
390                    ctx.failure(PlanError::unimplemented(
391                        "LiteralType",
392                        Some("PrecisionTimestamp"),
393                        "PrecisionTimestamp literal textification not implemented",
394                    ))
395                );
396            }
397            LiteralType::PrecisionTimestampTz(_p) => {
398                return write!(
399                    w,
400                    "{}",
401                    ctx.failure(PlanError::unimplemented(
402                        "LiteralType",
403                        Some("PrecisionTimestampTz"),
404                        "PrecisionTimestampTz literal textification not implemented",
405                    ))
406                );
407            }
408            LiteralType::Struct(_s) => {
409                return write!(
410                    w,
411                    "{}",
412                    ctx.failure(PlanError::unimplemented(
413                        "LiteralType",
414                        Some("Struct"),
415                        "Struct literal textification not implemented",
416                    )),
417                );
418            }
419            LiteralType::Map(_m) => {
420                return write!(
421                    w,
422                    "{}",
423                    ctx.failure(PlanError::unimplemented(
424                        "LiteralType",
425                        Some("Map"),
426                        "Map literal textification not implemented",
427                    )),
428                );
429            }
430            #[allow(deprecated)]
431            LiteralType::TimestampTz(_t) => {
432                return write!(
433                    w,
434                    "{}",
435                    ctx.failure(PlanError::unimplemented(
436                        "LiteralType",
437                        Some("TimestampTz"),
438                        "TimestampTz literal textification not implemented",
439                    ))
440                );
441            }
442            LiteralType::Uuid(_u) => {
443                return write!(
444                    w,
445                    "{}",
446                    ctx.failure(PlanError::unimplemented(
447                        "LiteralType",
448                        Some("Uuid"),
449                        "Uuid literal textification not implemented",
450                    ))
451                );
452            }
453            LiteralType::Null(_n) => {
454                return write!(
455                    w,
456                    "{}",
457                    ctx.failure(PlanError::unimplemented(
458                        "LiteralType",
459                        Some("Null"),
460                        "Null literal textification not implemented",
461                    ))
462                );
463            }
464            LiteralType::List(_l) => {
465                return write!(
466                    w,
467                    "{}",
468                    ctx.failure(PlanError::unimplemented(
469                        "LiteralType",
470                        Some("List"),
471                        "List literal textification not implemented",
472                    ))
473                );
474            }
475            LiteralType::EmptyList(_l) => {
476                return write!(
477                    w,
478                    "{}",
479                    ctx.failure(PlanError::unimplemented(
480                        "LiteralType",
481                        Some("EmptyList"),
482                        "EmptyList literal textification not implemented",
483                    ))
484                );
485            }
486            LiteralType::EmptyMap(_l) => {
487                return write!(
488                    w,
489                    "{}",
490                    ctx.failure(PlanError::unimplemented(
491                        "LiteralType",
492                        Some("EmptyMap"),
493                        "EmptyMap literal textification not implemented",
494                    ))
495                );
496            }
497            LiteralType::UserDefined(_u) => {
498                return write!(
499                    w,
500                    "{}",
501                    ctx.failure(PlanError::unimplemented(
502                        "LiteralType",
503                        Some("UserDefined"),
504                        "UserDefined literal textification not implemented",
505                    ))
506                );
507            }
508        }
509        Ok(())
510    }
511}
512
513impl Textify for expr::Literal {
514    fn name() -> &'static str {
515        "Literal"
516    }
517
518    fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
519        write!(w, "{}", ctx.expect(self.literal_type.as_ref()))
520    }
521}
522
523pub struct Reference(pub i32);
524
525impl fmt::Display for Reference {
526    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
527        write!(f, "${}", self.0)
528    }
529}
530
531impl From<Reference> for Expression {
532    fn from(r: Reference) -> Self {
533        // XXX: Why is it so many layers to make a struct field reference? This is
534        // surprisingly complex
535        Expression {
536            rex_type: Some(RexType::Selection(Box::new(FieldReference {
537                reference_type: Some(ReferenceType::DirectReference(ReferenceSegment {
538                    reference_type: Some(reference_segment::ReferenceType::StructField(Box::new(
539                        reference_segment::StructField {
540                            field: r.0,
541                            child: None,
542                        },
543                    ))),
544                })),
545                root_type: None,
546            }))),
547        }
548    }
549}
550
551impl Textify for Reference {
552    fn name() -> &'static str {
553        "Reference"
554    }
555
556    fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
557        write!(w, "{self}")
558    }
559}
560
561impl Textify for FieldReference {
562    fn name() -> &'static str {
563        "FieldReference"
564    }
565
566    fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
567        let ref_type = match &self.reference_type {
568            None => {
569                return write!(
570                    w,
571                    "{}",
572                    ctx.failure(PlanError::invalid(
573                        "FieldReference",
574                        Some("reference_type"),
575                        "Required field reference_type is missing",
576                    ))
577                );
578            }
579            Some(ReferenceType::DirectReference(r)) => r,
580            _ => {
581                return write!(
582                    w,
583                    "{}",
584                    ctx.failure(PlanError::unimplemented(
585                        "FieldReference",
586                        Some("FieldReference"),
587                        "FieldReference textification implemented only for StructField",
588                    ))
589                );
590            }
591        };
592
593        match &ref_type.reference_type {
594            Some(reference_segment::ReferenceType::StructField(s)) => {
595                write!(w, "{}", Reference(s.field))
596            }
597            None => write!(
598                w,
599                "{}",
600                ctx.failure(PlanError::invalid(
601                    "ReferenceSegment",
602                    Some("reference_type"),
603                    "Required field reference_type is missing",
604                ))
605            ),
606            _ => write!(
607                w,
608                "{}",
609                ctx.failure(PlanError::unimplemented(
610                    "ReferenceSegment",
611                    Some("reference_type"),
612                    "ReferenceSegment textification implemented only for StructField",
613                ))
614            ),
615        }
616    }
617}
618
619impl Textify for ScalarFunction {
620    fn name() -> &'static str {
621        "ScalarFunction"
622    }
623
624    fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
625        let name_and_anchor =
626            NamedAnchor::lookup(ctx, ExtensionKind::Function, self.function_reference);
627        let name_and_anchor = ctx.display(&name_and_anchor);
628
629        let args = ctx.separated(&self.arguments, ", ");
630        let options = ctx.separated(&self.options, ", ");
631        let between = if self.arguments.is_empty() || self.options.is_empty() {
632            ""
633        } else {
634            ", "
635        };
636
637        let output = OutputType(self.output_type.as_ref());
638        let output_type = ctx.optional(&output, ctx.options().fn_types);
639
640        write!(
641            w,
642            "{name_and_anchor}({args}{between}{options}){output_type}"
643        )?;
644        Ok(())
645    }
646}
647
648impl Textify for FunctionOption {
649    fn name() -> &'static str {
650        "FunctionOption"
651    }
652
653    fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
654        write!(w, "{}⇒[", self.name)?;
655        let mut first = true;
656        for pref in self.preference.iter() {
657            if !first {
658                write!(w, ", ")?;
659            } else {
660                first = false;
661            }
662            write!(w, "{pref}")?;
663        }
664        write!(w, "]")?;
665        Ok(())
666    }
667}
668
669impl Textify for FunctionArgument {
670    fn name() -> &'static str {
671        "FunctionArgument"
672    }
673
674    fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
675        write!(w, "{}", ctx.expect(self.arg_type.as_ref()))
676    }
677}
678
679impl Textify for ArgType {
680    fn name() -> &'static str {
681        "ArgType"
682    }
683
684    fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
685        match self {
686            ArgType::Type(t) => t.textify(ctx, w),
687            ArgType::Value(v) => v.textify(ctx, w),
688            ArgType::Enum(e) => textify_enum(e, ctx, w),
689        }
690    }
691}
692
693impl Textify for RexType {
694    fn name() -> &'static str {
695        "RexType"
696    }
697
698    fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
699        match self {
700            RexType::Literal(literal) => literal.textify(ctx, w),
701            RexType::Selection(f) => f.textify(ctx, w),
702            RexType::ScalarFunction(s) => s.textify(ctx, w),
703            RexType::WindowFunction(_w) => write!(
704                w,
705                "{}",
706                ctx.failure(PlanError::unimplemented(
707                    "RexType",
708                    Some("WindowFunction"),
709                    "WindowFunction textification not implemented",
710                ))
711            ),
712            RexType::IfThen(_i) => write!(
713                w,
714                "{}",
715                ctx.failure(PlanError::unimplemented(
716                    "RexType",
717                    Some("IfThen"),
718                    "IfThen textification not implemented",
719                ))
720            ),
721            RexType::SwitchExpression(_s) => write!(
722                w,
723                "{}",
724                ctx.failure(PlanError::unimplemented(
725                    "RexType",
726                    Some("SwitchExpression"),
727                    "SwitchExpression textification not implemented",
728                ))
729            ),
730            RexType::SingularOrList(_s) => write!(
731                w,
732                "{}",
733                ctx.failure(PlanError::unimplemented(
734                    "RexType",
735                    Some("SingularOrList"),
736                    "SingularOrList textification not implemented",
737                ))
738            ),
739            RexType::MultiOrList(_m) => write!(
740                w,
741                "{}",
742                ctx.failure(PlanError::unimplemented(
743                    "RexType",
744                    Some("MultiOrList"),
745                    "MultiOrList textification not implemented",
746                ))
747            ),
748            RexType::Cast(_c) => write!(
749                w,
750                "{}",
751                ctx.failure(PlanError::unimplemented(
752                    "RexType",
753                    Some("Cast"),
754                    "Cast textification not implemented",
755                ))
756            ),
757            RexType::Subquery(_s) => write!(
758                w,
759                "{}",
760                ctx.failure(PlanError::unimplemented(
761                    "RexType",
762                    Some("Subquery"),
763                    "Subquery textification not implemented",
764                ))
765            ),
766            RexType::Nested(_n) => write!(
767                w,
768                "{}",
769                ctx.failure(PlanError::unimplemented(
770                    "RexType",
771                    Some("Nested"),
772                    "Nested textification not implemented",
773                ))
774            ),
775            RexType::DynamicParameter(_d) => write!(
776                w,
777                "{}",
778                ctx.failure(PlanError::unimplemented(
779                    "RexType",
780                    Some("DynamicParameter"),
781                    "DynamicParameter textification not implemented",
782                ))
783            ),
784            #[allow(deprecated)]
785            RexType::Enum(_) => write!(
786                w,
787                "{}",
788                ctx.failure(PlanError::unimplemented(
789                    "RexType",
790                    Some("Enum"),
791                    "Enum textification not implemented",
792                ))
793            ),
794        }
795    }
796}
797
798impl Textify for Expression {
799    fn name() -> &'static str {
800        "Expression"
801    }
802
803    fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
804        write!(w, "{}", ctx.expect(self.rex_type.as_ref()))
805    }
806}
807
808impl Textify for AggregateFunction {
809    fn name() -> &'static str {
810        "AggregateFunction"
811    }
812
813    fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
814        // Similar to ScalarFunction textification
815        let name_and_anchor = crate::textify::types::NamedAnchor::lookup(
816            ctx,
817            crate::extensions::simple::ExtensionKind::Function,
818            self.function_reference,
819        );
820        let name_and_anchor = ctx.display(&name_and_anchor);
821
822        let args = ctx.separated(&self.arguments, ", ");
823        let options = ctx.separated(&self.options, ", ");
824        let between = if self.arguments.is_empty() || self.options.is_empty() {
825            ""
826        } else {
827            ", "
828        };
829
830        let output = crate::textify::types::OutputType(self.output_type.as_ref());
831        let output_type = ctx.optional(&output, ctx.options().fn_types);
832
833        write!(
834            w,
835            "{name_and_anchor}({args}{between}{options}){output_type}"
836        )
837    }
838}
839
840#[cfg(test)]
841mod tests {
842    use super::*;
843    use crate::extensions::simple::{ExtensionKind, MissingReference};
844    use crate::fixtures::TestContext;
845    use crate::textify::foundation::FormatError;
846
847    #[test]
848    fn test_literal_textify() {
849        let ctx = TestContext::new();
850
851        let literal = LiteralType::Boolean(true);
852        assert_eq!(ctx.textify_no_errors(&literal), "true");
853    }
854
855    #[test]
856    fn test_expression_textify() {
857        let ctx = TestContext::new();
858
859        // Test empty expression
860        let expr_empty = Expression { rex_type: None }; // Renamed to avoid conflict
861        let (s, errs) = ctx.textify(&expr_empty);
862        assert!(!errs.is_empty());
863        assert_eq!(s, "!{RexType}");
864
865        // Test literal expression
866        let expr_lit = Expression {
867            rex_type: Some(RexType::Literal(expr::Literal {
868                nullable: false,
869                type_variation_reference: 0,
870                literal_type: Some(expr::literal::LiteralType::Boolean(true)),
871            })),
872        };
873        assert_eq!(ctx.textify_no_errors(&expr_lit), "true");
874    }
875
876    #[test]
877    fn test_rextype_textify() {
878        let ctx = TestContext::new();
879
880        let func = RexType::ScalarFunction(ScalarFunction {
881            function_reference: 1000, // Does not exist
882            arguments: vec![],
883            options: vec![],
884            output_type: None,
885            #[allow(deprecated)]
886            args: vec![],
887        });
888        // With strict=false (default in OutputOptions), it should format as unknown
889        // If strict=true, it would be an error.
890        // Assuming default OutputOptions has strict = false.
891        // ScopedContext.options() has strict. Default OutputOptions has strict: false.
892        let (s, errq) = ctx.textify(&func);
893        let errs: Vec<_> = errq.0;
894        match errs[0] {
895            FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => {
896                assert_eq!(k, ExtensionKind::Function);
897                assert_eq!(a, 1000);
898            }
899            _ => panic!("Expected Lookup MissingAnchor: {}", errs[0]),
900        }
901        assert_eq!(s, "!{function}#1000()");
902
903        let ctx = ctx.with_urn(1, "first").with_function(1, 100, "first");
904        let func = RexType::ScalarFunction(ScalarFunction {
905            function_reference: 100,
906            arguments: vec![],
907            options: vec![],
908            output_type: None,
909            #[allow(deprecated)]
910            args: vec![],
911        });
912        let s = ctx.textify_no_errors(&func);
913        assert_eq!(s, "first()");
914
915        // Test for duplicated function name requiring anchor
916        let options_show_anchor = Default::default();
917
918        let ctx = TestContext::new()
919            .with_options(options_show_anchor)
920            .with_urn(1, "somewhere_on_the_internet")
921            .with_urn(2, "somewhere_else")
922            .with_function(1, 231, "duplicated")
923            .with_function(2, 232, "duplicated");
924
925        let rex_dup = RexType::ScalarFunction(ScalarFunction {
926            function_reference: 231,
927            arguments: vec![FunctionArgument {
928                arg_type: Some(ArgType::Value(Expression {
929                    rex_type: Some(RexType::Literal(expr::Literal {
930                        nullable: false,
931                        type_variation_reference: 0,
932                        literal_type: Some(expr::literal::LiteralType::Boolean(true)),
933                    })),
934                })),
935            }],
936            options: vec![],
937            output_type: None,
938            #[allow(deprecated)]
939            args: vec![],
940        });
941        let s = ctx.textify_no_errors(&rex_dup);
942        assert_eq!(s, "duplicated#231(true)");
943    }
944}