substrait-explain 0.7.0

Explain Substrait plans as human-readable text.
Documentation
#![doc = include_str!("../API.md")]

// Used as links in API.md
#[cfg(doc)]
pub use extensions::{AnyConvertible, Explainable, ExtensionRegistry};

pub mod extensions;
pub mod grammar;
mod parser;
mod textify;

#[cfg(test)]
mod fixtures;

#[cfg(test)]
mod types_tests;

#[cfg(feature = "cli")]
pub mod cli;
#[cfg(feature = "cli")]
pub mod json;

// Re-export commonly used types for easier access
pub use parser::{
    ExpectedExtensionLine, ExtensionParseError, MessageParseError, ParseContext, ParseError,
    ParseResult, Parser,
};
use substrait::proto::Plan;
use textify::foundation::ErrorQueue;
pub use textify::foundation::{FormatError, FormatErrorType, OutputOptions, PlanError, Visibility};
use textify::plan::PlanWriter;

/// Parse a Substrait plan from text format.
///
/// This is the main entry point for parsing well-formed plans.
/// Returns a clear error if parsing fails.
///
/// The input should be in the Substrait text format, which consists of:
/// - An optional extensions section starting with "=== Extensions"
/// - A plan section starting with "=== Plan"
/// - Indented relation definitions
///
/// # Example
/// ```rust
/// use substrait_explain::parse;
///
/// let plan_text = r#"
/// === Plan
/// Root[c, d]
///   Project[$1, 42]
///     Read[schema.table => a:i64, b:string?]
/// "#;
///
/// // Parse the plan. Builds a complete Substrait plan.
/// let plan = parse(plan_text).unwrap();
/// ```
///
/// # Errors
///
/// Returns a `ParseError` if the input cannot be parsed as a valid Substrait plan.
/// The error includes details about what went wrong and where in the input.
///
/// ```rust
/// use substrait_explain::parse;
///
/// let invalid_plan = r#"
/// === Plan
/// InvalidRelation[invalid syntax]
/// "#;
///
/// match parse(invalid_plan) {
///     Ok(_) => println!("Valid plan"),
///     Err(e) => println!("Parse error: {}", e),
/// }
/// ```
pub fn parse(input: &str) -> Result<Plan, ParseError> {
    parser::Parser::parse(input)
}

/// Parse a Substrait plan from text format with a custom extension registry.
///
/// Use this when the plan contains custom extensions registered via
/// [`extensions::ExtensionRegistry`]. This is the parsing counterpart to
/// [`format_with_registry`].
pub fn parse_with_registry(
    input: &str,
    registry: &extensions::ExtensionRegistry,
) -> Result<Plan, ParseError> {
    parser::Parser::new()
        .with_extension_registry(registry.clone())
        .parse_plan(input)
}

/// Format a Substrait plan as human-readable text.
///
/// This is the main entry point for formatting plans. It uses default
/// formatting options that produce concise, readable output.
///
/// Returns a tuple of `(formatted_text, errors)`. The text is always generated,
/// even if there are formatting errors. Errors are collected and returned for
/// inspection.
///
/// # Example
/// ```rust
/// use substrait_explain::{parse, format};
/// use substrait::proto::Plan;
///
/// let plan: Plan = parse(r#"
/// === Plan
/// Root[result]
///   Project[$0, $1]
///     Read[data => a:i64, b:string]
/// "#).unwrap();
///
/// let (text, errors) = format(&plan);
/// println!("{}", text);
///
/// if !errors.is_empty() {
///     println!("Formatting warnings: {:?}", errors);
/// }
/// ```
///
/// # Output Format
///
/// The output follows the Substrait text format specification, with relations
/// displayed in a hierarchical structure using indentation.
pub fn format(plan: &Plan) -> (String, Vec<FormatError>) {
    let options = OutputOptions::default();
    format_with_options(plan, &options)
}

/// Format a Substrait plan with custom options.
///
/// This function allows you to customize the formatting behavior, such as
/// showing more or less detail, changing indentation, or controlling
/// type visibility.
///
/// # Example
/// ```rust
/// use substrait_explain::{parse, format_with_options, OutputOptions, Visibility};
///
/// let plan = parse(r#"
/// === Plan
/// Root[result]
///   Project[$0, 42]
///     Read[data => a:i64]
/// "#).unwrap();
///
/// // Use verbose formatting
/// let verbose_options = OutputOptions::verbose();
/// let (text, _errors) = format_with_options(&plan, &verbose_options);
/// println!("Verbose output:\n{}", text);
///
/// // Custom options
/// let custom_options = OutputOptions {
///     literal_types: Visibility::Always,
///     indent: "    ".to_string(),
///     ..OutputOptions::default()
/// };
/// let (text, _errors) = format_with_options(&plan, &custom_options);
/// println!("Custom output:\n{}", text);
/// ```
///
/// # Options
///
/// See [`OutputOptions`] for all available configuration options.
pub fn format_with_options(plan: &Plan, options: &OutputOptions) -> (String, Vec<FormatError>) {
    let default_registry = extensions::ExtensionRegistry::default();
    format_with_registry(plan, options, &default_registry)
}

/// Format a Substrait plan with custom options and an extension registry.
///
/// This function allows you to provide a custom extension registry for handling
/// extension relations, enhancement addenda, and optimization addenda.
///
/// # Example
/// ```rust
/// use substrait_explain::extensions::examples;
/// use substrait_explain::{format_with_registry, OutputOptions, Parser};
///
/// let registry = examples::registry();
/// let parser = Parser::new().with_extension_registry(registry.clone());
/// let plan = parser.parse_plan(r#"
/// === Plan
/// Root[id, payload]
///   Read:Extension[id:i64, payload:string]
///     + Ext:BlobStoreRead['path/to/file', limit=100]
/// "#).unwrap();
///
/// let (text, errors) = format_with_registry(&plan, &OutputOptions::default(), &registry);
/// assert!(errors.is_empty());
/// assert!(text.contains("BlobStoreRead"));
/// ```
pub fn format_with_registry(
    plan: &Plan,
    options: &OutputOptions,
    registry: &extensions::ExtensionRegistry,
) -> (String, Vec<FormatError>) {
    let (writer, error_queue) = PlanWriter::<ErrorQueue>::new(options, plan, registry);
    let output = format!("{writer}");
    let errors = error_queue.into_iter().collect();
    (output, errors)
}