substrait_explain/textify/
expressions.rs

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