Skip to main content

substrait_explain/textify/
foundation.rs

1//! Foundation types for textifying a plan.
2
3use std::borrow::Cow;
4use std::fmt;
5use std::rc::Rc;
6use std::sync::mpsc;
7
8use thiserror::Error;
9
10use crate::extensions::registry::ExtensionError;
11use crate::extensions::simple::MissingReference;
12use crate::extensions::{ExtensionRegistry, InsertError, SimpleExtensions};
13
14pub(crate) const NONSPECIFIC: Option<&'static str> = None;
15
16#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
17pub enum Visibility {
18    /// Never show the information
19    Never,
20    /// Show the information if it is required for the output to be complete.
21    Required,
22    /// Show it always.
23    Always,
24}
25
26/// OutputOptions holds the options for textifying a Substrait type.
27#[derive(Debug, Clone)]
28pub struct OutputOptions {
29    /// Show the extension URNs in the output.
30    pub show_extension_urns: bool,
31    /// Show the extensions in the output. By default, simple extensions are
32    /// expanded into the input.
33    pub show_simple_extensions: bool,
34    /// Show the anchors of simple extensions in the output, and not just their
35    /// names.
36    ///
37    /// If `Required`, the anchor is shown for all simple extensions.
38    pub show_simple_extension_anchors: Visibility,
39    /// Instead of showing the emitted columns inline, show the emits directly.
40    pub show_emit: bool,
41
42    /// Show the types for columns in a read
43    pub read_types: bool,
44    /// Show the types for literals. If `Required`, the type is shown for anything other than
45    /// `i64`, `fp64`, `boolean`, or `string`.
46    pub literal_types: Visibility,
47    /// Show the nullability of types
48    pub nullability: bool,
49    /// The indent to use for nested types
50    pub indent: String,
51    /// Show the binary values for literal types as hex strings. Normally, they
52    /// are shown as '{{binary}}'
53    pub show_literal_binaries: bool,
54}
55
56impl Default for OutputOptions {
57    fn default() -> Self {
58        Self {
59            show_extension_urns: false,
60            show_simple_extensions: false,
61            show_simple_extension_anchors: Visibility::Required,
62            literal_types: Visibility::Required,
63            show_emit: false,
64            read_types: false,
65            nullability: false,
66            indent: "  ".to_string(),
67            show_literal_binaries: false,
68        }
69    }
70}
71
72impl OutputOptions {
73    /// A verbose output options that shows all the necessary information for
74    /// reconstructing a plan.
75    pub fn verbose() -> Self {
76        Self {
77            show_extension_urns: true,
78            show_simple_extensions: true,
79            show_simple_extension_anchors: Visibility::Always,
80            literal_types: Visibility::Always,
81            // Emits are not required for a complete plan - just not a precise one.
82            show_emit: false,
83            read_types: true,
84            nullability: true,
85            indent: "  ".to_string(),
86            show_literal_binaries: true,
87        }
88    }
89}
90pub(crate) trait ErrorAccumulator: Clone {
91    fn push(&self, e: FormatError);
92}
93
94#[derive(Debug, Clone)]
95pub(crate) struct ErrorQueue {
96    sender: mpsc::Sender<FormatError>,
97    receiver: Rc<mpsc::Receiver<FormatError>>,
98}
99
100impl Default for ErrorQueue {
101    fn default() -> Self {
102        let (sender, receiver) = mpsc::channel();
103        Self {
104            sender,
105            receiver: Rc::new(receiver),
106        }
107    }
108}
109
110impl From<ErrorQueue> for Vec<FormatError> {
111    fn from(v: ErrorQueue) -> Vec<FormatError> {
112        v.receiver.try_iter().collect()
113    }
114}
115
116impl ErrorAccumulator for ErrorQueue {
117    fn push(&self, e: FormatError) {
118        self.sender.send(e).unwrap();
119    }
120}
121
122impl fmt::Display for ErrorQueue {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        for (i, e) in self.receiver.try_iter().enumerate() {
125            if i == 0 {
126                writeln!(f, "Warnings during conversion:")?;
127            }
128            let error_number = i + 1;
129            writeln!(f, "  - {error_number}: {e}")?;
130        }
131        Ok(())
132    }
133}
134
135impl ErrorQueue {
136    #[cfg(test)]
137    pub(crate) fn errs(self) -> Result<(), ErrorList> {
138        let errors: Vec<FormatError> = self.receiver.try_iter().collect();
139        if errors.is_empty() {
140            Ok(())
141        } else {
142            Err(ErrorList(errors))
143        }
144    }
145}
146
147// A list of errors, used to return errors from textify operations.
148#[cfg(test)]
149pub(crate) struct ErrorList(pub(crate) Vec<FormatError>);
150
151#[cfg(test)]
152impl ErrorList {
153    pub(crate) fn first(&self) -> &FormatError {
154        self.0
155            .first()
156            .expect("Expected at least one error in ErrorList")
157    }
158
159    pub(crate) fn is_empty(&self) -> bool {
160        self.0.is_empty()
161    }
162}
163
164#[cfg(test)]
165impl fmt::Display for ErrorList {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        for (i, e) in self.0.iter().enumerate() {
168            if i > 0 {
169                writeln!(f)?;
170            }
171            write!(f, "{e}")?;
172        }
173        Ok(())
174    }
175}
176
177#[cfg(test)]
178impl fmt::Debug for ErrorList {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        for (i, e) in self.0.iter().enumerate() {
181            if i == 0 {
182                writeln!(f, "Errors:")?;
183            }
184            writeln!(f, "! {e:?}")?;
185        }
186        Ok(())
187    }
188}
189
190#[cfg(test)]
191impl std::error::Error for ErrorList {}
192
193impl<'e> IntoIterator for &'e ErrorQueue {
194    type Item = FormatError;
195    type IntoIter = std::sync::mpsc::TryIter<'e, FormatError>;
196
197    fn into_iter(self) -> Self::IntoIter {
198        self.receiver.try_iter()
199    }
200}
201
202pub(crate) trait IndentTracker {
203    // TODO: Use this and remove the allow(dead_code)
204    #[allow(dead_code)]
205    fn indent<W: fmt::Write>(&self, w: &mut W) -> fmt::Result;
206    fn push(self) -> Self;
207}
208
209#[derive(Debug, Copy, Clone)]
210pub(crate) struct IndentStack<'a> {
211    count: u32,
212    indent: &'a str,
213}
214
215impl<'a> fmt::Display for IndentStack<'a> {
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        for _ in 0..self.count {
218            f.write_str(self.indent)?;
219        }
220        Ok(())
221    }
222}
223
224#[derive(Debug, Copy, Clone)]
225pub(crate) struct ScopedContext<'a, Err: ErrorAccumulator> {
226    errors: &'a Err,
227    options: &'a OutputOptions,
228    extensions: &'a SimpleExtensions,
229    indent: IndentStack<'a>,
230    extension_registry: &'a ExtensionRegistry,
231}
232
233impl<'a> IndentStack<'a> {
234    pub(crate) fn new(indent: &'a str) -> Self {
235        Self { count: 0, indent }
236    }
237}
238
239impl<'a> IndentTracker for IndentStack<'a> {
240    fn indent<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
241        for _ in 0..self.count {
242            w.write_str(self.indent)?;
243        }
244        Ok(())
245    }
246
247    fn push(mut self) -> Self {
248        self.count += 1;
249        self
250    }
251}
252
253impl<'a, Err: ErrorAccumulator> ScopedContext<'a, Err> {
254    pub(crate) fn new(
255        options: &'a OutputOptions,
256        errors: &'a Err,
257        extensions: &'a SimpleExtensions,
258        extension_registry: &'a ExtensionRegistry,
259    ) -> Self {
260        Self {
261            options,
262            errors,
263            extensions,
264            indent: IndentStack::new(options.indent.as_str()),
265            extension_registry,
266        }
267    }
268}
269
270/// Errors that can occur when formatting a plan.
271#[derive(Error, Debug, Clone)]
272pub enum FormatError {
273    /// Error in adding extensions to the plan - e.g. duplicates, invalid URN
274    /// references, etc.
275    #[error("Error adding simple extension: {0}")]
276    Insert(#[from] InsertError),
277    /// Error in looking up an extension - e.g. missing URN, anchor, name, etc.
278    #[error("Error finding simple extension: {0}")]
279    Lookup(#[from] MissingReference),
280    /// Error in extension registry operations - e.g. extension not found, parse errors, etc.
281    #[error("Extension error: {0}")]
282    Extension(#[from] ExtensionError),
283    /// Error in formatting the plan - e.g. invalid value, unimplemented, etc.
284    #[error("Error formatting output: {0}")]
285    Format(#[from] PlanError),
286}
287
288impl FormatError {
289    pub fn message(&self) -> &'static str {
290        match self {
291            FormatError::Lookup(MissingReference::MissingUrn(_)) => "uri",
292            FormatError::Lookup(MissingReference::MissingAnchor(k, _)) => k.name(),
293            FormatError::Lookup(MissingReference::MissingName(k, _)) => k.name(),
294            FormatError::Lookup(MissingReference::Mismatched(k, _, _)) => k.name(),
295            FormatError::Lookup(MissingReference::DuplicateName(k, _)) => k.name(),
296            FormatError::Extension(_) => "extension",
297            FormatError::Format(m) => m.message,
298            FormatError::Insert(InsertError::MissingMappingType) => "extension",
299            FormatError::Insert(InsertError::DuplicateUrnAnchor { .. }) => "uri",
300            FormatError::Insert(InsertError::DuplicateAnchor { .. }) => "extension",
301            FormatError::Insert(InsertError::MissingUrn { .. }) => "uri",
302            FormatError::Insert(InsertError::DuplicateAndMissingUrn { .. }) => "uri",
303        }
304    }
305}
306
307#[derive(Debug, Clone)]
308pub struct PlanError {
309    // The message this originated in
310    pub message: &'static str,
311    // The specific lookup that failed, if specifiable
312    pub lookup: Option<Cow<'static, str>>,
313    // The description of the error
314    pub description: Cow<'static, str>,
315    // The error type
316    pub error_type: FormatErrorType,
317}
318
319impl PlanError {
320    pub fn invalid(
321        message: &'static str,
322        specific: Option<impl Into<Cow<'static, str>>>,
323        description: impl Into<Cow<'static, str>>,
324    ) -> Self {
325        Self {
326            message,
327            lookup: specific.map(|s| s.into()),
328            description: description.into(),
329            error_type: FormatErrorType::InvalidValue,
330        }
331    }
332
333    pub fn unimplemented(
334        message: &'static str,
335        specific: Option<impl Into<Cow<'static, str>>>,
336        description: impl Into<Cow<'static, str>>,
337    ) -> Self {
338        Self {
339            message,
340            lookup: specific.map(|s| s.into()),
341            description: description.into(),
342            error_type: FormatErrorType::Unimplemented,
343        }
344    }
345
346    pub fn internal(
347        message: &'static str,
348        specific: Option<impl Into<Cow<'static, str>>>,
349        description: impl Into<Cow<'static, str>>,
350    ) -> Self {
351        Self {
352            message,
353            lookup: specific.map(|s| s.into()),
354            description: description.into(),
355            error_type: FormatErrorType::Internal,
356        }
357    }
358}
359
360#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
361pub enum FormatErrorType {
362    InvalidValue,
363    Unimplemented,
364    Internal,
365}
366
367impl fmt::Display for FormatErrorType {
368    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369        match self {
370            FormatErrorType::InvalidValue => write!(f, "InvalidValue"),
371            FormatErrorType::Unimplemented => write!(f, "Unimplemented"),
372            FormatErrorType::Internal => write!(f, "Internal"),
373        }
374    }
375}
376
377impl std::error::Error for PlanError {}
378
379impl fmt::Display for PlanError {
380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381        write!(
382            f,
383            "{} Error writing {}: {}",
384            self.error_type, self.message, self.description
385        )
386    }
387}
388
389#[derive(Debug, Copy, Clone)]
390/// A token used to represent an error in the textified output.
391pub(crate) struct ErrorToken(
392    /// The kind of item this is in place of.
393    pub &'static str,
394);
395
396impl fmt::Display for ErrorToken {
397    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398        write!(f, "!{{{}}}", self.0)
399    }
400}
401
402#[derive(Debug, Copy, Clone)]
403pub(crate) struct MaybeToken<V: fmt::Display>(pub(crate) Result<V, ErrorToken>);
404
405impl<V: fmt::Display> fmt::Display for MaybeToken<V> {
406    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407        match &self.0 {
408            Ok(t) => t.fmt(f),
409            Err(e) => e.fmt(f),
410        }
411    }
412}
413
414/// A trait for types that can be output in text format.
415///
416/// This trait is used to convert a Substrait type into a string representation
417/// that can be written to a text writer.
418pub(crate) trait Textify {
419    fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result;
420
421    // TODO: Prost can give this to us if substrait was generated with `enable_type_names`
422    // <https://docs.rs/prost-build/latest/prost_build/struct.Config.html#method.enable_type_names>
423    fn name() -> &'static str;
424}
425
426/// Holds the context for outputting a plan.
427///
428/// This includes:
429/// - Options (`&`[`OutputOptions`]) for formatting
430/// - Errors (`&`[`ErrorAccumulator`]) for collecting errors
431/// - Extensions (`&`[`SimpleExtensions`]) for looking up function and type
432///   names
433/// - Indent (`&`[`IndentTracker`]) for tracking the current indent level
434///
435/// The `Scope` trait is used to provide the context for textifying a plan.
436pub(crate) trait Scope: Sized {
437    /// The type of errors that can occur when textifying a plan.
438    type Errors: ErrorAccumulator;
439    type Indent: IndentTracker;
440
441    /// Get the current indent level.
442    fn indent(&self) -> impl fmt::Display;
443    /// Push a new indent level.
444    fn push_indent(&self) -> Self;
445
446    /// Get the options for formatting the plan.
447    fn options(&self) -> &OutputOptions;
448    fn extensions(&self) -> &SimpleExtensions;
449
450    /// Get the extension registry
451    fn extension_registry(&self) -> &ExtensionRegistry;
452    fn errors(&self) -> &Self::Errors;
453
454    fn push_error(&self, e: FormatError) {
455        self.errors().push(e);
456    }
457
458    /// Handle a failure to textify a value. Textify errors are written as
459    /// "!{name}" to the output (where "name" is the type name), and
460    /// the error is pushed to the error accumulator.
461    fn failure<E: Into<FormatError>>(&self, e: E) -> ErrorToken {
462        let e = e.into();
463        let token = ErrorToken(e.message());
464        self.push_error(e);
465        token
466    }
467
468    fn expect<'a, T: Textify>(&'a self, t: Option<&'a T>) -> MaybeToken<impl fmt::Display> {
469        match t {
470            Some(t) => MaybeToken(Ok(self.display(t))),
471            None => {
472                let err = PlanError::invalid(
473                    T::name(),
474                    // TODO: Make this an optional input
475                    NONSPECIFIC,
476                    "Required field expected, None found",
477                );
478                let err_token = self.failure(err);
479                MaybeToken(Err(err_token))
480            }
481        }
482    }
483
484    fn display<'a, T: Textify>(&'a self, value: &'a T) -> Displayable<'a, Self, T> {
485        Displayable { scope: self, value }
486    }
487
488    /// Wrap an iterator over textifiable items into a displayable that will
489    /// separate them with the given separator.
490    ///
491    /// # Example
492    ///
493    /// ```ignore
494    /// let items = vec![1, 2, 3];
495    /// let separated = self.separated(&items, ", ");
496    /// ```
497    fn separated<'a, T: Textify, I: IntoIterator<Item = &'a T> + Clone>(
498        &'a self,
499        items: I,
500        separator: &'static str,
501    ) -> Separated<'a, Self, T, I> {
502        Separated {
503            scope: self,
504            items,
505            separator,
506        }
507    }
508
509    // TODO: this was last used on 2026-06-15, before a refactor removed it; if its not used again, perhaps we should drop it?
510    #[allow(dead_code)]
511    fn optional<'a, T: Textify>(
512        &'a self,
513        value: &'a T,
514        option: bool,
515    ) -> OptionalDisplayable<'a, Self, T> {
516        let value = if option { Some(value) } else { None };
517        OptionalDisplayable { scope: self, value }
518    }
519}
520
521#[derive(Clone)]
522pub(crate) struct Separated<'a, S: Scope, T: Textify + 'a, I: IntoIterator<Item = &'a T> + Clone> {
523    scope: &'a S,
524    items: I,
525    separator: &'static str,
526}
527
528impl<'a, S: Scope, T: Textify, I: IntoIterator<Item = &'a T> + Clone> fmt::Display
529    for Separated<'a, S, T, I>
530{
531    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532        for (i, item) in self.items.clone().into_iter().enumerate() {
533            if i > 0 {
534                f.write_str(self.separator)?;
535            }
536            item.textify(self.scope, f)?;
537        }
538        Ok(())
539    }
540}
541
542impl<'a, S: Scope, T: Textify, I: IntoIterator<Item = &'a T> + Clone + fmt::Debug> fmt::Debug
543    for Separated<'a, S, T, I>
544{
545    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
546        write!(
547            f,
548            "Separated{{items: {:?}, separator: {:?}}}",
549            self.items, self.separator
550        )
551    }
552}
553
554#[derive(Copy, Clone)]
555pub(crate) struct Displayable<'a, S: Scope, T: Textify> {
556    scope: &'a S,
557    value: &'a T,
558}
559
560impl<'a, S: Scope, T: Textify + fmt::Debug> fmt::Debug for Displayable<'a, S, T> {
561    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562        write!(f, "Displayable({:?})", self.value)
563    }
564}
565
566impl<'a, S: Scope, T: Textify> fmt::Display for Displayable<'a, S, T> {
567    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
568        self.value.textify(self.scope, f)
569    }
570}
571
572#[derive(Copy, Clone)]
573#[allow(dead_code)]
574pub(crate) struct OptionalDisplayable<'a, S: Scope, T: Textify> {
575    scope: &'a S,
576    value: Option<&'a T>,
577}
578
579impl<'a, S: Scope, T: Textify> fmt::Display for OptionalDisplayable<'a, S, T> {
580    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
581        match &self.value {
582            Some(t) => t.textify(self.scope, f),
583            None => Ok(()),
584        }
585    }
586}
587
588impl<'a, S: Scope, T: Textify + fmt::Debug> fmt::Debug for OptionalDisplayable<'a, S, T> {
589    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590        write!(f, "OptionalDisplayable({:?})", self.value)
591    }
592}
593
594impl<'a, Err: ErrorAccumulator> Scope for ScopedContext<'a, Err> {
595    type Errors = Err;
596    type Indent = IndentStack<'a>;
597
598    fn indent(&self) -> impl fmt::Display {
599        self.indent
600    }
601
602    fn push_indent(&self) -> Self {
603        Self {
604            indent: self.indent.push(),
605            ..*self
606        }
607    }
608
609    fn options(&self) -> &OutputOptions {
610        self.options
611    }
612
613    fn errors(&self) -> &Self::Errors {
614        self.errors
615    }
616
617    fn extensions(&self) -> &SimpleExtensions {
618        self.extensions
619    }
620
621    fn extension_registry(&self) -> &ExtensionRegistry {
622        self.extension_registry
623    }
624}