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