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