Skip to main content

substrait_explain/parser/
extensions.rs

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