Skip to main content

substrait_explain/parser/
structural.rs

1//! Parser for the structural part of the Substrait file format.
2//!
3//! This is the overall parser for parsing the text format. It is responsible
4//! for tracking which section of the file we are currently parsing, and parsing
5//! each line separately.
6
7use std::fmt;
8
9use pest::iterators::Pair;
10use substrait::proto::extensions::AdvancedExtension;
11use substrait::proto::{
12    AggregateRel, FetchRel, FilterRel, JoinRel, Plan, PlanRel, ProjectRel, ReadRel, Rel, RelRoot,
13    SortRel, plan_rel,
14};
15
16use crate::extensions::{AddendumKind, ExtensionRegistry, SimpleExtensions, simple};
17use crate::parser::common::{MessageParseError, ParsePair, ScopedParsePair};
18use crate::parser::errors::{ParseContext, ParseError, ParseResult};
19use crate::parser::expressions::Name;
20use crate::parser::extensions::{
21    AddendumInvocation, ExtensionInvocation, ExtensionParseError, ExtensionParser,
22};
23use crate::parser::relations::{RelationParsingContext, VirtualReadRel};
24use crate::parser::{ErrorKind, ExpressionParser, RelationParsePair, Rule, unwrap_single_pair};
25
26pub const PLAN_HEADER: &str = "=== Plan";
27
28/// Represents an input line, trimmed of leading two-space indents and final
29/// whitespace. Contains the number of indents and the trimmed line.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct IndentedLine<'a>(pub usize, pub &'a str);
32
33impl<'a> From<&'a str> for IndentedLine<'a> {
34    fn from(line: &'a str) -> Self {
35        let line = line.trim_end();
36        let mut spaces = 0;
37        for c in line.chars() {
38            if c == ' ' {
39                spaces += 1;
40            } else {
41                break;
42            }
43        }
44
45        let indents = spaces / 2;
46
47        let (_, trimmed) = line.split_at(indents * 2);
48
49        IndentedLine(indents, trimmed)
50    }
51}
52
53/// A `+`-prefixed addendum line attached to a relation node. The `pair` holds
54/// the grammar rule directly (already unwrapped from the outer `planNode`).
55#[derive(Debug, Clone)]
56pub struct Addendum<'a> {
57    pub pair: Pair<'a, Rule>, // Rule::adv_extension
58    pub line_no: i64,
59}
60
61/// A relation node in the plan tree, before conversion to a Substrait proto.
62#[derive(Debug, Clone)]
63pub struct RelationNode<'a> {
64    pub pair: Pair<'a, Rule>,
65    pub line_no: i64,
66    pub addenda: Vec<Addendum<'a>>,
67    pub children: Vec<RelationNode<'a>>,
68}
69
70impl<'a> RelationNode<'a> {
71    pub fn context(&self) -> ParseContext {
72        ParseContext {
73            line_no: self.line_no,
74            line: self.pair.as_str().to_string(),
75        }
76    }
77}
78
79/// A parsed plan line: either a relation or a `+`-prefixed addendum line.
80///
81/// Classification happens at construction time by inspecting the inner grammar
82/// rule, so downstream code can use standard Rust pattern matching rather than
83/// runtime rule inspection.
84#[derive(Debug, Clone)]
85pub enum LineNode<'a> {
86    Relation(RelationNode<'a>),
87    Addendum(Addendum<'a>),
88}
89
90impl<'a> LineNode<'a> {
91    pub fn parse(line: &'a str, line_no: i64) -> Result<Self, ParseError> {
92        let mut pairs: pest::iterators::Pairs<'a, Rule> =
93            <ExpressionParser as pest::Parser<Rule>>::parse(Rule::planNode, line).map_err(|e| {
94                ParseError::Plan(
95                    ParseContext {
96                        line_no,
97                        line: line.to_string(),
98                    },
99                    MessageParseError::new("planNode", ErrorKind::InvalidValue, Box::new(e)),
100                )
101            })?;
102
103        let outer = pairs.next().unwrap();
104        assert!(pairs.next().is_none()); // Should be exactly one pair
105        let inner = unwrap_single_pair(outer);
106
107        Ok(match inner.as_rule() {
108            Rule::adv_extension => LineNode::Addendum(Addendum {
109                pair: inner,
110                line_no,
111            }),
112            _ => LineNode::Relation(RelationNode {
113                pair: inner,
114                line_no,
115                addenda: Vec::new(),
116                children: Vec::new(),
117            }),
118        })
119    }
120
121    /// Parse the line as a top-level relation at depth 0 (either root_relation or regular relation)
122    pub fn parse_root(line: &'a str, line_no: i64) -> Result<Self, ParseError> {
123        let mut pairs: pest::iterators::Pairs<'a, Rule> = <ExpressionParser as pest::Parser<
124            Rule,
125        >>::parse(
126            Rule::top_level_relation, line
127        )
128        .map_err(|e| {
129            ParseError::Plan(
130                ParseContext::new(line_no, line.to_string()),
131                MessageParseError::new("top_level_relation", ErrorKind::Syntax, Box::new(e)),
132            )
133        })?;
134
135        let outer = pairs.next().unwrap();
136        assert!(pairs.next().is_none());
137
138        // top_level_relation is either root_relation or planNode.
139        // If planNode, unwrap one more level to obtain the specific relation rule.
140        let inner = unwrap_single_pair(outer);
141        let pair = if inner.as_rule() == Rule::planNode {
142            unwrap_single_pair(inner)
143        } else {
144            inner // root_relation
145        };
146
147        // planNode can include addenda (+Enh:, +Opt:); surface them so
148        // TreeBuilder::add_line can produce the appropriate depth-0 error.
149        if pair.as_rule() == Rule::adv_extension {
150            return Ok(LineNode::Addendum(Addendum { pair, line_no }));
151        }
152
153        Ok(LineNode::Relation(RelationNode {
154            pair,
155            line_no,
156            addenda: Vec::new(),
157            children: Vec::new(),
158        }))
159    }
160}
161
162#[derive(Copy, Clone, Debug)]
163pub enum State {
164    // The initial state, before we have parsed any lines.
165    Initial,
166    // The extensions section, after parsing the header and any other Extension lines.
167    Extensions,
168    // The plan section, after parsing the header and any other Plan lines.
169    Plan,
170}
171
172impl fmt::Display for State {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        write!(f, "{self:?}")
175    }
176}
177
178// An in-progress tree builder, building the tree of relations.
179#[derive(Debug, Clone, Default)]
180pub struct TreeBuilder<'a> {
181    // Current tree of nodes being built. These have been successfully parsed
182    // into Pest pairs, but have not yet been converted to substrait plans.
183    current: Option<RelationNode<'a>>,
184    // Completed trees that have been built.
185    completed: Vec<RelationNode<'a>>,
186}
187
188impl<'a> TreeBuilder<'a> {
189    /// Traverse down the tree, always taking the last child at each level, until reaching the specified depth.
190    pub fn get_at_depth(&mut self, depth: usize) -> Option<&mut RelationNode<'a>> {
191        let mut node = self.current.as_mut()?;
192        for _ in 0..depth {
193            node = node.children.last_mut()?;
194        }
195        Some(node)
196    }
197
198    pub fn add_line(&mut self, depth: usize, node: LineNode<'a>) -> Result<(), ParseError> {
199        match node {
200            LineNode::Relation(rel_node) => {
201                if depth == 0 {
202                    if let Some(prev) = self.current.take() {
203                        self.completed.push(prev);
204                    }
205                    self.current = Some(rel_node);
206                    return Ok(());
207                }
208
209                let parent = match self.get_at_depth(depth - 1) {
210                    None => {
211                        return Err(ParseError::Plan(
212                            rel_node.context(),
213                            MessageParseError::invalid(
214                                "relation",
215                                rel_node.pair.as_span(),
216                                format!("No parent found for depth {depth}"),
217                            ),
218                        ));
219                    }
220                    Some(parent) => parent,
221                };
222
223                parent.children.push(rel_node);
224            }
225            LineNode::Addendum(addendum) => {
226                let context =
227                    ParseContext::new(addendum.line_no, addendum.pair.as_str().to_string());
228                if depth == 0 {
229                    return Err(ParseError::ValidationError(
230                        context,
231                        "addenda (+ Enh: / + Opt:) cannot appear at the top level".to_string(),
232                    ));
233                }
234
235                let parent = match self.get_at_depth(depth - 1) {
236                    None => {
237                        return Err(ParseError::ValidationError(
238                            context,
239                            format!("no parent found for addendum at depth {depth}"),
240                        ));
241                    }
242                    Some(parent) => parent,
243                };
244
245                if !parent.children.is_empty() {
246                    return Err(ParseError::ValidationError(
247                        context,
248                        "addenda (+ Enh: / + Opt:) must appear before child relations, \
249                         not after"
250                            .to_string(),
251                    ));
252                }
253
254                parent.addenda.push(addendum);
255            }
256        }
257        Ok(())
258    }
259
260    /// End of input - move any remaining nodes from stack to completed and
261    /// return any trees in progress. Resets the builder to its initial state
262    /// (empty)
263    /// Move any remaining nodes from stack to completed
264    pub fn finish(&mut self) -> Vec<RelationNode<'a>> {
265        if let Some(node) = self.current.take() {
266            self.completed.push(node);
267        }
268        std::mem::take(&mut self.completed)
269    }
270}
271
272/// Intermediate state for relation parsing: the structural tree data
273/// (children, addenda) has been processed into proto types, but the
274/// relation's own grammar pair hasn't been parsed yet.
275struct RelationContext<'a> {
276    pair: Pair<'a, Rule>,
277    line_no: i64,
278    #[allow(clippy::vec_box)]
279    children: Vec<Box<Rel>>,
280    input_field_count: usize,
281    advanced_extension: Option<AdvancedExtension>,
282}
283
284// Relation parsing component - handles converting LineNodes to Relations
285#[derive(Debug, Clone, Default)]
286pub struct RelationParser<'a> {
287    tree: TreeBuilder<'a>,
288}
289
290impl<'a> RelationParser<'a> {
291    pub fn parse_line(&mut self, line: IndentedLine<'a>, line_no: i64) -> Result<(), ParseError> {
292        let IndentedLine(depth, line) = line;
293
294        // Use parse_root for depth 0 (top-level relations), parse for other depths
295        let node = if depth == 0 {
296            LineNode::parse_root(line, line_no)?
297        } else {
298            LineNode::parse(line, line_no)?
299        };
300
301        self.tree.add_line(depth, node)
302    }
303
304    /// Dispatch by grammar rule after validating addenda constraints.
305    /// Standard relations go through [`parse_rel`](Self::parse_rel);
306    /// extension relations go through
307    /// [`parse_extension_relation`](Self::parse_extension_relation).
308    fn parse_relation(
309        &self,
310        extensions: &SimpleExtensions,
311        registry: &ExtensionRegistry,
312        ctx: RelationContext,
313    ) -> Result<(Rel, usize), ParseError> {
314        match ctx.pair.as_rule() {
315            Rule::virtual_read_relation => self.parse_rel::<VirtualReadRel>(extensions, ctx),
316            Rule::read_relation => self.parse_rel::<ReadRel>(extensions, ctx),
317            Rule::filter_relation => self.parse_rel::<FilterRel>(extensions, ctx),
318            Rule::project_relation => self.parse_rel::<ProjectRel>(extensions, ctx),
319            Rule::aggregate_relation => self.parse_rel::<AggregateRel>(extensions, ctx),
320            Rule::sort_relation => self.parse_rel::<SortRel>(extensions, ctx),
321            Rule::fetch_relation => self.parse_rel::<FetchRel>(extensions, ctx),
322            Rule::join_relation => self.parse_rel::<JoinRel>(extensions, ctx),
323            Rule::extension_relation => self.parse_extension_relation(extensions, registry, ctx),
324            _ => unreachable!("unhandled relation rule: {:?}", ctx.pair.as_rule()),
325        }
326    }
327
328    /// Generic bridge between [`parse_relation`](Self::parse_relation) and
329    /// the [`RelationParsePair`] trait: wraps `MessageParseError` with line
330    /// context and calls [`into_rel`](RelationParsePair::into_rel) to apply
331    /// addenda and produce the final [`Rel`].
332    fn parse_rel<T: RelationParsePair>(
333        &self,
334        extensions: &SimpleExtensions,
335        ctx: RelationContext,
336    ) -> Result<(Rel, usize), ParseError> {
337        assert_eq!(ctx.pair.as_rule(), T::rule());
338        let line_no = ctx.line_no;
339        let line = ctx.pair.as_str();
340
341        match T::parse_pair_with_context(extensions, ctx.pair, ctx.children, ctx.input_field_count)
342        {
343            Ok((parsed, count)) => Ok((parsed.into_rel(ctx.advanced_extension), count)),
344            Err(e) => Err(ParseError::Plan(
345                ParseContext::new(line_no, line.to_string()),
346                e,
347            )),
348        }
349    }
350
351    /// Handle extension relations separately from [`parse_rel`](Self::parse_rel)
352    /// because they need registry lookups that [`RelationParsePair`] doesn't
353    /// support.
354    fn parse_extension_relation(
355        &self,
356        extensions: &SimpleExtensions,
357        registry: &ExtensionRegistry,
358        ctx: RelationContext,
359    ) -> Result<(Rel, usize), ParseError> {
360        assert_eq!(ctx.pair.as_rule(), Rule::extension_relation);
361        let line_no = ctx.line_no;
362        let line = ctx.pair.as_str().to_string();
363        let pair_span = ctx.pair.as_span();
364
365        let ExtensionInvocation {
366            relation_kind,
367            name,
368            args: extension_args,
369        } = ExtensionInvocation::parse_pair(extensions, ctx.pair.clone())
370            .map_err(|e| ParseError::Plan(ParseContext::new(line_no, line.clone()), e))?;
371
372        let child_count = ctx.children.len();
373        relation_kind
374            .validate_child_count(child_count)
375            .map_err(|e| {
376                ParseError::Plan(
377                    ParseContext::new(line_no, line.to_string()),
378                    MessageParseError::invalid("extension_relation", pair_span, e),
379                )
380            })?;
381
382        let context = RelationParsingContext {
383            extensions,
384            registry,
385            line_no,
386            line: &line,
387        };
388
389        let detail = context.resolve_extension_detail(&name, &extension_args)?;
390        let output_column_count = extension_args.output_columns.len();
391
392        let children = ctx.children.into_iter().map(|child| *child).collect();
393        let rel = relation_kind.create_rel(detail, children);
394
395        if ctx.advanced_extension.is_some() {
396            return Err(ParseError::ValidationError(
397                ParseContext::new(line_no, line.to_string()),
398                "extension relations do not support advanced extensions (+ Enh / + Opt)"
399                    .to_string(),
400            ));
401        }
402        Ok((rel, output_column_count))
403    }
404
405    /// Walk the relation tree depth-first, converting structural types
406    /// (children, addenda) into proto types via [`RelationContext`].
407    /// Delegates grammar-rule-specific work to
408    /// [`parse_relation`](Self::parse_relation).
409    fn build_rel(
410        &self,
411        extensions: &SimpleExtensions,
412        registry: &ExtensionRegistry,
413        node: RelationNode,
414    ) -> Result<(Rel, usize), ParseError> {
415        let mut children: Vec<Box<Rel>> = Vec::new();
416        let mut input_field_count: usize = 0;
417        for child in node.children {
418            let (rel, count) = self.build_rel(extensions, registry, child)?;
419            input_field_count += count;
420            children.push(Box::new(rel));
421        }
422
423        let advanced_extension = if node.addenda.is_empty() {
424            None
425        } else {
426            Some(self.build_advanced_extension(extensions, registry, node.addenda)?)
427        };
428
429        self.parse_relation(
430            extensions,
431            registry,
432            RelationContext {
433                pair: node.pair,
434                line_no: node.line_no,
435                children,
436                input_field_count,
437                advanced_extension,
438            },
439        )
440    }
441
442    /// Parse enhancement/optimization addenda into an [`AdvancedExtension`] proto.
443    fn build_advanced_extension(
444        &self,
445        extensions: &SimpleExtensions,
446        registry: &ExtensionRegistry,
447        addenda: Vec<Addendum>,
448    ) -> Result<AdvancedExtension, ParseError> {
449        let mut enhancement = None;
450        let mut optimizations = Vec::new();
451
452        for addendum in addenda {
453            let line_no = addendum.line_no;
454            let line = addendum.pair.as_str().to_string();
455            let invocation = AddendumInvocation::parse_pair(extensions, addendum.pair)
456                .map_err(|e| ParseError::Plan(ParseContext::new(line_no, line.clone()), e))?;
457            let context = RelationParsingContext {
458                extensions,
459                registry,
460                line_no,
461                line: &line,
462            };
463
464            match invocation.kind {
465                AddendumKind::Enhancement => {
466                    let detail = context.resolve_adv_ext_detail(
467                        AddendumKind::Enhancement,
468                        &invocation.name,
469                        &invocation.args,
470                    )?;
471                    if enhancement.is_some() {
472                        return Err(ParseError::ValidationError(
473                            ParseContext::new(line_no, line.clone()),
474                            "at most one enhancement per relation is allowed".to_string(),
475                        ));
476                    }
477                    enhancement = Some(detail.into());
478                }
479                AddendumKind::Optimization => {
480                    let detail = context.resolve_adv_ext_detail(
481                        AddendumKind::Optimization,
482                        &invocation.name,
483                        &invocation.args,
484                    )?;
485                    optimizations.push(detail.into());
486                }
487            }
488        }
489
490        Ok(AdvancedExtension {
491            enhancement,
492            optimization: optimizations,
493        })
494    }
495
496    /// Build a tree of relations.
497    fn build_plan_rel(
498        &self,
499        extensions: &SimpleExtensions,
500        registry: &ExtensionRegistry,
501        node: RelationNode,
502    ) -> Result<PlanRel, ParseError> {
503        // Plain relations are allowed as root relations; they just don't have names.
504        if node.pair.as_rule() != Rule::root_relation {
505            let (rel, _) = self.build_rel(extensions, registry, node)?;
506            return Ok(PlanRel {
507                rel_type: Some(plan_rel::RelType::Rel(rel)),
508            });
509        }
510
511        // Root relations don't support addenda — reject rather than silently discard.
512        if !node.addenda.is_empty() {
513            let first = &node.addenda[0];
514            let context = ParseContext::new(first.line_no, first.pair.as_str().to_string());
515            return Err(ParseError::ValidationError(
516                context,
517                "addenda (+ Enh: / + Opt:) are not supported on Root relations".to_string(),
518            ));
519        }
520
521        // Named root relation.
522        let context = node.context();
523        let span = node.pair.as_span();
524
525        // Parse the column names
526        let column_names_pair = unwrap_single_pair(node.pair);
527        assert_eq!(column_names_pair.as_rule(), Rule::root_name_list);
528
529        let names: Vec<String> = column_names_pair
530            .into_inner()
531            .map(|name_pair| {
532                assert_eq!(name_pair.as_rule(), Rule::name);
533                Name::parse_pair(name_pair).0
534            })
535            .collect();
536
537        let mut children = node.children;
538        let child = match children.len() {
539            1 => {
540                let (rel, _) = self.build_rel(extensions, registry, children.pop().unwrap())?;
541                rel
542            }
543            n => {
544                return Err(ParseError::Plan(
545                    context,
546                    MessageParseError::invalid(
547                        "root_relation",
548                        span,
549                        format!("Root relation must have exactly one child, found {n}"),
550                    ),
551                ));
552            }
553        };
554
555        Ok(PlanRel {
556            rel_type: Some(plan_rel::RelType::Root(RelRoot {
557                names,
558                input: Some(child),
559            })),
560        })
561    }
562
563    /// Build all the trees.
564    fn build(
565        mut self,
566        extensions: &SimpleExtensions,
567        registry: &ExtensionRegistry,
568    ) -> Result<Vec<PlanRel>, ParseError> {
569        let nodes = self.tree.finish();
570        nodes
571            .into_iter()
572            .map(|n| self.build_plan_rel(extensions, registry, n))
573            .collect::<Result<Vec<PlanRel>, ParseError>>()
574    }
575}
576
577/// A parser for Substrait query plans in text format.
578///
579/// The `Parser` converts human-readable Substrait text format into Substrait
580/// protobuf plans. It handles both the extensions section (which defines
581/// functions, types, etc.) and the plan section (which defines the actual query
582/// structure).
583///
584/// ## Usage
585///
586/// The simplest entry point is the static `parse()` method:
587///
588/// ```rust
589/// use substrait_explain::parser::Parser;
590///
591/// let plan_text = r#"
592/// === Plan
593/// Root[c, d]
594///   Project[$1, 42]
595///     Read[schema.table => a:i64, b:string?]
596/// "#;
597///
598/// let plan = Parser::parse(plan_text).unwrap();
599/// ```
600///
601/// ## Input Format
602///
603/// The parser expects input in the following format:
604///
605/// ```text
606/// === Extensions
607/// URNs:
608///   @  1: https://github.com/substrait-io/substrait/blob/main/extensions/functions_arithmetic.yaml
609/// Functions:
610///   # 10 @  1: add
611/// === Plan
612/// Root[columns]
613///   Relation[arguments => columns]
614///     ChildRelation[arguments => columns]
615/// ```
616///
617/// - **Extensions section** (optional): Defines URNs and function/type declarations
618/// - **Plan section** (required): Defines the query structure with indented relations
619///
620/// ## Error Handling
621///
622/// The parser provides detailed error information including:
623/// - Line number where the error occurred
624/// - The actual line content that failed to parse
625/// - Specific error type and description
626///
627/// ```rust
628/// use substrait_explain::parser::Parser;
629///
630/// let invalid_plan = r#"
631/// === Plan
632/// InvalidRelation[invalid syntax]
633/// "#;
634///
635/// match Parser::parse(invalid_plan) {
636///     Ok(plan) => println!("Successfully parsed"),
637///     Err(e) => eprintln!("Parse error: {}", e),
638/// }
639/// ```
640///
641/// ## Supported Relations
642///
643/// The parser supports all standard Substrait relations:
644/// - `Read[table => columns]` - Read from a table
645/// - `Project[expressions]` - Project columns/expressions
646/// - `Filter[condition => columns]` - Filter rows
647/// - `Root[columns]` - Root relation with output columns
648/// - And more...
649///
650/// ## Extensions Support
651///
652/// The parser fully supports Substrait Simple Extensions, allowing you to:
653/// - Define custom functions with URNs and anchors
654/// - Reference functions by name in expressions
655/// - Use custom types and type variations
656///
657/// ```rust
658/// use substrait_explain::parser::Parser;
659///
660/// let plan_with_extensions = r#"
661/// === Extensions
662/// URNs:
663///   @  1: https://example.com/functions.yaml
664/// Functions:
665///   ## 10 @  1: my_custom_function
666/// === Plan
667/// Root[result]
668///   Project[my_custom_function($0, $1)]
669///     Read[table => col1:i32, col2:i32]
670/// "#;
671///
672/// let plan = Parser::parse(plan_with_extensions).unwrap();
673/// ```
674///
675/// ## Performance
676///
677/// The parser is designed for efficiency:
678/// - Single-pass parsing with minimal allocations
679/// - Early error detection and reporting
680/// - Memory-efficient tree building
681///
682/// ## Thread Safety
683///
684/// `Parser` instances are not thread-safe and should not be shared between threads.
685/// However, the static `parse()` method is safe to call from multiple threads.
686#[derive(Debug)]
687pub struct Parser<'a> {
688    line_no: i64,
689    state: State,
690    extension_parser: ExtensionParser,
691    extension_registry: ExtensionRegistry,
692    relation_parser: RelationParser<'a>,
693}
694impl<'a> Default for Parser<'a> {
695    fn default() -> Self {
696        Self::new()
697    }
698}
699
700impl<'a> Parser<'a> {
701    /// Parse a Substrait plan from text format.
702    ///
703    /// This is the main entry point for parsing.
704    ///
705    /// The input should be in the Substrait text format, which consists of:
706    /// - An optional extensions section starting with "=== Extensions"
707    /// - A plan section starting with "=== Plan"
708    /// - Indented relation definitions
709    ///
710    /// # Examples
711    ///
712    /// Simple parsing:
713    /// ```rust
714    /// use substrait_explain::parser::Parser;
715    ///
716    /// let plan_text = r#"
717    /// === Plan
718    /// Root[result]
719    ///   Read[table => col:i32]
720    /// "#;
721    ///
722    /// let plan = Parser::parse(plan_text).unwrap();
723    /// assert_eq!(plan.relations.len(), 1);
724    /// ```
725    ///
726    /// # Errors
727    ///
728    /// Returns a [`ParseError`] if the input cannot be parsed.
729    pub fn parse(input: &str) -> ParseResult {
730        Self::new().parse_plan(input)
731    }
732
733    /// Create a new parser with default configuration.
734    pub fn new() -> Self {
735        Self {
736            line_no: 1,
737            state: State::Initial,
738            extension_parser: ExtensionParser::default(),
739            extension_registry: ExtensionRegistry::new(),
740            relation_parser: RelationParser::default(),
741        }
742    }
743
744    /// Configure the parser to use the specified extension registry.
745    pub fn with_extension_registry(mut self, registry: ExtensionRegistry) -> Self {
746        self.extension_registry = registry;
747        self
748    }
749
750    /// Parse a Substrait plan with the current parser configuration.
751    pub fn parse_plan(mut self, input: &'a str) -> ParseResult {
752        for line in input.lines() {
753            if line.trim().is_empty() {
754                self.line_no += 1;
755                continue;
756            }
757
758            self.parse_line(line)?;
759            self.line_no += 1;
760        }
761
762        let plan = self.build_plan()?;
763        Ok(plan)
764    }
765
766    /// Parse a single line of input.
767    fn parse_line(&mut self, line: &'a str) -> Result<(), ParseError> {
768        let indented_line = IndentedLine::from(line);
769        let line_no = self.line_no;
770        let ctx = || ParseContext {
771            line_no,
772            line: line.to_string(),
773        };
774
775        match self.state {
776            State::Initial => self.parse_initial(indented_line),
777            State::Extensions => self
778                .parse_extensions(indented_line)
779                .map_err(|e| ParseError::Extension(ctx(), e)),
780            State::Plan => {
781                let IndentedLine(depth, line_str) = indented_line;
782
783                // Parse the line
784                let node = if depth == 0 {
785                    LineNode::parse_root(line_str, line_no)?
786                } else {
787                    LineNode::parse(line_str, line_no)?
788                };
789
790                self.relation_parser.tree.add_line(depth, node)
791            }
792        }
793    }
794
795    /// Parse the initial line(s) of the input, which is either a blank line or
796    /// the extensions or plan header.
797    fn parse_initial(&mut self, line: IndentedLine) -> Result<(), ParseError> {
798        match line {
799            IndentedLine(0, l) if l.trim().is_empty() => {}
800            IndentedLine(0, simple::EXTENSIONS_HEADER) => {
801                self.state = State::Extensions;
802            }
803            IndentedLine(0, PLAN_HEADER) => {
804                self.state = State::Plan;
805            }
806            IndentedLine(n, l) => {
807                return Err(ParseError::Initial(
808                    ParseContext::new(n as i64, l.to_string()),
809                    MessageParseError::invalid(
810                        "initial",
811                        pest::Span::new(l, 0, l.len()).expect("Invalid span?!"),
812                        format!("Unknown initial line: {l:?}"),
813                    ),
814                ));
815            }
816        }
817        Ok(())
818    }
819
820    /// Parse a single line from the extensions section of the input, updating
821    /// the parser state.
822    fn parse_extensions(&mut self, line: IndentedLine<'_>) -> Result<(), ExtensionParseError> {
823        if line == IndentedLine(0, PLAN_HEADER) {
824            self.state = State::Plan;
825            return Ok(());
826        }
827        self.extension_parser.parse_line(line)
828    }
829
830    /// Build the plan from the parser state with warning collection.
831    fn build_plan(self) -> Result<Plan, ParseError> {
832        let Parser {
833            relation_parser,
834            extension_parser,
835            extension_registry,
836            ..
837        } = self;
838
839        let extensions = extension_parser.extensions();
840
841        // Parse the tree into relations
842        let root_relations = relation_parser.build(extensions, &extension_registry)?;
843
844        // Build the final plan
845        Ok(Plan {
846            extension_urns: extensions.to_extension_urns(),
847            extensions: extensions.to_extension_declarations(),
848            relations: root_relations,
849            ..Default::default()
850        })
851    }
852}
853
854#[cfg(test)]
855mod tests {
856    use substrait::proto::extensions::simple_extension_declaration::MappingType;
857    use substrait::proto::rel::RelType;
858
859    use super::*;
860    use crate::extensions::simple::ExtensionKind;
861    use crate::parser::extensions::ExtensionParserState;
862
863    #[test]
864    fn test_parse_basic_block() {
865        let mut expected_extensions = SimpleExtensions::new();
866        expected_extensions
867            .add_extension_urn("/urn/common".to_string(), 1)
868            .unwrap();
869        expected_extensions
870            .add_extension_urn("/urn/specific_funcs".to_string(), 2)
871            .unwrap();
872        expected_extensions
873            .add_extension(ExtensionKind::Function, 1, 10, "func_a".to_string())
874            .unwrap();
875        expected_extensions
876            .add_extension(ExtensionKind::Function, 2, 11, "func_b_special".to_string())
877            .unwrap();
878        expected_extensions
879            .add_extension(ExtensionKind::Type, 1, 20, "SomeType".to_string())
880            .unwrap();
881        expected_extensions
882            .add_extension(ExtensionKind::TypeVariation, 2, 30, "VarX".to_string())
883            .unwrap();
884
885        let mut parser = ExtensionParser::default();
886        let input_block = r#"
887URNs:
888  @  1: /urn/common
889  @  2: /urn/specific_funcs
890Functions:
891  # 10 @  1: func_a
892  # 11 @  2: func_b_special
893Types:
894  # 20 @  1: SomeType
895Type Variations:
896  # 30 @  2: VarX
897"#;
898
899        for line_str in input_block.trim().lines() {
900            parser
901                .parse_line(IndentedLine::from(line_str))
902                .unwrap_or_else(|e| panic!("Failed to parse line \'{line_str}\': {e:?}"));
903        }
904
905        assert_eq!(*parser.extensions(), expected_extensions);
906
907        let extensions_str = parser.extensions().to_string("  ");
908        // The writer adds the header; the ExtensionParser does not parse the
909        // header, so we add it here for comparison.
910        let expected_str = format!(
911            "{}\n{}",
912            simple::EXTENSIONS_HEADER,
913            input_block.trim_start()
914        );
915        assert_eq!(extensions_str.trim(), expected_str.trim());
916        // Check final state after all lines are processed.
917        // The last significant line in input_block is a TypeVariation declaration.
918        assert_eq!(
919            parser.state(),
920            ExtensionParserState::ExtensionDeclarations(ExtensionKind::TypeVariation)
921        );
922
923        // Check that a subsequent blank line correctly resets state to Extensions.
924        parser.parse_line(IndentedLine(0, "")).unwrap();
925        assert_eq!(parser.state(), ExtensionParserState::Extensions);
926    }
927
928    /// Test that we can parse a larger extensions block and it matches the input.
929    #[test]
930    fn test_parse_complete_extension_block() {
931        let mut parser = ExtensionParser::default();
932        let input_block = r#"
933URNs:
934  @  1: /urn/common
935  @  2: /urn/specific_funcs
936  @  3: /urn/types_lib
937  @  4: /urn/variations_lib
938Functions:
939  # 10 @  1: func_a
940  # 11 @  2: func_b_special
941  # 12 @  1: func_c_common
942Types:
943  # 20 @  1: CommonType
944  # 21 @  3: LibraryType
945  # 22 @  1: AnotherCommonType
946Type Variations:
947  # 30 @  4: VarX
948  # 31 @  4: VarY
949"#;
950
951        for line_str in input_block.trim().lines() {
952            parser
953                .parse_line(IndentedLine::from(line_str))
954                .unwrap_or_else(|e| panic!("Failed to parse line \'{line_str}\': {e:?}"));
955        }
956
957        let extensions_str = parser.extensions().to_string("  ");
958        // The writer adds the header; the ExtensionParser does not parse the
959        // header, so we add it here for comparison.
960        let expected_str = format!(
961            "{}\n{}",
962            simple::EXTENSIONS_HEADER,
963            input_block.trim_start()
964        );
965        assert_eq!(extensions_str.trim(), expected_str.trim());
966    }
967
968    #[test]
969    fn test_parse_relation_tree() {
970        // Example plan with a Project, a Filter, and a Read, nested by indentation
971        let plan = r#"=== Plan
972Project[$0, $1, 42, 84]
973  Filter[$2 => $0, $1]
974    Read[my.table => a:i32, b:string?, c:boolean]
975"#;
976        let mut parser = Parser::default();
977        for line in plan.lines() {
978            parser.parse_line(line).unwrap();
979        }
980
981        // Complete the current tree to convert it to relations
982        let plan = parser.build_plan().unwrap();
983
984        let root_rel = &plan.relations[0].rel_type;
985        let first_rel = match root_rel {
986            Some(plan_rel::RelType::Rel(rel)) => rel,
987            _ => panic!("Expected Rel type, got {root_rel:?}"),
988        };
989        // Root should be Project
990        let project = match &first_rel.rel_type {
991            Some(RelType::Project(p)) => p,
992            other => panic!("Expected Project at root, got {other:?}"),
993        };
994
995        // Check that Project has Filter as input
996        assert!(project.input.is_some());
997        let filter_input = project.input.as_ref().unwrap();
998
999        // Check that Filter has Read as input
1000        match &filter_input.rel_type {
1001            Some(RelType::Filter(_)) => {
1002                match &filter_input.rel_type {
1003                    Some(RelType::Filter(filter)) => {
1004                        assert!(filter.input.is_some());
1005                        let read_input = filter.input.as_ref().unwrap();
1006
1007                        // Check that Read has no input (it's a leaf)
1008                        match &read_input.rel_type {
1009                            Some(RelType::Read(_)) => {}
1010                            other => panic!("Expected Read relation, got {other:?}"),
1011                        }
1012                    }
1013                    other => panic!("Expected Filter relation, got {other:?}"),
1014                }
1015            }
1016            other => panic!("Expected Filter relation, got {other:?}"),
1017        }
1018    }
1019
1020    #[test]
1021    fn test_parse_root_relation() {
1022        // Test a plan with a Root relation
1023        let plan = r#"=== Plan
1024Root[result]
1025  Project[$0, $1]
1026    Read[my.table => a:i32, b:string?]
1027"#;
1028        let mut parser = Parser::default();
1029        for line in plan.lines() {
1030            parser.parse_line(line).unwrap();
1031        }
1032
1033        let plan = parser.build_plan().unwrap();
1034
1035        // Check that we have exactly one relation
1036        assert_eq!(plan.relations.len(), 1);
1037
1038        let root_rel = &plan.relations[0].rel_type;
1039        let rel_root = match root_rel {
1040            Some(plan_rel::RelType::Root(rel_root)) => rel_root,
1041            other => panic!("Expected Root type, got {other:?}"),
1042        };
1043
1044        // Check that the root has the correct name
1045        assert_eq!(rel_root.names, vec!["result"]);
1046
1047        // Check that the root has a Project as input
1048        let project_input = match &rel_root.input {
1049            Some(rel) => rel,
1050            None => panic!("Root should have an input"),
1051        };
1052
1053        let project = match &project_input.rel_type {
1054            Some(RelType::Project(p)) => p,
1055            other => panic!("Expected Project as root input, got {other:?}"),
1056        };
1057
1058        // Check that Project has Read as input
1059        let read_input = match &project.input {
1060            Some(rel) => rel,
1061            None => panic!("Project should have an input"),
1062        };
1063
1064        match &read_input.rel_type {
1065            Some(RelType::Read(_)) => {}
1066            other => panic!("Expected Read relation, got {other:?}"),
1067        }
1068    }
1069
1070    #[test]
1071    fn test_parse_root_relation_no_names() {
1072        // Test a plan with a Root relation with no names
1073        let plan = r#"=== Plan
1074Root[]
1075  Project[$0, $1]
1076    Read[my.table => a:i32, b:string?]
1077"#;
1078        let mut parser = Parser::default();
1079        for line in plan.lines() {
1080            parser.parse_line(line).unwrap();
1081        }
1082
1083        let plan = parser.build_plan().unwrap();
1084
1085        let root_rel = &plan.relations[0].rel_type;
1086        let rel_root = match root_rel {
1087            Some(plan_rel::RelType::Root(rel_root)) => rel_root,
1088            other => panic!("Expected Root type, got {other:?}"),
1089        };
1090
1091        // Check that the root has no names
1092        assert_eq!(rel_root.names, Vec::<String>::new());
1093    }
1094
1095    #[test]
1096    fn test_parse_full_plan() {
1097        // Test a complete Substrait plan with extensions and relations
1098        let input = r#"
1099=== Extensions
1100URNs:
1101  @  1: /urn/common
1102  @  2: /urn/specific_funcs
1103Functions:
1104  # 10 @  1: func_a
1105  # 11 @  2: func_b_special
1106Types:
1107  # 20 @  1: SomeType
1108Type Variations:
1109  # 30 @  2: VarX
1110
1111=== Plan
1112Project[$0, $1, 42, 84]
1113  Filter[$2 => $0, $1]
1114    Read[my.table => a:i32, b:string?, c:boolean]
1115"#;
1116
1117        let plan = Parser::parse(input).unwrap();
1118
1119        // Verify the plan structure
1120        assert_eq!(plan.extension_urns.len(), 2);
1121        assert_eq!(plan.extensions.len(), 4);
1122        assert_eq!(plan.relations.len(), 1);
1123
1124        // Verify extension URIs
1125        let urn1 = &plan.extension_urns[0];
1126        assert_eq!(urn1.extension_urn_anchor, 1);
1127        assert_eq!(urn1.urn, "/urn/common");
1128
1129        let urn2 = &plan.extension_urns[1];
1130        assert_eq!(urn2.extension_urn_anchor, 2);
1131        assert_eq!(urn2.urn, "/urn/specific_funcs");
1132
1133        // Verify extensions
1134        let func1 = &plan.extensions[0];
1135        match &func1.mapping_type {
1136            Some(MappingType::ExtensionFunction(f)) => {
1137                assert_eq!(f.function_anchor, 10);
1138                assert_eq!(f.extension_urn_reference, 1);
1139                assert_eq!(f.name, "func_a");
1140            }
1141            other => panic!("Expected ExtensionFunction, got {other:?}"),
1142        }
1143
1144        let func2 = &plan.extensions[1];
1145        match &func2.mapping_type {
1146            Some(MappingType::ExtensionFunction(f)) => {
1147                assert_eq!(f.function_anchor, 11);
1148                assert_eq!(f.extension_urn_reference, 2);
1149                assert_eq!(f.name, "func_b_special");
1150            }
1151            other => panic!("Expected ExtensionFunction, got {other:?}"),
1152        }
1153
1154        let type1 = &plan.extensions[2];
1155        match &type1.mapping_type {
1156            Some(MappingType::ExtensionType(t)) => {
1157                assert_eq!(t.type_anchor, 20);
1158                assert_eq!(t.extension_urn_reference, 1);
1159                assert_eq!(t.name, "SomeType");
1160            }
1161            other => panic!("Expected ExtensionType, got {other:?}"),
1162        }
1163
1164        let var1 = &plan.extensions[3];
1165        match &var1.mapping_type {
1166            Some(MappingType::ExtensionTypeVariation(v)) => {
1167                assert_eq!(v.type_variation_anchor, 30);
1168                assert_eq!(v.extension_urn_reference, 2);
1169                assert_eq!(v.name, "VarX");
1170            }
1171            other => panic!("Expected ExtensionTypeVariation, got {other:?}"),
1172        }
1173
1174        // Verify the relation tree structure
1175        let root_rel = &plan.relations[0];
1176        match &root_rel.rel_type {
1177            Some(plan_rel::RelType::Rel(rel)) => {
1178                match &rel.rel_type {
1179                    Some(RelType::Project(project)) => {
1180                        // Verify Project relation
1181                        assert_eq!(project.expressions.len(), 2); // 42 and 84
1182                        assert!(project.input.is_some()); // Should have Filter as input
1183
1184                        // Check the Filter input
1185                        let filter_input = project.input.as_ref().unwrap();
1186                        match &filter_input.rel_type {
1187                            Some(RelType::Filter(filter)) => {
1188                                assert!(filter.input.is_some()); // Should have Read as input
1189
1190                                // Check the Read input
1191                                let read_input = filter.input.as_ref().unwrap();
1192                                match &read_input.rel_type {
1193                                    Some(RelType::Read(read)) => {
1194                                        // Verify Read relation
1195                                        let schema = read.base_schema.as_ref().unwrap();
1196                                        assert_eq!(schema.names.len(), 3);
1197                                        assert_eq!(schema.names[0], "a");
1198                                        assert_eq!(schema.names[1], "b");
1199                                        assert_eq!(schema.names[2], "c");
1200
1201                                        let struct_ = schema.r#struct.as_ref().unwrap();
1202                                        assert_eq!(struct_.types.len(), 3);
1203                                    }
1204                                    other => panic!("Expected Read relation, got {other:?}"),
1205                                }
1206                            }
1207                            other => panic!("Expected Filter relation, got {other:?}"),
1208                        }
1209                    }
1210                    other => panic!("Expected Project relation, got {other:?}"),
1211                }
1212            }
1213            other => panic!("Expected Rel type, got {other:?}"),
1214        }
1215    }
1216}