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