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