Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ cat plan.substrait | substrait-explain convert -f text -t json > plan.json
- `-t, --to <FORMAT>` - Output format (default: text)
- `-i, --input <FILE>` - Input file (default: stdin)
- `-o, --output <FILE>` - Output file (default: stdout)
- `--show-literal-types` - Show type annotations on literals
- `--detailed` - Show more detail on plans, including type annotations and plan version
- `--verbose` - Show detailed progress information

#### Validate Command
Expand Down Expand Up @@ -394,8 +394,8 @@ substrait-explain validate -i plan.substrait --verbose
substrait-explain validate -i example-plans/basic.substrait
substrait-explain validate -i example-plans/simple.substrait

# Convert with verbose output and type information
substrait-explain convert -f text -t json --show-literal-types --verbose -i example-plans/basic.substrait
# Convert with verbose output and full plan detail
substrait-explain convert -f text -t json --detailed --verbose -i example-plans/basic.substrait

# Roundtrip test: text → protobuf → text
substrait-explain convert -f text -t protobuf -i plan.substrait -o plan.pb
Expand Down
23 changes: 21 additions & 2 deletions GRAMMAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,14 @@ The header carries the version number as `major.minor.patch` (three
non-negative integers). The indented `producer:` and `git_hash:` lines are
optional and may appear in either order beneath the header.

The `=== Version` section as a whole is optional; a document with no version
section is valid and denotes a plan without a declared version.
The section is optional, and a document that leaves it out gets the Substrait
version `substrait-explain` was built against, with `substrait-explain` recorded
as the producer. `null` in place of the version number means the plan has no
version at all:

```text
=== Version null
```

```rust
# use substrait_explain::Parser;
Expand All @@ -136,6 +142,19 @@ Root[result]
# assert_eq!(version.producer, "my-optimizer");
```

```rust
# use substrait_explain::Parser;
#
# let plan_text = r#"
=== Version null
=== Plan
Root[result]
Read[orders => quantity:i32?]
# "#;
#
# Parser::parse(plan_text).unwrap();
```

#### Extension format

