Skip to main content

substrait_explain/
lib.rs

1#![doc = include_str!("../API.md")]
2
3// Used as links in API.md
4#[cfg(doc)]
5pub use extensions::{AnyConvertible, Explainable, ExtensionRegistry};
6
7pub mod extensions;
8pub mod grammar;
9mod parser;
10mod textify;
11
12#[cfg(test)]
13mod fixtures;
14
15#[cfg(test)]
16mod types_tests;
17
18#[cfg(feature = "cli")]
19pub mod cli;
20#[cfg(feature = "cli")]
21pub mod json;
22
23// Re-export commonly used types for easier access
24pub use parser::{
25    ExpectedExtensionLine, ExtensionParseError, MessageParseError, ParseContext, ParseError,
26    ParseResult, Parser,
27};
28use substrait::proto::Plan;
29use textify::foundation::ErrorQueue;
30pub use textify::foundation::{FormatError, FormatErrorType, OutputOptions, PlanError, Visibility};
31use textify::plan::PlanWriter;
32
33/// Parse a Substrait plan from text format.
34///
35/// This is the main entry point for parsing well-formed plans.
36/// Returns a clear error if parsing fails.
37///
38/// The input should be in the Substrait text format, which consists of:
39/// - An optional extensions section starting with "=== Extensions"
40/// - A plan section starting with "=== Plan"
41/// - Indented relation definitions
42///
43/// # Example
44/// ```rust
45/// use substrait_explain::parse;
46///
47/// let plan_text = r#"
48/// === Plan
49/// Root[c, d]
50///   Project[$1, 42]
51///     Read[schema.table => a:i64, b:string?]
52/// "#;
53///
54/// // Parse the plan. Builds a complete Substrait plan.
55/// let plan = parse(plan_text).unwrap();
56/// ```
57///
58/// # Errors
59///
60/// Returns a `ParseError` if the input cannot be parsed as a valid Substrait plan.
61/// The error includes details about what went wrong and where in the input.
62///
63/// ```rust
64/// use substrait_explain::parse;
65///
66/// let invalid_plan = r#"
67/// === Plan
68/// InvalidRelation[invalid syntax]
69/// "#;
70///
71/// match parse(invalid_plan) {
72///     Ok(_) => println!("Valid plan"),
73///     Err(e) => println!("Parse error: {}", e),
74/// }
75/// ```
76pub fn parse(input: &str) -> Result<Plan, ParseError> {
77    parser::Parser::parse(input)
78}
79
80/// Parse a Substrait plan from text format with a custom extension registry.
81///
82/// Use this when the plan contains custom extensions registered via
83/// [`extensions::ExtensionRegistry`]. This is the parsing counterpart to
84/// [`format_with_registry`].
85pub fn parse_with_registry(
86    input: &str,
87    registry: &extensions::ExtensionRegistry,
88) -> Result<Plan, ParseError> {
89    parser::Parser::new()
90        .with_extension_registry(registry.clone())
91        .parse_plan(input)
92}
93
94/// Format a Substrait plan as human-readable text.
95///
96/// This is the main entry point for formatting plans. It uses default
97/// formatting options that produce concise, readable output.
98///
99/// Returns a tuple of `(formatted_text, errors)`. The text is always generated,
100/// even if there are formatting errors. Errors are collected and returned for
101/// inspection.
102///
103/// # Example
104/// ```rust
105/// use substrait_explain::{parse, format};
106/// use substrait::proto::Plan;
107///
108/// let plan: Plan = parse(r#"
109/// === Plan
110/// Root[result]
111///   Project[$0, $1]
112///     Read[data => a:i64, b:string]
113/// "#).unwrap();
114///
115/// let (text, errors) = format(&plan);
116/// println!("{}", text);
117///
118/// if !errors.is_empty() {
119///     println!("Formatting warnings: {:?}", errors);
120/// }
121/// ```
122///
123/// # Output Format
124///
125/// The output follows the Substrait text format specification, with relations
126/// displayed in a hierarchical structure using indentation.
127pub fn format(plan: &Plan) -> (String, Vec<FormatError>) {
128    let options = OutputOptions::default();
129    format_with_options(plan, &options)
130}
131
132/// Format a Substrait plan with custom options.
133///
134/// This function allows you to customize the formatting behavior, such as
135/// showing more or less detail, changing indentation, or controlling
136/// type visibility.
137///
138/// # Example
139/// ```rust
140/// use substrait_explain::{parse, format_with_options, OutputOptions, Visibility};
141///
142/// let plan = parse(r#"
143/// === Plan
144/// Root[result]
145///   Project[$0, 42]
146///     Read[data => a:i64]
147/// "#).unwrap();
148///
149/// // Use verbose formatting
150/// let verbose_options = OutputOptions::verbose();
151/// let (text, _errors) = format_with_options(&plan, &verbose_options);
152/// println!("Verbose output:\n{}", text);
153///
154/// // Custom options
155/// let custom_options = OutputOptions {
156///     literal_types: Visibility::Always,
157///     indent: "    ".to_string(),
158///     ..OutputOptions::default()
159/// };
160/// let (text, _errors) = format_with_options(&plan, &custom_options);
161/// println!("Custom output:\n{}", text);
162/// ```
163///
164/// # Options
165///
166/// See [`OutputOptions`] for all available configuration options.
167pub fn format_with_options(plan: &Plan, options: &OutputOptions) -> (String, Vec<FormatError>) {
168    let default_registry = extensions::ExtensionRegistry::default();
169    format_with_registry(plan, options, &default_registry)
170}
171
172/// Format a Substrait plan with custom options and an extension registry.
173///
174/// This function allows you to provide a custom extension registry for handling
175/// extension relations, enhancement addenda, and optimization addenda.
176///
177/// # Example
178/// ```rust
179/// use substrait_explain::extensions::examples;
180/// use substrait_explain::{format_with_registry, OutputOptions, Parser};
181///
182/// let registry = examples::registry();
183/// let parser = Parser::new().with_extension_registry(registry.clone());
184/// let plan = parser.parse_plan(r#"
185/// === Plan
186/// Root[id, payload]
187///   Read:Extension[id:i64, payload:string]
188///     + Ext:BlobStoreRead['path/to/file', limit=100]
189/// "#).unwrap();
190///
191/// let (text, errors) = format_with_registry(&plan, &OutputOptions::default(), &registry);
192/// assert!(errors.is_empty());
193/// assert!(text.contains("BlobStoreRead"));
194/// ```
195pub fn format_with_registry(
196    plan: &Plan,
197    options: &OutputOptions,
198    registry: &extensions::ExtensionRegistry,
199) -> (String, Vec<FormatError>) {
200    let (writer, error_queue) = PlanWriter::<ErrorQueue>::new(options, plan, registry);
201    let output = format!("{writer}");
202    let errors = error_queue.into_iter().collect();
203    (output, errors)
204}