Skip to main content

substrait_explain/parser/
extensions.rs

1use std::fmt;
2use std::str::FromStr;
3
4use pest::iterators::Pair;
5use substrait::proto::rel::RelType;
6use substrait::proto::{
7    Expression, ExtensionLeafRel, ExtensionMultiRel, ExtensionSingleRel, Rel, Type,
8};
9use thiserror::Error;
10
11use super::{
12    ErrorKind, ExpressionParser, MessageParseError, ParsePair, Rule, RuleIter, ScopedParsePair,
13    unescape_string, unwrap_single_pair,
14};
15use crate::extensions::any::Any;
16use crate::extensions::simple::{self, ExtensionKind};
17use crate::extensions::{
18    AddendumKind, ExtensionArgs, ExtensionColumn, ExtensionValue, InsertError, SimpleExtensions,
19    TupleValue,
20};
21use crate::parser::expressions::{FieldIndex, Name};
22use crate::parser::relations::direct_common;
23use crate::parser::structural::IndentedLine;
24use crate::textify::expressions::Reference;
25
26#[derive(Debug, Clone, Error)]
27pub enum ExtensionParseError {
28    #[error("Unexpected line, expected {0}")]
29    UnexpectedLine(ExpectedExtensionLine),
30    #[error("Error adding extension: {0}")]
31    ExtensionError(#[from] InsertError),
32    #[error("Error parsing message: {0}")]
33    Message(#[from] super::MessageParseError),
34}
35
36/// The kind of extension-section line expected next.
37///
38/// `ExtensionParser` also uses this as its internal state, since each parser
39/// state corresponds directly to the next accepted line shape.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum ExpectedExtensionLine {
42    // The extensions section, after parsing the 'Extensions:' header, before
43    // parsing any subsection headers.
44    Extensions,
45    // The extension URNs section, after parsing the 'URNs:' subsection header,
46    // and any URNs so far.
47    ExtensionUrns,
48    // In a subsection, after parsing the subsection header, and any
49    // declarations so far.
50    ExtensionDeclarations(ExtensionKind),
51}
52
53impl fmt::Display for ExpectedExtensionLine {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            ExpectedExtensionLine::Extensions => write!(f, "Subsection Header, e.g. 'URNs:'"),
57            ExpectedExtensionLine::ExtensionUrns => write!(f, "Extension URNs"),
58            ExpectedExtensionLine::ExtensionDeclarations(kind) => {
59                write!(f, "Extension Declaration for {kind}")
60            }
61        }
62    }
63}
64
65/// The parser for the extension section of the Substrait file format.
66///
67/// This is responsible for parsing the extension section of the file, which
68/// contains the extension URNs and declarations. Note that this parser does not
69/// parse the header; otherwise, this is symmetric with the
70/// SimpleExtensions::write method.
71#[derive(Debug)]
72pub struct ExtensionParser {
73    state: ExpectedExtensionLine,
74    extensions: SimpleExtensions,
75}
76
77impl Default for ExtensionParser {
78    fn default() -> Self {
79        Self {
80            state: ExpectedExtensionLine::Extensions,
81            extensions: SimpleExtensions::new(),
82        }
83    }
84}
85
86impl ExtensionParser {
87    pub fn parse_line(&mut self, line: IndentedLine) -> Result<(), ExtensionParseError> {
88        if line.1.is_empty() {
89            // Blank lines are allowed between subsections, so if we see
90            // one, we revert out of the subsection.
91            self.state = ExpectedExtensionLine::Extensions;
92            return Ok(());
93        }
94
95        match self.state {
96            ExpectedExtensionLine::Extensions => self.parse_subsection(line),
97            ExpectedExtensionLine::ExtensionUrns => self.parse_extension_urns(line),
98            ExpectedExtensionLine::ExtensionDeclarations(extension_kind) => {
99                self.parse_declarations(line, extension_kind)
100            }
101        }
102    }
103
104    fn parse_subsection(&mut self, line: IndentedLine) -> Result<(), ExtensionParseError> {
105        match line {
106            IndentedLine(0, simple::EXTENSION_URNS_HEADER) => {
107                self.state = ExpectedExtensionLine::ExtensionUrns;
108                Ok(())
109            }
110            IndentedLine(0, simple::EXTENSION_FUNCTIONS_HEADER) => {
111                self.state = ExpectedExtensionLine::ExtensionDeclarations(ExtensionKind::Function);
112                Ok(())
113            }
114            IndentedLine(0, simple::EXTENSION_TYPES_HEADER) => {
115                self.state = ExpectedExtensionLine::ExtensionDeclarations(ExtensionKind::Type);
116                Ok(())
117            }
118            IndentedLine(0, simple::EXTENSION_TYPE_VARIATIONS_HEADER) => {
119                self.state =
120                    ExpectedExtensionLine::ExtensionDeclarations(ExtensionKind::TypeVariation);
121                Ok(())
122            }
123            _ => Err(ExtensionParseError::UnexpectedLine(self.state)),
124        }
125    }
126
127    fn parse_extension_urns(&mut self, line: IndentedLine) -> Result<(), ExtensionParseError> {
128        match line {
129            IndentedLine(0, _s) => self.parse_subsection(line), // Pass the original line with 0 indent
130            IndentedLine(1, s) => {
131                let urn =
132                    URNExtensionDeclaration::from_str(s).map_err(ExtensionParseError::Message)?;
133                self.extensions.add_extension_urn(urn.urn, urn.anchor)?;
134                Ok(())
135            }
136            _ => Err(ExtensionParseError::UnexpectedLine(self.state)),
137        }
138    }
139
140    fn parse_declarations(
141        &mut self,
142        line: IndentedLine,
143        extension_kind: ExtensionKind,
144    ) -> Result<(), ExtensionParseError> {
145        match line {
146            IndentedLine(0, _s) => self.parse_subsection(line), // Pass the original line with 0 indent
147            IndentedLine(1, s) => {
148                let decl = SimpleExtensionDeclaration::parse_from_kind(s, extension_kind)?;
149                self.extensions.add_extension(
150                    extension_kind,
151                    decl.urn_anchor,
152                    decl.anchor,
153                    decl.name,
154                )?;
155                Ok(())
156            }
157            _ => Err(ExtensionParseError::UnexpectedLine(self.state)),
158        }
159    }
160
161    pub fn extensions(&self) -> &SimpleExtensions {
162        &self.extensions
163    }
164
165    #[cfg(test)]
166    pub(crate) fn state(&self) -> ExpectedExtensionLine {
167        self.state
168    }
169}
170
171#[derive(Debug, Clone, PartialEq)]
172pub struct URNExtensionDeclaration {
173    pub anchor: u32,
174    pub urn: String,
175}
176
177#[derive(Debug, Clone, PartialEq)]
178pub struct SimpleExtensionDeclaration {
179    pub anchor: u32,
180    pub urn_anchor: u32,
181    pub name: String,
182}
183
184impl ParsePair for URNExtensionDeclaration {
185    fn rule() -> Rule {
186        Rule::extension_urn_declaration
187    }
188
189    fn message() -> &'static str {
190        "URNExtensionDeclaration"
191    }
192
193    fn parse_pair(pair: Pair<Rule>) -> Self {
194        assert_eq!(pair.as_rule(), Self::rule());
195
196        let mut iter = RuleIter::from(pair.into_inner());
197        let anchor_pair = iter.pop(Rule::urn_anchor);
198        let anchor = unwrap_single_pair(anchor_pair)
199            .as_str()
200            .parse::<u32>()
201            .unwrap();
202        let urn = iter.pop(Rule::urn).as_str().to_string();
203        iter.done();
204
205        URNExtensionDeclaration { anchor, urn }
206    }
207}
208
209impl FromStr for URNExtensionDeclaration {
210    type Err = super::MessageParseError;
211
212    fn from_str(s: &str) -> Result<Self, Self::Err> {
213        Self::parse_str(s)
214    }
215}
216
217impl SimpleExtensionDeclaration {
218    fn parse_from_kind(s: &str, kind: ExtensionKind) -> Result<Self, MessageParseError> {
219        let mut pairs = <ExpressionParser as pest::Parser<Rule>>::parse(Rule::simple_extension, s)
220            .map_err(|e| {
221                MessageParseError::new("SimpleExtensionDeclaration", ErrorKind::Syntax, Box::new(e))
222            })?;
223        assert_eq!(pairs.as_str(), s);
224        let pair = pairs.next().unwrap();
225        let mut iter = RuleIter::from(pair.into_inner());
226
227        let anchor = unwrap_single_pair(iter.pop(Rule::anchor))
228            .as_str()
229            .parse::<u32>()
230            .unwrap();
231        let urn_anchor = unwrap_single_pair(iter.pop(Rule::urn_anchor))
232            .as_str()
233            .parse::<u32>()
234            .unwrap();
235        let name_pair = iter.pop(Rule::simple_extension_name);
236        let name_span = name_pair.as_span();
237        let name = name_pair.as_str();
238
239        if kind != ExtensionKind::Type && name.starts_with("u!") {
240            return Err(MessageParseError::invalid(
241                "simple_extension_name",
242                name_span,
243                format!("'u!' prefix is only valid for type declarations, not {kind}"),
244            ));
245        }
246        if matches!(kind, ExtensionKind::Type | ExtensionKind::TypeVariation) && name.contains(':')
247        {
248            return Err(MessageParseError::invalid(
249                "simple_extension_name",
250                name_span,
251                format!(
252                    "type/type-variation names must not include a signature suffix, got '{name}'"
253                ),
254            ));
255        }
256        iter.done();
257
258        Ok(SimpleExtensionDeclaration {
259            anchor,
260            urn_anchor,
261            name: name.to_string(),
262        })
263    }
264}
265
266// Extension relation parsing implementations
267// These were moved from extensions/registry.rs to maintain clean architecture
268
269impl ScopedParsePair for ExtensionValue {
270    fn rule() -> Rule {
271        Rule::extension_argument
272    }
273
274    fn message() -> &'static str {
275        "ExtensionValue"
276    }
277
278    fn parse_pair(
279        extensions: &SimpleExtensions,
280        pair: Pair<Rule>,
281    ) -> Result<Self, MessageParseError> {
282        assert_eq!(pair.as_rule(), Self::rule());
283
284        let inner = unwrap_single_pair(pair); // Extract the actual content
285
286        Ok(match inner.as_rule() {
287            Rule::enum_value => {
288                // Strip leading '&' and store the identifier
289                let s = inner.as_str().trim_start_matches('&').to_string();
290                ExtensionValue::Enum(s)
291            }
292            Rule::reference => {
293                // Reuse the existing FieldIndex parser, then extract the i32
294                let field_index = FieldIndex::parse_pair(inner);
295                ExtensionValue::from(Reference(field_index.0))
296            }
297            Rule::parameter_literal => {
298                // Parameter literals can contain integer, float, boolean, string, or null.
299                let value_pair = unwrap_single_pair(inner);
300                match value_pair.as_rule() {
301                    Rule::string_literal => ExtensionValue::String(unescape_string(value_pair)),
302                    Rule::integer => {
303                        ExtensionValue::Integer(value_pair.as_str().parse::<i64>().unwrap())
304                    }
305                    Rule::float => {
306                        ExtensionValue::Float(value_pair.as_str().parse::<f64>().unwrap())
307                    }
308                    Rule::boolean => ExtensionValue::Boolean(value_pair.as_str() == "true"),
309                    Rule::null => ExtensionValue::Null,
310                    _ => panic!(
311                        "Unexpected extension parameter literal type: {:?}",
312                        value_pair.as_rule()
313                    ),
314                }
315            }
316            Rule::tuple => {
317                let tv = inner
318                    .into_inner()
319                    .map(|pair| ExtensionValue::parse_pair(extensions, pair))
320                    .collect::<Result<TupleValue, MessageParseError>>()?;
321                ExtensionValue::Tuple(tv)
322            }
323            Rule::expression => {
324                let expr = Expression::parse_pair(extensions, inner)?;
325                ExtensionValue::from(expr)
326            }
327            _ => panic!("Unexpected extension argument type: {:?}", inner.as_rule()),
328        })
329    }
330}
331
332impl ScopedParsePair for ExtensionColumn {
333    fn rule() -> Rule {
334        Rule::extension_column
335    }
336
337    fn message() -> &'static str {
338        "ExtensionColumn"
339    }
340
341    fn parse_pair(
342        extensions: &SimpleExtensions,
343        pair: Pair<Rule>,
344    ) -> Result<Self, MessageParseError> {
345        assert_eq!(pair.as_rule(), Self::rule());
346
347        let inner = unwrap_single_pair(pair); // Extract the actual content
348
349        Ok(match inner.as_rule() {
350            Rule::named_column => {
351                let mut iter = inner.into_inner();
352                let name_pair = iter.next().unwrap(); // Grammar guarantees type exists
353                let type_pair = iter.next().unwrap(); // Grammar guarantees type exists
354
355                let name = Name::parse_pair(name_pair).0.to_string(); // Reuse existing Name parser
356                let ty = Type::parse_pair(extensions, type_pair)?;
357
358                ExtensionColumn::Named { name, r#type: ty }
359            }
360            Rule::reference => {
361                // Reuse the existing FieldIndex parser, then extract the i32
362                let field_index = FieldIndex::parse_pair(inner);
363                ExtensionColumn::Expr(Reference(field_index.0).into())
364            }
365            Rule::expression => {
366                let expr = Expression::parse_pair(extensions, inner)?;
367                ExtensionColumn::Expr(expr.into())
368            }
369            _ => panic!("Unexpected extension column type: {:?}", inner.as_rule()),
370        })
371    }
372}
373
374/// Relation kind encoded by the text syntax prefix (`ExtensionLeaf`,
375/// `ExtensionSingle`, or `ExtensionMulti`).
376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
377pub(crate) enum ExtensionRelationKind {
378    Leaf,
379    Single,
380    Multi,
381}
382
383impl FromStr for ExtensionRelationKind {
384    type Err = String;
385
386    fn from_str(s: &str) -> Result<Self, Self::Err> {
387        match s {
388            "ExtensionLeaf" => Ok(ExtensionRelationKind::Leaf),
389            "ExtensionSingle" => Ok(ExtensionRelationKind::Single),
390            "ExtensionMulti" => Ok(ExtensionRelationKind::Multi),
391            _ => Err(format!("Unknown extension relation type: {s}")),
392        }
393    }
394}
395
396impl ExtensionRelationKind {
397    pub(crate) fn validate_child_count(self, child_count: usize) -> Result<(), String> {
398        match self {
399            ExtensionRelationKind::Leaf => {
400                if child_count == 0 {
401                    Ok(())
402                } else {
403                    Err(format!(
404                        "ExtensionLeaf should have no input children, got {child_count}"
405                    ))
406                }
407            }
408            ExtensionRelationKind::Single => {
409                if child_count == 1 {
410                    Ok(())
411                } else {
412                    Err(format!(
413                        "ExtensionSingle should have exactly 1 input child, got {child_count}"
414                    ))
415                }
416            }
417            ExtensionRelationKind::Multi => Ok(()),
418        }
419    }
420
421    /// Create appropriate relation structure from extension detail and children.
422    pub(crate) fn create_rel(self, detail: Option<Any>, children: Vec<Rel>) -> Rel {
423        let rel_type = match self {
424            ExtensionRelationKind::Leaf => RelType::ExtensionLeaf(ExtensionLeafRel {
425                common: Some(direct_common()),
426                detail: detail.map(Into::into),
427            }),
428            ExtensionRelationKind::Single => {
429                let input = children.into_iter().next();
430                RelType::ExtensionSingle(Box::new(ExtensionSingleRel {
431                    common: Some(direct_common()),
432                    detail: detail.map(Into::into),
433                    input: input.map(Box::new),
434                }))
435            }
436            ExtensionRelationKind::Multi => RelType::ExtensionMulti(ExtensionMultiRel {
437                common: Some(direct_common()),
438                detail: detail.map(Into::into),
439                inputs: children,
440            }),
441        };
442
443        Rel {
444            rel_type: Some(rel_type),
445        }
446    }
447}
448
449/// Fully parsed extension invocation, including the user-supplied name and the
450/// structured argument payload.
451#[derive(Debug, Clone)]
452pub(crate) struct ExtensionInvocation {
453    pub(crate) relation_kind: ExtensionRelationKind,
454    pub(crate) name: String,
455    pub(crate) args: ExtensionArgs,
456}
457
458impl ScopedParsePair for ExtensionInvocation {
459    fn rule() -> Rule {
460        Rule::extension_relation
461    }
462
463    fn message() -> &'static str {
464        "ExtensionInvocation"
465    }
466
467    fn parse_pair(
468        extensions: &SimpleExtensions,
469        pair: Pair<Rule>,
470    ) -> Result<Self, MessageParseError> {
471        assert_eq!(pair.as_rule(), Self::rule());
472
473        let mut iter = pair.into_inner();
474
475        // Parse extension name to determine relation type and custom name
476        let extension_name_pair = iter.next().unwrap(); // Grammar guarantees extension_name exists
477        let full_extension_name = extension_name_pair.as_str();
478
479        // Extract the relation type and custom name from the extension name
480        // (e.g., "ExtensionLeaf:ParquetScan" -> "ExtensionLeaf" and "ParquetScan")
481        let (relation_type_str, custom_name) = if full_extension_name.contains(':') {
482            let parts: Vec<&str> = full_extension_name.splitn(2, ':').collect();
483            (parts[0], parts[1].to_string())
484        } else {
485            (full_extension_name, "UnknownExtension".to_string())
486        };
487
488        let relation_kind = ExtensionRelationKind::from_str(relation_type_str).unwrap();
489        let mut args = ExtensionArgs::default();
490
491        // Parse optional arguments
492        let ext_arguments = iter.next().unwrap();
493        match ext_arguments.as_rule() {
494            Rule::extension_args => {
495                extension_args_rule_parsing(extensions, ext_arguments, &mut args)?;
496            }
497            r => unreachable!("Unexpected rule in ExtensionArgs: {:?}", r),
498        }
499
500        // parse optional output columns
501        let extension_columns = iter.next();
502        if let Some(value) = extension_columns {
503            match value.as_rule() {
504                Rule::extension_columns => {
505                    for col_pair in value.into_inner() {
506                        if col_pair.as_rule() == Rule::extension_column {
507                            let column = ExtensionColumn::parse_pair(extensions, col_pair)?;
508                            args.output_columns.push(column);
509                        }
510                    }
511                }
512                r => unreachable!("Unexpected rule in ExtensionArgs: {:?}", r),
513            }
514        }
515
516        Ok(ExtensionInvocation {
517            relation_kind,
518            name: custom_name,
519            args,
520        })
521    }
522}
523
524/// A parsed `+` addendum line.
525#[derive(Debug, Clone)]
526pub(crate) struct AddendumInvocation {
527    pub(crate) kind: AddendumKind,
528    pub(crate) name: String,
529    pub(crate) args: ExtensionArgs,
530}
531
532impl ScopedParsePair for AddendumInvocation {
533    fn rule() -> Rule {
534        Rule::addendum
535    }
536
537    fn message() -> &'static str {
538        "AddendumInvocation"
539    }
540
541    fn parse_pair(
542        extensions: &SimpleExtensions,
543        pair: Pair<Rule>,
544    ) -> Result<Self, MessageParseError> {
545        assert_eq!(pair.as_rule(), Self::rule());
546
547        let mut iter = pair.into_inner();
548
549        // First token: addendum_type - grammar guarantees a known addendum prefix.
550        let type_pair = iter.next().unwrap(); // Grammar guarantees addendum_type exists
551        let kind = match type_pair.as_str() {
552            "Enh" => AddendumKind::Enhancement,
553            "Opt" => AddendumKind::Optimization,
554            "Ext" => AddendumKind::ExtensionTable,
555            other => unreachable!("Unexpected addendum_type: {other}"),
556        };
557
558        // Second token: name
559        let name_pair = iter.next().unwrap();
560        let name = Name::parse_pair(name_pair).0.to_string();
561
562        // Remaining token: extension_args — grammar guarantees it is always present.
563        let mut args = ExtensionArgs::default();
564
565        let arguments_pair = iter.next().unwrap();
566        match arguments_pair.as_rule() {
567            Rule::extension_args => {
568                extension_args_rule_parsing(extensions, arguments_pair, &mut args)?;
569            }
570            r => unreachable!("Unexpected rule in AddendumInvocation args: {r:?}"),
571        }
572
573        Ok(AddendumInvocation { kind, name, args })
574    }
575}
576
577fn extension_args_rule_parsing(
578    extensions: &SimpleExtensions,
579    inner_pair: Pair<'_, Rule>,
580    args: &mut ExtensionArgs,
581) -> Result<(), MessageParseError> {
582    for arg in inner_pair.into_inner() {
583        match arg.as_rule() {
584            Rule::extension_arguments => {
585                for arg_pair in arg.into_inner() {
586                    assert_eq!(arg_pair.as_rule(), Rule::extension_argument);
587                    args.push(ExtensionValue::parse_pair(extensions, arg_pair)?);
588                }
589            }
590            Rule::extension_named_arguments => {
591                for arg_pair in arg.into_inner() {
592                    assert_eq!(arg_pair.as_rule(), Rule::extension_named_argument);
593                    let mut arg_iter = arg_pair.into_inner();
594                    let name_p = arg_iter.next().unwrap();
595                    let value_p = arg_iter.next().unwrap();
596                    let key = Name::parse_pair(name_p).0.to_string();
597                    let val = ExtensionValue::parse_pair(extensions, value_p)?;
598                    args.insert(key, val);
599                }
600            }
601            Rule::empty => {}
602            r => unreachable!("Unexpected rule in extension args: {r:?}"),
603        }
604    }
605    Ok(())
606}
607
608#[cfg(test)]
609mod tests {
610    use substrait::proto;
611    use substrait::proto::expression::RexType;
612    use substrait::proto::expression::literal::LiteralType;
613
614    use super::*;
615    use crate::extensions::{Expr, ExtensionValue};
616    use crate::fixtures::TestContext;
617    use crate::parser::common::test_support::ScopedParse;
618    use crate::parser::{ParseError, Parser};
619    use crate::{OutputOptions, format};
620
621    fn parse_extension_value(text: &str) -> ExtensionValue {
622        ExtensionValue::parse(&SimpleExtensions::default(), text).unwrap()
623    }
624
625    #[test]
626    fn test_parse_urn_extension_declaration() {
627        let line = "@1: /my/urn1";
628        let urn = URNExtensionDeclaration::parse_str(line).unwrap();
629        assert_eq!(urn.anchor, 1);
630        assert_eq!(urn.urn, "/my/urn1");
631    }
632
633    #[test]
634    fn test_parse_simple_extension_declaration() {
635        let line = "#5@2: my_function_name";
636        let decl =
637            SimpleExtensionDeclaration::parse_from_kind(line, ExtensionKind::Function).unwrap();
638        assert_eq!(decl.anchor, 5);
639        assert_eq!(decl.urn_anchor, 2);
640        assert_eq!(decl.name, "my_function_name");
641
642        let line2 = "#10  @200: another_ext_123";
643        let decl =
644            SimpleExtensionDeclaration::parse_from_kind(line2, ExtensionKind::Function).unwrap();
645        assert_eq!(decl.anchor, 10);
646        assert_eq!(decl.urn_anchor, 200);
647        assert_eq!(decl.name, "another_ext_123");
648    }
649
650    #[test]
651    fn test_parse_urn_extension_declaration_str() {
652        let line = "@1: /my/urn1";
653        let urn = URNExtensionDeclaration::parse_str(line).unwrap();
654        assert_eq!(urn.anchor, 1);
655        assert_eq!(urn.urn, "/my/urn1");
656    }
657
658    #[test]
659    fn test_extensions_round_trip_plan() {
660        let input = r#"
661=== Extensions
662URNs:
663  @  1: /urn/common
664  @  2: /urn/specific_funcs
665Functions:
666  # 10 @  1: func_a
667  # 11 @  2: func_b_special
668Types:
669  # 20 @  1: SomeType
670Type Variations:
671  # 30 @  2: VarX
672"#
673        .trim_start();
674
675        // Parse the input using the structural parser
676        let plan = Parser::parse(input).unwrap();
677
678        // Verify the plan has the expected extensions
679        assert_eq!(plan.extension_urns.len(), 2);
680        assert_eq!(plan.extensions.len(), 4);
681
682        // Convert the plan extensions back to SimpleExtensions
683        let (extensions, errors) =
684            SimpleExtensions::from_extensions(&plan.extension_urns, &plan.extensions);
685
686        assert!(errors.is_empty());
687        // Convert back to string
688        let output = extensions.to_string("  ");
689
690        // The output should match the input
691        assert_eq!(output, input);
692    }
693
694    #[test]
695    fn test_parse_simple_extension_declaration_compound_name() {
696        // A function name that includes a Substrait signature suffix
697        let line = "#1 @2: equal:any_any";
698        let decl =
699            SimpleExtensionDeclaration::parse_from_kind(line, ExtensionKind::Function).unwrap();
700        assert_eq!(decl.anchor, 1);
701        assert_eq!(decl.urn_anchor, 2);
702        assert_eq!(decl.name, "equal:any_any");
703    }
704
705    #[test]
706    fn test_parse_simple_extension_declaration_compound_name_multi_segment() {
707        let line = "#3 @1: regexp_match_substring:str_str_i64";
708        let decl =
709            SimpleExtensionDeclaration::parse_from_kind(line, ExtensionKind::Function).unwrap();
710        assert_eq!(decl.anchor, 3);
711        assert_eq!(decl.urn_anchor, 1);
712        assert_eq!(decl.name, "regexp_match_substring:str_str_i64");
713    }
714
715    #[test]
716    fn test_parse_simple_extension_declaration_u_prefix_function_with_u_signature() {
717        // u! is valid inside a signature suffix (e.g. u!json as an arg type); only
718        // the base function name itself may not be u!-prefixed.
719        let line = "#5 @2: json_extract_path:u!json_str";
720        let decl =
721            SimpleExtensionDeclaration::parse_from_kind(line, ExtensionKind::Function).unwrap();
722        assert_eq!(decl.anchor, 5);
723        assert_eq!(decl.urn_anchor, 2);
724        assert_eq!(decl.name, "json_extract_path:u!json_str");
725    }
726
727    #[test]
728    fn test_u_prefix_type_declaration_accepted() {
729        // u! prefix on a type name is non-standard but accepted; normalized to bare name at storage.
730        let plan_text = "\
731=== Extensions
732URNs:
733  @  1: https://example.com/types
734Types:
735  # 11 @  1: u!point
736=== Plan
737Root[result]
738  Project[$0]
739    Read[data => p:point#11]";
740        let plan = Parser::parse(plan_text).unwrap();
741        let (text, errors) = format(&plan);
742        assert!(errors.is_empty(), "unexpected errors: {errors:?}");
743        assert!(
744            text.contains("  # 11 @  1: point"),
745            "declaration line must use bare name"
746        );
747        assert!(
748            !text.contains("u!point"),
749            "u! prefix should be stripped in output"
750        );
751    }
752
753    #[test]
754    fn test_u_prefix_type_variation_declaration_rejected() {
755        // u! prefix on a type variation name is invalid, same as for functions.
756        let plan_text = "\
757=== Extensions
758URNs:
759  @  1: https://example.com/types
760Type Variations:
761  # 30 @  1: u!myvar
762=== Plan
763Root[result]
764  Read[data => x:i64]";
765        assert!(
766            Parser::parse(plan_text).is_err(),
767            "u! prefix on a type variation name should be rejected"
768        );
769    }
770
771    #[test]
772    fn test_u_prefix_function_declaration_rejected() {
773        // u! prefix on a function base name is invalid; function names are never u!-prefixed.
774        let plan_text = "\
775=== Extensions
776URNs:
777  @  1: https://example.com/funcs
778Functions:
779  # 21 @  1: u!json_get
780=== Plan
781Root[result]
782  Read[data => x:i64]";
783        assert!(
784            Parser::parse(plan_text).is_err(),
785            "u! prefix on a function name should be rejected"
786        );
787    }
788
789    #[test]
790    fn test_signature_on_type_declaration_rejected() {
791        // Function signatures (':' suffix) are invalid on type declarations.
792        let plan_text = "\
793=== Extensions
794URNs:
795  @  1: https://example.com/types
796Types:
797  # 10 @  1: mytype:i64_i64
798=== Plan
799Root[result]
800  Read[data => x:i64]";
801        assert!(
802            Parser::parse(plan_text).is_err(),
803            "function signature suffix on a type declaration should be rejected"
804        );
805    }
806
807    #[test]
808    fn test_u_prefix_function_rejected_at_parser_level() {
809        // The parser should reject function names with a `u!` prefix
810        let plan_text = "\
811=== Extensions
812URNs:
813  @  1: https://example.com/funcs
814Functions:
815  # 21 @  1: u!bad_func
816=== Plan
817Root[result]
818  Read[data => x:i64]";
819        let err = Parser::parse(plan_text).unwrap_err();
820        assert!(
821            matches!(
822                err,
823                ParseError::Extension(_, ExtensionParseError::Message(_))
824            ),
825            "expected parser-level MessageParseError, got: {err}"
826        );
827    }
828
829    #[test]
830    fn test_extensions_round_trip_plan_with_compound_names() {
831        let input = r#"=== Extensions
832URNs:
833  @  1: extension:io.substrait:functions_string
834  @  2: extension:io.substrait:functions_comparison
835Functions:
836  #  1 @  2: equal:any_any
837  #  2 @  1: regexp_match_substring:str_str
838  #  3 @  1: regexp_match_substring:str_str_i64
839"#;
840        let plan = Parser::parse(input).unwrap();
841        let (extensions, errors) =
842            SimpleExtensions::from_extensions(&plan.extension_urns, &plan.extensions);
843        assert!(errors.is_empty());
844        // Compound names must survive the roundtrip
845        assert_eq!(
846            extensions
847                .find_by_anchor(ExtensionKind::Function, 1)
848                .unwrap()
849                .1
850                .full(),
851            "equal:any_any"
852        );
853        assert_eq!(
854            extensions
855                .find_by_anchor(ExtensionKind::Function, 3)
856                .unwrap()
857                .1
858                .full(),
859            "regexp_match_substring:str_str_i64"
860        );
861        // Text output must reproduce the input exactly
862        assert_eq!(extensions.to_string("  "), input);
863    }
864
865    #[test]
866    fn test_tuple_mixed_types_parses() {
867        // tuple has overlapping grammar syntax with expression.
868        let val = parse_extension_value("(&HASH, 8, 'hello')");
869        let ExtensionValue::Tuple(items) = val else {
870            panic!("expected Tuple, got {val:?}");
871        };
872        assert_eq!(items.len(), 3);
873        let items: Vec<&ExtensionValue> = items.iter().collect();
874        assert!(matches!(items[0], ExtensionValue::Enum(s) if s == "HASH"));
875        assert_eq!(i64::try_from(items[1]).unwrap(), 8);
876        assert_eq!(<&str>::try_from(items[2]).unwrap(), "hello");
877    }
878
879    #[test]
880    fn test_empty_tuple_parses() {
881        let val = parse_extension_value("()");
882        let ExtensionValue::Tuple(items) = val else {
883            panic!("expected Tuple, got {val:?}");
884        };
885        assert!(items.is_empty());
886    }
887
888    #[test]
889    fn test_nested_tuple_parses() {
890        let val = parse_extension_value("((&HASH, &RANGE), 8)");
891        let ExtensionValue::Tuple(outer) = val else {
892            panic!("expected Tuple, got {val:?}");
893        };
894        assert_eq!(outer.len(), 2);
895        let ExtensionValue::Tuple(inner) = outer.iter().next().unwrap() else {
896            panic!("expected inner Tuple");
897        };
898        assert_eq!(inner.len(), 2);
899        assert!(matches!(inner.iter().next().unwrap(), ExtensionValue::Enum(s) if s == "HASH"));
900        assert_eq!(i64::try_from(outer.iter().nth(1).unwrap()).unwrap(), 8);
901    }
902
903    #[test]
904    fn test_tuple_in_addendum_parses() {
905        let inv = AddendumInvocation::parse(
906            &SimpleExtensions::default(),
907            "+ Enh:Foo[(&HASH, &RANGE), count=8]",
908        )
909        .unwrap();
910        assert_eq!(inv.kind, AddendumKind::Enhancement);
911        assert_eq!(inv.name, "Foo");
912        assert_eq!(inv.args.positional.len(), 1);
913        let ExtensionValue::Tuple(items) = &inv.args.positional[0] else {
914            panic!("expected Tuple positional arg");
915        };
916        assert_eq!(items.len(), 2);
917        let items: Vec<&ExtensionValue> = items.iter().collect();
918        assert!(matches!(items[0], ExtensionValue::Enum(s) if s == "HASH"));
919        assert!(matches!(items[1], ExtensionValue::Enum(s) if s == "RANGE"));
920        assert_eq!(inv.args.named.len(), 1);
921    }
922
923    #[test]
924    fn extension_relation_kind_parses_text_prefixes() {
925        assert_eq!(
926            ExtensionRelationKind::from_str("ExtensionLeaf").unwrap(),
927            ExtensionRelationKind::Leaf
928        );
929        assert_eq!(
930            ExtensionRelationKind::from_str("ExtensionSingle").unwrap(),
931            ExtensionRelationKind::Single
932        );
933        assert_eq!(
934            ExtensionRelationKind::from_str("ExtensionMulti").unwrap(),
935            ExtensionRelationKind::Multi
936        );
937    }
938
939    #[test]
940    fn extension_multi_allows_any_child_count() {
941        assert!(ExtensionRelationKind::Multi.validate_child_count(0).is_ok());
942        assert!(ExtensionRelationKind::Multi.validate_child_count(1).is_ok());
943        assert!(ExtensionRelationKind::Multi.validate_child_count(3).is_ok());
944    }
945
946    #[test]
947    fn extension_single_rejects_wrong_child_counts() {
948        assert!(
949            ExtensionRelationKind::Single
950                .validate_child_count(0)
951                .is_err()
952        );
953        assert!(
954            ExtensionRelationKind::Single
955                .validate_child_count(2)
956                .is_err()
957        );
958    }
959
960    #[test]
961    fn test_tuple_textify_roundtrip() {
962        let ctx = TestContext::new();
963        for text in &[
964            "(&HASH, &RANGE)",
965            "(&HASH, 8, 'hello')",
966            "()",
967            "(&HASH,)",
968            "((&HASH, &RANGE), 8)",
969        ] {
970            let val = parse_extension_value(text);
971            let rendered = ctx.textify_no_errors(&val);
972            assert_eq!(&rendered, text, "roundtrip failed for {text}");
973        }
974    }
975
976    #[test]
977    fn test_literal_expression_value_textifies_to_canonical_literal() {
978        let expr = proto::Expression {
979            rex_type: Some(RexType::Literal(proto::expression::Literal {
980                literal_type: Some(LiteralType::I64(42)),
981                nullable: false,
982                type_variation_reference: 0,
983            })),
984        };
985        let value = ExtensionValue::from(expr.clone());
986        let ctx = TestContext::new();
987
988        let rendered = ctx.textify_no_errors(&value);
989        assert_eq!(rendered, "42");
990
991        let parsed = parse_extension_value(&rendered);
992        let parsed_expr = Expr::try_from(&parsed).unwrap();
993        assert_eq!(parsed_expr.as_proto(), &expr);
994    }
995
996    #[test]
997    fn test_extension_parameter_literals_stay_scalar_in_verbose_output() {
998        let ctx = TestContext::new().with_options(OutputOptions::verbose());
999
1000        let scalar = ExtensionValue::from(42_i64);
1001        assert_eq!(ctx.textify_no_errors(&scalar), "42");
1002
1003        let expression = ExtensionValue::from(Expr::from(42_i64));
1004        assert_eq!(ctx.textify_no_errors(&expression), "42:i64");
1005    }
1006
1007    #[test]
1008    fn test_untyped_null_extension_literal_roundtrips() {
1009        let value = parse_extension_value("null");
1010        assert!(matches!(value, ExtensionValue::Null));
1011        assert_eq!(TestContext::new().textify_no_errors(&value), "null");
1012    }
1013
1014    #[test]
1015    fn test_typed_extension_literal_parses_as_expression() {
1016        let value = parse_extension_value("null:i64?");
1017        assert!(i64::try_from(&value).is_err());
1018
1019        let expr = Expr::try_from(&value).unwrap();
1020        assert_eq!(ctx_text(&expr), "null:i64?");
1021    }
1022
1023    fn ctx_text(value: &Expr) -> String {
1024        TestContext::new().textify_no_errors(value)
1025    }
1026}