```text
Expand Down
62 changes: 31 additions & 31 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ use prost::Message;
use substrait::proto::Plan;

use crate::extensions::ExtensionRegistry;
use crate::{
FormatError, OutputOptions, Visibility, format_with_registry, json, parse_with_registry,
};
use crate::{FormatError, OutputOptions, format_with_registry, json, parse_with_registry};

/// The outcome of a CLI operation.
///
Expand Down Expand Up @@ -77,14 +75,14 @@ impl Cli {
output,
from,
to,
show_literal_types,
detailed,
verbose,
} => {
let reader = get_reader(input)
.with_context(|| format!("Failed to open input file: {input}"))?;
let writer = get_writer(output)
.with_context(|| format!("Failed to create output file: {output}"))?;
let options = self.create_output_options(*show_literal_types);
let options = self.create_output_options(*detailed);
let from_format = self.resolve_input_format(from, input)?;
let to_format = self.resolve_output_format(to, output)?;
self.run_convert_with_io(
Expand Down Expand Up @@ -125,11 +123,11 @@ impl Cli {
output,
from,
to,
show_literal_types,
detailed,
verbose,
..
} => {
let options = self.create_output_options(*show_literal_types);
let options = self.create_output_options(*detailed);
let from_format = self.resolve_input_format(from, input)?;
let to_format = self.resolve_output_format(to, output)?;
self.run_convert_with_io(
Expand All @@ -149,14 +147,12 @@ impl Cli {
}
}

fn create_output_options(&self, show_literal_types: bool) -> OutputOptions {
let mut options = OutputOptions::default();

if show_literal_types {
options.literal_types = Visibility::Always;
fn create_output_options(&self, detailed: bool) -> OutputOptions {
if detailed {
OutputOptions::verbose()
} else {
OutputOptions::default()
}

options
}

fn resolve_input_format(&self, format: &Option<Format>, input_path: &str) -> Result<Format> {
Expand Down Expand Up @@ -277,9 +273,9 @@ pub enum Commands {
/// Output format: text, json, yaml, protobuf/proto/pb (auto-detected from file extension if not specified)
#[arg(short = 't', long)]
to: Option<Format>,
/// Show literal types (text output only)
/// Show more detail on plans, including type annotations and plan version
#[arg(long)]
show_literal_types: bool,
detailed: bool,
/// Verbose output
#[arg(short, long)]
verbose: bool,
Expand Down Expand Up @@ -515,7 +511,7 @@ Root[result]
output: "output.substrait".to_string(),
from: Some(Format::Text),
to: Some(Format::Text),
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand All @@ -541,7 +537,7 @@ Root[result]
output: "output.json".to_string(),
from: Some(Format::Text),
to: Some(Format::Json),
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand All @@ -568,7 +564,7 @@ Root[result]
output: "output.json".to_string(),
from: Some(Format::Text),
to: Some(Format::Json),
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand All @@ -587,7 +583,7 @@ Root[result]
output: "output.substrait".to_string(),
from: Some(Format::Json),
to: Some(Format::Text),
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand All @@ -612,7 +608,7 @@ Root[result]
output: "output.pb".to_string(),
from: Some(Format::Text),
to: Some(Format::Protobuf),
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand Down Expand Up @@ -685,17 +681,21 @@ Root[result]
output: "output.substrait".to_string(),
from: Some(Format::Text),
to: Some(Format::Text),
show_literal_types: true,
detailed: true,
verbose: false,
},
};

cli.run_with_io(input, &mut output, &ExtensionRegistry::default())
.unwrap();

// `--detailed` adds what the default output leaves out: the version
// section, read types, and nullability.
let output_content = String::from_utf8(output).unwrap();
assert!(output_content.contains("=== Version"));
assert!(output_content.contains("=== Plan"));
assert!(output_content.contains("Root[result]"));
assert!(output_content.contains("Read[data => a:i64, b:string]"));
}

#[test]
Expand Down Expand Up @@ -738,7 +738,7 @@ Root[result]
output: "output.json".to_string(),
from: None, // Auto-detect from extension
to: None, // Auto-detect from extension
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand All @@ -764,7 +764,7 @@ Root[result]
output: "output.json".to_string(),
from: None, // Should fail auto-detection
to: None,
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand All @@ -790,7 +790,7 @@ Root[result]
output: "output.unknown".to_string(),
from: None,
to: None, // Should fail auto-detection
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand All @@ -816,7 +816,7 @@ Root[result]
output: "output.pb".to_string(), // Would auto-detect as Protobuf
from: Some(Format::Text), // Explicit override
to: Some(Format::Text), // Explicit override
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand All @@ -841,7 +841,7 @@ Root[result]
output: "output.pb".to_string(),
from: Some(Format::Text),
to: Some(Format::Protobuf),
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand All @@ -860,7 +860,7 @@ Root[result]
output: "output.substrait".to_string(),
from: Some(Format::Protobuf),
to: Some(Format::Text),
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand Down Expand Up @@ -951,7 +951,7 @@ Root[val]
output: "output.substrait".to_string(),
from: Some(Format::Text),
to: Some(Format::Text),
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand All @@ -974,7 +974,7 @@ Root[val]
output: "output.json".to_string(),
from: Some(Format::Text),
to: Some(Format::Json),
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand Down Expand Up @@ -1017,7 +1017,7 @@ Root[val]
output: "output.substrait".to_string(),
from: Some(Format::Text),
to: Some(Format::Text),
show_literal_types: false,
detailed: false,
verbose: false,
},
};
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub mod json;
// Re-export commonly used types for easier access
pub use parser::{
ExpectedExtensionLine, ExtensionParseError, MessageParseError, ParseContext, ParseError,
ParseResult, Parser,
ParseResult, Parser, default_plan_version,
};
use substrait::proto::Plan;
use textify::foundation::ErrorQueue;
Expand Down
4 changes: 2 additions & 2 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ pub(crate) use common::{
pub use errors::{ParseContext, ParseError, ParseResult};
pub use extensions::{ExpectedExtensionLine, ExtensionParseError};
pub(crate) use relations::RelationParsePair;
pub use structural::Parser;
pub(crate) use structural::{PLAN_HEADER, VERSION_HEADER};
pub(crate) use structural::{PLAN_HEADER, VERSION_HEADER, VERSION_NULL};
pub use structural::{Parser, default_plan_version};
Loading