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