substrait_explain/extensions/
simple.rs

1use std::collections::BTreeMap;
2use std::collections::btree_map::Entry;
3use std::fmt;
4
5use pext::simple_extension_declaration::MappingType;
6use substrait::proto::extensions as pext;
7use thiserror::Error;
8
9pub const EXTENSIONS_HEADER: &str = "=== Extensions";
10pub const EXTENSION_URNS_HEADER: &str = "URNs:";
11pub const EXTENSION_FUNCTIONS_HEADER: &str = "Functions:";
12pub const EXTENSION_TYPES_HEADER: &str = "Types:";
13pub const EXTENSION_TYPE_VARIATIONS_HEADER: &str = "Type Variations:";
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
16pub enum ExtensionKind {
17    // Urn,
18    Function,
19    Type,
20    TypeVariation,
21}
22
23impl ExtensionKind {
24    pub fn name(&self) -> &'static str {
25        match self {
26            // ExtensionKind::Urn => "urn",
27            ExtensionKind::Function => "function",
28            ExtensionKind::Type => "type",
29            ExtensionKind::TypeVariation => "type_variation",
30        }
31    }
32}
33
34impl fmt::Display for ExtensionKind {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            // ExtensionKind::Urn => write!(f, "URN"),
38            ExtensionKind::Function => write!(f, "Function"),
39            ExtensionKind::Type => write!(f, "Type"),
40            ExtensionKind::TypeVariation => write!(f, "Type Variation"),
41        }
42    }
43}
44
45#[derive(Error, Debug, PartialEq, Clone)]
46pub enum InsertError {
47    #[error("Extension declaration missing mapping type")]
48    MissingMappingType,
49
50    #[error("Duplicate URN anchor {anchor} for {prev} and {name}")]
51    DuplicateUrnAnchor {
52        anchor: u32,
53        prev: String,
54        name: String,
55    },
56
57    #[error("Duplicate extension {kind} anchor {anchor} for {prev} and {name}")]
58    DuplicateAnchor {
59        kind: ExtensionKind,
60        anchor: u32,
61        prev: String,
62        name: String,
63    },
64
65    #[error("Missing URN anchor {urn} for extension {kind} anchor {anchor} name {name}")]
66    MissingUrn {
67        kind: ExtensionKind,
68        anchor: u32,
69        name: String,
70        urn: u32,
71    },
72
73    #[error(
74        "Duplicate extension {kind} anchor {anchor} for {prev} and {name}, also missing URN {urn}"
75    )]
76    DuplicateAndMissingUrn {
77        kind: ExtensionKind,
78        anchor: u32,
79        prev: String,
80        name: String,
81        urn: u32,
82    },
83}
84
85/// A Substrait compound function name, e.g. `"equal:any_any"` or `"add"`.
86///
87/// The name before the first `:` is the *base name*; the part after is the
88/// *type-signature suffix*.  For plain names with no `:` the base name is the
89/// full name and `has_signature` returns `false`.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct CompoundName {
92    /// Full name including the signature suffix, e.g. `"equal:any_any"`.
93    name: String,
94    /// Byte index of the `:` separator, or `name.len()` when absent.
95    index: usize,
96}
97
98impl CompoundName {
99    pub fn new(name: impl Into<String>) -> Self {
100        let name = name.into();
101        let index = name.find(':').unwrap_or(name.len());
102        Self { name, index }
103    }
104
105    /// The base name (part before the first `:`), e.g. `"equal"`.
106    pub fn base(&self) -> &str {
107        &self.name[..self.index]
108    }
109
110    /// The full compound name, e.g. `"equal:any_any"`.
111    pub fn full(&self) -> &str {
112        &self.name
113    }
114
115    /// `true` when the name includes a signature suffix (contains `:`).
116    pub fn has_signature(&self) -> bool {
117        self.index < self.name.len()
118    }
119}
120
121impl fmt::Display for CompoundName {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        f.write_str(&self.name)
124    }
125}
126
127/// ExtensionLookup contains mappings from anchors to extension URNs, functions,
128/// types, and type variations.
129#[derive(Default, Debug, Clone, PartialEq)]
130pub struct SimpleExtensions {
131    // Maps from extension URN anchor to URN
132    urns: BTreeMap<u32, String>,
133    // Maps from anchor and extension kind to (URN anchor, name)
134    extensions: BTreeMap<(u32, ExtensionKind), (u32, CompoundName)>,
135}
136
137impl SimpleExtensions {
138    pub fn new() -> Self {
139        Self::default()
140    }
141
142    pub fn from_extensions<'a>(
143        urns: impl IntoIterator<Item = &'a pext::SimpleExtensionUrn>,
144        extensions: impl IntoIterator<Item = &'a pext::SimpleExtensionDeclaration>,
145    ) -> (Self, Vec<InsertError>) {
146        // TODO: this checks for missing URNs and duplicate anchors, but not
147        // duplicate names with the same anchor.
148
149        let mut exts = Self::new();
150
151        let mut errors = Vec::<InsertError>::new();
152
153        for urn in urns {
154            if let Err(e) = exts.add_extension_urn(urn.urn.clone(), urn.extension_urn_anchor) {
155                errors.push(e);
156            }
157        }
158
159        for extension in extensions {
160            match &extension.mapping_type {
161                Some(MappingType::ExtensionType(t)) => {
162                    if let Err(e) = exts.add_extension(
163                        ExtensionKind::Type,
164                        t.extension_urn_reference,
165                        t.type_anchor,
166                        t.name.clone(),
167                    ) {
168                        errors.push(e);
169                    }
170                }
171                Some(MappingType::ExtensionFunction(f)) => {
172                    if let Err(e) = exts.add_extension(
173                        ExtensionKind::Function,
174                        f.extension_urn_reference,
175                        f.function_anchor,
176                        f.name.clone(),
177                    ) {
178                        errors.push(e);
179                    }
180                }
181                Some(MappingType::ExtensionTypeVariation(v)) => {
182                    if let Err(e) = exts.add_extension(
183                        ExtensionKind::TypeVariation,
184                        v.extension_urn_reference,
185                        v.type_variation_anchor,
186                        v.name.clone(),
187                    ) {
188                        errors.push(e);
189                    }
190                }
191                None => {
192                    errors.push(InsertError::MissingMappingType);
193                }
194            }
195        }
196
197        (exts, errors)
198    }
199
200    pub fn add_extension_urn(&mut self, urn: String, anchor: u32) -> Result<(), InsertError> {
201        match self.urns.entry(anchor) {
202            Entry::Occupied(e) => {
203                return Err(InsertError::DuplicateUrnAnchor {
204                    anchor,
205                    prev: e.get().clone(),
206                    name: urn,
207                });
208            }
209            Entry::Vacant(e) => {
210                e.insert(urn);
211            }
212        }
213        Ok(())
214    }
215
216    pub fn add_extension(
217        &mut self,
218        kind: ExtensionKind,
219        urn: u32,
220        anchor: u32,
221        name: String,
222    ) -> Result<(), InsertError> {
223        let missing_urn = !self.urns.contains_key(&urn);
224
225        let prev = match self.extensions.entry((anchor, kind)) {
226            Entry::Occupied(e) => Some(e.get().1.full().to_string()),
227            Entry::Vacant(v) => {
228                v.insert((urn, CompoundName::new(name.clone())));
229                None
230            }
231        };
232
233        match (missing_urn, prev) {
234            (true, Some(prev)) => Err(InsertError::DuplicateAndMissingUrn {
235                kind,
236                anchor,
237                prev,
238                name,
239                urn,
240            }),
241            (false, Some(prev)) => Err(InsertError::DuplicateAnchor {
242                kind,
243                anchor,
244                prev,
245                name,
246            }),
247            (true, None) => Err(InsertError::MissingUrn {
248                kind,
249                anchor,
250                name,
251                urn,
252            }),
253            (false, None) => Ok(()),
254        }
255    }
256
257    pub fn is_empty(&self) -> bool {
258        self.urns.is_empty() && self.extensions.is_empty()
259    }
260
261    /// Convert the extension URNs to protobuf format for Plan construction.
262    pub fn to_extension_urns(&self) -> Vec<pext::SimpleExtensionUrn> {
263        self.urns
264            .iter()
265            .map(|(anchor, urn)| pext::SimpleExtensionUrn {
266                extension_urn_anchor: *anchor,
267                urn: urn.clone(),
268            })
269            .collect()
270    }
271
272    /// Convert the extensions to protobuf format for Plan construction.
273    pub fn to_extension_declarations(&self) -> Vec<pext::SimpleExtensionDeclaration> {
274        self.extensions
275            .iter()
276            .map(|((anchor, kind), (urn_ref, name))| {
277                let mapping_type = match kind {
278                    ExtensionKind::Function => MappingType::ExtensionFunction(
279                        #[allow(deprecated)]
280                        pext::simple_extension_declaration::ExtensionFunction {
281                            extension_urn_reference: *urn_ref,
282                            extension_uri_reference: Default::default(), // deprecated
283                            function_anchor: *anchor,
284                            name: name.full().to_string(),
285                        },
286                    ),
287                    ExtensionKind::Type => MappingType::ExtensionType(
288                        #[allow(deprecated)]
289                        pext::simple_extension_declaration::ExtensionType {
290                            extension_urn_reference: *urn_ref,
291                            extension_uri_reference: Default::default(), // deprecated
292                            type_anchor: *anchor,
293                            name: name.full().to_string(),
294                        },
295                    ),
296                    ExtensionKind::TypeVariation => MappingType::ExtensionTypeVariation(
297                        #[allow(deprecated)]
298                        pext::simple_extension_declaration::ExtensionTypeVariation {
299                            extension_urn_reference: *urn_ref,
300                            extension_uri_reference: Default::default(), // deprecated
301                            type_variation_anchor: *anchor,
302                            name: name.full().to_string(),
303                        },
304                    ),
305                };
306                pext::SimpleExtensionDeclaration {
307                    mapping_type: Some(mapping_type),
308                }
309            })
310            .collect()
311    }
312
313    /// Write the extensions to the given writer, with the given indent.
314    ///
315    /// The header will be included if there are any extensions.
316    pub fn write<W: fmt::Write>(&self, w: &mut W, indent: &str) -> fmt::Result {
317        if self.is_empty() {
318            // No extensions, so no need to write anything.
319            return Ok(());
320        }
321
322        writeln!(w, "{EXTENSIONS_HEADER}")?;
323        if !self.urns.is_empty() {
324            writeln!(w, "{EXTENSION_URNS_HEADER}")?;
325            for (anchor, urn) in &self.urns {
326                writeln!(w, "{indent}@{anchor:3}: {urn}")?;
327            }
328        }
329
330        let kinds_and_headers = [
331            (ExtensionKind::Function, EXTENSION_FUNCTIONS_HEADER),
332            (ExtensionKind::Type, EXTENSION_TYPES_HEADER),
333            (
334                ExtensionKind::TypeVariation,
335                EXTENSION_TYPE_VARIATIONS_HEADER,
336            ),
337        ];
338        for (kind, header) in kinds_and_headers {
339            let mut filtered = self
340                .extensions
341                .iter()
342                .filter(|((_a, k), _)| *k == kind)
343                .peekable();
344            if filtered.peek().is_none() {
345                continue;
346            }
347
348            writeln!(w, "{header}")?;
349            for ((anchor, _), (urn_ref, name)) in filtered {
350                writeln!(w, "{indent}#{anchor:3} @{urn_ref:3}: {name}")?;
351            }
352        }
353        Ok(())
354    }
355
356    pub fn to_string(&self, indent: &str) -> String {
357        let mut output = String::new();
358        self.write(&mut output, indent).unwrap();
359        output
360    }
361}
362
363#[derive(Error, Debug, Clone, PartialEq)]
364pub enum MissingReference {
365    #[error("Missing URN for {0}")]
366    MissingUrn(u32),
367    #[error("Missing anchor for {0}: {1}")]
368    MissingAnchor(ExtensionKind, u32),
369    #[error("Missing name for {0}: {1}")]
370    MissingName(ExtensionKind, String),
371    #[error("Mismatched {0}: {1}#{2}")]
372    /// When the name of the value does not match the expected name
373    Mismatched(ExtensionKind, String, u32),
374    #[error("Duplicate name without anchor for {0}: {1}")]
375    DuplicateName(ExtensionKind, String),
376}
377
378#[derive(Debug, Clone, PartialEq)]
379pub struct SimpleExtension {
380    pub kind: ExtensionKind,
381    pub name: String,
382    pub anchor: u32,
383    pub urn: u32,
384}
385
386/// The result of resolving a function anchor to its full metadata.
387pub struct ResolvedFunction<'a> {
388    pub anchor: u32,
389    pub urn: u32,
390    /// The full compound name stored for this anchor.
391    pub name: &'a CompoundName,
392    /// `true` when the base name is unique across all registered functions
393    /// (controls whether the signature suffix is needed in compact mode).
394    pub base_name_unique: bool,
395    /// `true` when the full compound name is unique across all registered
396    /// functions (controls whether the `#anchor` suffix is needed).
397    pub name_unique: bool,
398}
399
400impl SimpleExtensions {
401    pub fn find_urn(&self, anchor: u32) -> Result<&str, MissingReference> {
402        self.urns
403            .get(&anchor)
404            .map(String::as_str)
405            .ok_or(MissingReference::MissingUrn(anchor))
406    }
407
408    pub fn find_by_anchor(
409        &self,
410        kind: ExtensionKind,
411        anchor: u32,
412    ) -> Result<(u32, &CompoundName), MissingReference> {
413        let &(urn, ref name) = self
414            .extensions
415            .get(&(anchor, kind))
416            .ok_or(MissingReference::MissingAnchor(kind, anchor))?;
417
418        Ok((urn, name))
419    }
420
421    pub fn find_by_name(&self, kind: ExtensionKind, name: &str) -> Result<u32, MissingReference> {
422        let mut matches = self
423            .extensions
424            .iter()
425            .filter(move |((_a, k), (_, n))| *k == kind && n.full() == name)
426            .map(|((anchor, _), _)| *anchor);
427
428        let anchor = matches
429            .next()
430            .ok_or(MissingReference::MissingName(kind, name.to_string()))?;
431
432        match matches.next() {
433            Some(_) => Err(MissingReference::DuplicateName(kind, name.to_string())),
434            None => Ok(anchor),
435        }
436    }
437
438    /// Returns `true` when no other extension of the same kind has the same
439    /// full compound name (i.e. the anchor display can be suppressed).
440    ///
441    /// Returns `Err` when `anchor` is not registered for `kind`.
442    pub fn is_name_unique(
443        &self,
444        kind: ExtensionKind,
445        anchor: u32,
446        name: &str,
447    ) -> Result<bool, MissingReference> {
448        let mut found = false;
449        let mut other = false;
450        for (&(a, k), (_, n)) in self.extensions.iter() {
451            if k != kind {
452                continue;
453            }
454
455            if a == anchor {
456                found = true;
457                if n.full() != name {
458                    return Err(MissingReference::Mismatched(kind, name.to_string(), anchor));
459                }
460                continue;
461            }
462
463            if n.full() != name {
464                // Neither anchor nor name match, so this is irrelevant.
465                continue;
466            }
467
468            // At this point, the anchor is different, but the name is the same.
469            other = true;
470            if found {
471                break;
472            }
473        }
474
475        match (found, other) {
476            // Found the one we're looking for, and no other matches.
477            (true, false) => Ok(true),
478            // Found the one we're looking for, and another match.
479            (true, true) => Ok(false),
480            // Didn't find the one we're looking for.
481            (false, _) => Err(MissingReference::MissingAnchor(kind, anchor)),
482        }
483    }
484
485    /// Look up a function anchor and return its full resolution metadata.
486    /// The caller already has `anchor` from the
487    /// Substrait plan and needs the name, URN, and uniqueness flags.
488    pub fn lookup_function(&self, anchor: u32) -> Result<ResolvedFunction<'_>, MissingReference> {
489        let (urn, name) = self.find_by_anchor(ExtensionKind::Function, anchor)?;
490        let name_unique = self.is_name_unique(ExtensionKind::Function, anchor, name.full())?;
491        let base_name_unique = self.is_base_name_unique(ExtensionKind::Function, anchor)?;
492        Ok(ResolvedFunction {
493            anchor,
494            urn,
495            name,
496            name_unique,
497            base_name_unique,
498        })
499    }
500
501    /// Resolve a function name (plain or compound) with an optional explicit
502    /// anchor to a [`ResolvedFunction`].
503    /// the caller has a text name and an optional anchor
504    /// from the plan source and needs the canonical anchor plus uniqueness info.
505    ///
506    /// * `anchor = Some(a)` — validates that `name` matches the stored name
507    ///   (exact compound-name match **or** base-name match, e.g. `"equal"`
508    ///   matches stored `"equal:any_any"`).
509    /// * `anchor = None` — tries an exact match first; if not found and
510    ///   `name` contains no `:`, falls back to base-name search so that
511    ///   `equal(…)` resolves when `equal:any_any` is the only overload.
512    pub fn resolve_function(
513        &self,
514        name: &str,
515        anchor: Option<u32>,
516    ) -> Result<ResolvedFunction<'_>, MissingReference> {
517        let resolved_anchor = match anchor {
518            Some(a) => {
519                let (_, stored) = self.find_by_anchor(ExtensionKind::Function, a)?;
520                if stored.full() == name || stored.base() == name {
521                    a
522                } else {
523                    return Err(MissingReference::Mismatched(
524                        ExtensionKind::Function,
525                        name.to_string(),
526                        a,
527                    ));
528                }
529            }
530            None => match self.find_by_name(ExtensionKind::Function, name) {
531                Ok(a) => a,
532                Err(MissingReference::MissingName(_, _)) if !name.contains(':') => {
533                    self.find_by_base_name(ExtensionKind::Function, name)?
534                }
535                Err(e) => return Err(e),
536            },
537        };
538        self.lookup_function(resolved_anchor)
539    }
540
541    fn is_base_name_unique(
542        &self,
543        kind: ExtensionKind,
544        anchor: u32,
545    ) -> Result<bool, MissingReference> {
546        let (_, name) = self.find_by_anchor(kind, anchor)?;
547        let my_base = name.base();
548
549        let other_exists = self
550            .extensions
551            .iter()
552            .any(|(&(a, k), (_, n))| k == kind && a != anchor && n.base() == my_base);
553
554        Ok(!other_exists)
555    }
556
557    fn find_by_base_name(&self, kind: ExtensionKind, base: &str) -> Result<u32, MissingReference> {
558        let mut matches = self
559            .extensions
560            .iter()
561            .filter(|&(&(_a, k), (_, n))| k == kind && n.base() == base)
562            .map(|(&(anchor, _), _)| anchor);
563
564        let anchor = matches
565            .next()
566            .ok_or_else(|| MissingReference::MissingName(kind, base.to_string()))?;
567
568        match matches.next() {
569            Some(_) => Err(MissingReference::DuplicateName(kind, base.to_string())),
570            None => Ok(anchor),
571        }
572    }
573}
574
575#[cfg(test)]
576mod tests {
577    use pext::simple_extension_declaration::{
578        ExtensionFunction, ExtensionType, ExtensionTypeVariation, MappingType,
579    };
580    use substrait::proto::extensions as pext;
581
582    use super::*;
583
584    fn new_urn(anchor: u32, urn_str: &str) -> pext::SimpleExtensionUrn {
585        pext::SimpleExtensionUrn {
586            extension_urn_anchor: anchor,
587            urn: urn_str.to_string(),
588        }
589    }
590
591    fn new_ext_fn(anchor: u32, urn_ref: u32, name: &str) -> pext::SimpleExtensionDeclaration {
592        pext::SimpleExtensionDeclaration {
593            #[allow(deprecated)]
594            mapping_type: Some(MappingType::ExtensionFunction(ExtensionFunction {
595                extension_urn_reference: urn_ref,
596                extension_uri_reference: Default::default(), // deprecated
597                function_anchor: anchor,
598                name: name.to_string(),
599            })),
600        }
601    }
602
603    fn new_ext_type(anchor: u32, urn_ref: u32, name: &str) -> pext::SimpleExtensionDeclaration {
604        #[allow(deprecated)]
605        pext::SimpleExtensionDeclaration {
606            mapping_type: Some(MappingType::ExtensionType(ExtensionType {
607                extension_urn_reference: urn_ref,
608                extension_uri_reference: Default::default(), // deprecated
609                type_anchor: anchor,
610                name: name.to_string(),
611            })),
612        }
613    }
614
615    fn new_type_var(anchor: u32, urn_ref: u32, name: &str) -> pext::SimpleExtensionDeclaration {
616        pext::SimpleExtensionDeclaration {
617            #[allow(deprecated)]
618            mapping_type: Some(MappingType::ExtensionTypeVariation(
619                ExtensionTypeVariation {
620                    extension_urn_reference: urn_ref,
621                    extension_uri_reference: Default::default(), // deprecated
622                    type_variation_anchor: anchor,
623                    name: name.to_string(),
624                },
625            )),
626        }
627    }
628
629    fn assert_no_errors(errs: &[InsertError]) {
630        for err in errs {
631            println!("Error: {err:?}");
632        }
633        assert!(errs.is_empty());
634    }
635
636    fn unwrap_new_extensions<'a>(
637        urns: impl IntoIterator<Item = &'a pext::SimpleExtensionUrn>,
638        extensions: impl IntoIterator<Item = &'a pext::SimpleExtensionDeclaration>,
639    ) -> SimpleExtensions {
640        let (exts, errs) = SimpleExtensions::from_extensions(urns, extensions);
641        assert_no_errors(&errs);
642        exts
643    }
644
645    #[test]
646    fn test_extension_lookup_empty() {
647        let lookup = SimpleExtensions::new();
648        assert!(lookup.find_urn(1).is_err());
649        assert!(lookup.find_by_anchor(ExtensionKind::Function, 1).is_err());
650        assert!(lookup.find_by_anchor(ExtensionKind::Type, 1).is_err());
651        assert!(
652            lookup
653                .find_by_anchor(ExtensionKind::TypeVariation, 1)
654                .is_err()
655        );
656        assert!(lookup.find_by_name(ExtensionKind::Function, "any").is_err());
657        assert!(lookup.find_by_name(ExtensionKind::Type, "any").is_err());
658        assert!(
659            lookup
660                .find_by_name(ExtensionKind::TypeVariation, "any")
661                .is_err()
662        );
663    }
664
665    #[test]
666    fn test_from_extensions_basic() {
667        let urns = vec![new_urn(1, "urn1"), new_urn(2, "urn2")];
668        let extensions = vec![
669            new_ext_fn(10, 1, "func1"),
670            new_ext_type(20, 1, "type1"),
671            new_type_var(30, 2, "var1"),
672        ];
673        let exts = unwrap_new_extensions(&urns, &extensions);
674
675        assert_eq!(exts.find_urn(1).unwrap(), "urn1");
676        assert_eq!(exts.find_urn(2).unwrap(), "urn2");
677        assert!(exts.find_urn(3).is_err());
678
679        let (urn, name) = exts.find_by_anchor(ExtensionKind::Function, 10).unwrap();
680        assert_eq!(name.full(), "func1");
681        assert_eq!(urn, 1);
682        assert!(exts.find_by_anchor(ExtensionKind::Function, 11).is_err());
683
684        let (urn, name) = exts.find_by_anchor(ExtensionKind::Type, 20).unwrap();
685        assert_eq!(name.full(), "type1");
686        assert_eq!(urn, 1);
687        assert!(exts.find_by_anchor(ExtensionKind::Type, 21).is_err());
688
689        let (urn, name) = exts
690            .find_by_anchor(ExtensionKind::TypeVariation, 30)
691            .unwrap();
692        assert_eq!(name.full(), "var1");
693        assert_eq!(urn, 2);
694        assert!(
695            exts.find_by_anchor(ExtensionKind::TypeVariation, 31)
696                .is_err()
697        );
698    }
699
700    #[test]
701    fn test_from_extensions_duplicates() {
702        let urns = vec![
703            new_urn(1, "urn_old"),
704            new_urn(1, "urn_new"),
705            new_urn(2, "second"),
706        ];
707        let extensions = vec![
708            new_ext_fn(10, 1, "func_old"),
709            new_ext_fn(10, 2, "func_new"), // Duplicate function anchor
710            new_ext_fn(11, 3, "func_missing"),
711        ];
712        let (exts, errs) = SimpleExtensions::from_extensions(&urns, &extensions);
713        assert_eq!(
714            errs,
715            vec![
716                InsertError::DuplicateUrnAnchor {
717                    anchor: 1,
718                    name: "urn_new".to_string(),
719                    prev: "urn_old".to_string()
720                },
721                InsertError::DuplicateAnchor {
722                    kind: ExtensionKind::Function,
723                    anchor: 10,
724                    prev: "func_old".to_string(),
725                    name: "func_new".to_string()
726                },
727                InsertError::MissingUrn {
728                    kind: ExtensionKind::Function,
729                    anchor: 11,
730                    name: "func_missing".to_string(),
731                    urn: 3,
732                },
733            ]
734        );
735
736        // This is a duplicate anchor, so the first one is used.
737        assert_eq!(exts.find_urn(1).unwrap(), "urn_old");
738        let (urn, name) = exts.find_by_anchor(ExtensionKind::Function, 10).unwrap();
739        assert_eq!(urn, 1);
740        assert_eq!(name.full(), "func_old");
741    }
742
743    #[test]
744    fn test_from_extensions_invalid_mapping_type() {
745        let extensions = vec![pext::SimpleExtensionDeclaration { mapping_type: None }];
746
747        let (_exts, errs) = SimpleExtensions::from_extensions(vec![], &extensions);
748        assert_eq!(errs.len(), 1);
749        let err = &errs[0];
750        assert_eq!(err, &InsertError::MissingMappingType);
751    }
752
753    #[test]
754    fn test_find_by_name() {
755        let urns = vec![new_urn(1, "urn1")];
756        let extensions = vec![
757            new_ext_fn(10, 1, "name1"),
758            new_ext_fn(11, 1, "name2"),
759            new_ext_fn(12, 1, "name1"), // Duplicate name
760            new_ext_type(20, 1, "type_name1"),
761            new_type_var(30, 1, "var_name1"),
762        ];
763        let exts = unwrap_new_extensions(&urns, &extensions);
764
765        let err = exts
766            .find_by_name(ExtensionKind::Function, "name1")
767            .unwrap_err();
768        assert_eq!(
769            err,
770            MissingReference::DuplicateName(ExtensionKind::Function, "name1".to_string())
771        );
772
773        let found = exts.find_by_name(ExtensionKind::Function, "name2").unwrap();
774        assert_eq!(found, 11);
775
776        let found = exts
777            .find_by_name(ExtensionKind::Type, "type_name1")
778            .unwrap();
779        assert_eq!(found, 20);
780
781        let err = exts
782            .find_by_name(ExtensionKind::Type, "non_existent_type_name")
783            .unwrap_err();
784        assert_eq!(
785            err,
786            MissingReference::MissingName(
787                ExtensionKind::Type,
788                "non_existent_type_name".to_string()
789            )
790        );
791
792        let found = exts
793            .find_by_name(ExtensionKind::TypeVariation, "var_name1")
794            .unwrap();
795        assert_eq!(found, 30);
796
797        let err = exts
798            .find_by_name(ExtensionKind::TypeVariation, "non_existent_var_name")
799            .unwrap_err();
800        assert_eq!(
801            err,
802            MissingReference::MissingName(
803                ExtensionKind::TypeVariation,
804                "non_existent_var_name".to_string()
805            )
806        );
807    }
808
809    #[test]
810    fn test_display_extension_lookup_empty() {
811        let lookup = SimpleExtensions::new();
812        let mut output = String::new();
813        lookup.write(&mut output, "  ").unwrap();
814        let expected = r"";
815        assert_eq!(output, expected.trim_start());
816    }
817
818    #[test]
819    fn test_display_extension_lookup_with_content() {
820        let urns = vec![
821            new_urn(1, "/my/urn1"),
822            new_urn(42, "/another/urn"),
823            new_urn(4091, "/big/anchor"),
824        ];
825        let extensions = vec![
826            new_ext_fn(10, 1, "my_func"),
827            new_ext_type(20, 1, "my_type"),
828            new_type_var(30, 42, "my_var"),
829            new_ext_fn(11, 42, "another_func"),
830            new_ext_fn(108812, 4091, "big_func"),
831        ];
832        let exts = unwrap_new_extensions(&urns, &extensions);
833        let display_str = exts.to_string("  ");
834
835        let expected = r"
836=== Extensions
837URNs:
838  @  1: /my/urn1
839  @ 42: /another/urn
840  @4091: /big/anchor
841Functions:
842  # 10 @  1: my_func
843  # 11 @ 42: another_func
844  #108812 @4091: big_func
845Types:
846  # 20 @  1: my_type
847Type Variations:
848  # 30 @ 42: my_var
849";
850        assert_eq!(display_str, expected.trim_start());
851    }
852
853    #[test]
854    fn test_extensions_output() {
855        // Manually build the extensions
856        let mut extensions = SimpleExtensions::new();
857        extensions
858            .add_extension_urn("/urn/common".to_string(), 1)
859            .unwrap();
860        extensions
861            .add_extension_urn("/urn/specific_funcs".to_string(), 2)
862            .unwrap();
863        extensions
864            .add_extension(ExtensionKind::Function, 1, 10, "func_a".to_string())
865            .unwrap();
866        extensions
867            .add_extension(ExtensionKind::Function, 2, 11, "func_b_special".to_string())
868            .unwrap();
869        extensions
870            .add_extension(ExtensionKind::Type, 1, 20, "SomeType".to_string())
871            .unwrap();
872        extensions
873            .add_extension(ExtensionKind::TypeVariation, 2, 30, "VarX".to_string())
874            .unwrap();
875
876        // Convert to string
877        let output = extensions.to_string("  ");
878
879        // The output should match the expected format
880        let expected_output = r#"
881=== Extensions
882URNs:
883  @  1: /urn/common
884  @  2: /urn/specific_funcs
885Functions:
886  # 10 @  1: func_a
887  # 11 @  2: func_b_special
888Types:
889  # 20 @  1: SomeType
890Type Variations:
891  # 30 @  2: VarX
892"#;
893
894        assert_eq!(output, expected_output.trim_start());
895    }
896
897    #[test]
898    fn test_compound_name_plain() {
899        let n = CompoundName::new("add");
900        assert_eq!(n.full(), "add");
901        assert_eq!(n.base(), "add");
902        assert!(!n.has_signature());
903
904        let n2 = CompoundName::new("coalesce");
905        assert_eq!(n2.full(), "coalesce");
906        assert_eq!(n2.base(), "coalesce");
907    }
908
909    #[test]
910    fn test_compound_name_with_signature() {
911        let n = CompoundName::new("equal:any_any");
912        assert_eq!(n.full(), "equal:any_any");
913        assert_eq!(n.base(), "equal");
914        assert!(n.has_signature());
915
916        let n2 = CompoundName::new("regexp_match_substring:str_str_i64");
917        assert_eq!(n2.base(), "regexp_match_substring");
918        assert_eq!(n2.full(), "regexp_match_substring:str_str_i64");
919        assert!(n2.has_signature());
920
921        let n3 = CompoundName::new("add:i64_i64");
922        assert_eq!(n3.base(), "add");
923    }
924
925    #[test]
926    fn test_compound_name_trailing_colon() {
927        // Edge case: trailing colon → empty signature suffix, base is the prefix
928        let n = CompoundName::new("foo:");
929        assert_eq!(n.base(), "foo");
930        assert_eq!(n.full(), "foo:");
931        assert!(n.has_signature());
932    }
933
934    // ---- Tests for lookup_function ----
935
936    fn make_overloaded_extensions() -> SimpleExtensions {
937        let urns = vec![new_urn(1, "urn:comparison")];
938        let extensions = vec![
939            new_ext_fn(1, 1, "equal:any_any"),
940            new_ext_fn(2, 1, "equal:str_str"),
941            new_ext_fn(3, 1, "add:i64_i64"),
942        ];
943        unwrap_new_extensions(&urns, &extensions)
944    }
945
946    #[test]
947    fn test_lookup_function_uniqueness_flags() {
948        // `equal:any_any` and `equal:str_str` share the base name "equal" →
949        // base_name_unique false, compound name unique within the one URN.
950        // `add:i64_i64` is the only "add" → both flags true.
951        let exts = make_overloaded_extensions();
952
953        let r1 = exts.lookup_function(1).unwrap();
954        assert_eq!(r1.name.full(), "equal:any_any");
955        assert!(!r1.base_name_unique, "two 'equal' overloads");
956        assert!(r1.name_unique, "compound name 'equal:any_any' is unique");
957
958        let r2 = exts.lookup_function(2).unwrap();
959        assert_eq!(r2.name.full(), "equal:str_str");
960        assert!(!r2.base_name_unique);
961        assert!(r2.name_unique);
962
963        let r3 = exts.lookup_function(3).unwrap();
964        assert_eq!(r3.name.full(), "add:i64_i64");
965        assert!(r3.base_name_unique, "only one 'add' overload");
966        assert!(r3.name_unique, "compound name appears only once");
967    }
968
969    #[test]
970    fn test_lookup_function_missing_anchor() {
971        let exts = SimpleExtensions::new();
972        assert!(exts.lookup_function(99).is_err());
973    }
974
975    #[test]
976    fn test_lookup_function_plain_name_overloaded_across_urns() {
977        // Same plain name in two URNs → base_name_unique false, name_unique false.
978        let urns = vec![new_urn(1, "urn1"), new_urn(2, "urn2")];
979        let extensions = vec![
980            new_ext_fn(1, 1, "duplicated"),
981            new_ext_fn(2, 2, "duplicated"),
982        ];
983        let exts = unwrap_new_extensions(&urns, &extensions);
984
985        let r = exts.lookup_function(1).unwrap();
986        assert!(!r.base_name_unique);
987        assert!(!r.name_unique);
988    }
989
990    #[test]
991    fn test_lookup_function_different_base_names_each_unique() {
992        // `equal:any_any` and `like:str_str` have distinct base names → each unique.
993        let urns = vec![new_urn(1, "urn1")];
994        let extensions = vec![
995            new_ext_fn(1, 1, "equal:any_any"),
996            new_ext_fn(2, 1, "like:str_str"),
997        ];
998        let exts = unwrap_new_extensions(&urns, &extensions);
999
1000        assert!(exts.lookup_function(1).unwrap().base_name_unique);
1001        assert!(exts.lookup_function(2).unwrap().base_name_unique);
1002    }
1003
1004    // ---- Tests for resolve_function ----
1005
1006    fn make_resolve_extensions() -> SimpleExtensions {
1007        // Mirrors the fixture used in the old parser/types.rs tests.
1008        let urns = vec![new_urn(1, "test_urn")];
1009        let extensions = vec![
1010            new_ext_fn(1, 1, "equal:any_any"),
1011            new_ext_fn(2, 1, "equal:str_str"),
1012            new_ext_fn(3, 1, "add:i64_i64"),
1013        ];
1014        unwrap_new_extensions(&urns, &extensions)
1015    }
1016
1017    #[test]
1018    fn test_resolve_function_with_anchor() {
1019        // Explicit anchor: exact compound name, base name, and mismatched name.
1020        let exts = make_resolve_extensions();
1021
1022        // Exact compound name matches stored name → resolves.
1023        assert_eq!(
1024            exts.resolve_function("equal:any_any", Some(1))
1025                .unwrap()
1026                .anchor,
1027            1
1028        );
1029
1030        // Base name "equal" matches stored "equal:any_any" → resolves.
1031        assert_eq!(exts.resolve_function("equal", Some(1)).unwrap().anchor, 1);
1032
1033        // "like" does not match stored "equal:any_any" → error.
1034        assert!(exts.resolve_function("like", Some(1)).is_err());
1035    }
1036
1037    #[test]
1038    fn test_resolve_function_without_anchor() {
1039        // No anchor: exact compound, unique base (fallback), and ambiguous base.
1040        let exts = make_resolve_extensions();
1041
1042        // Exact compound names resolve directly.
1043        assert_eq!(
1044            exts.resolve_function("equal:any_any", None).unwrap().anchor,
1045            1
1046        );
1047        assert_eq!(
1048            exts.resolve_function("equal:str_str", None).unwrap().anchor,
1049            2
1050        );
1051
1052        // "add" is the only overload → base-name fallback finds anchor 3.
1053        assert_eq!(exts.resolve_function("add", None).unwrap().anchor, 3);
1054
1055        // "equal" has two overloads → DuplicateName error.
1056        assert!(exts.resolve_function("equal", None).is_err());
1057    }
1058
1059    #[test]
1060    fn test_resolve_function_plain_stored_name() {
1061        // Functions stored without a signature still resolve by their plain name.
1062        let urns = vec![new_urn(1, "urn")];
1063        let extensions = vec![new_ext_fn(10, 1, "coalesce")];
1064        let exts = unwrap_new_extensions(&urns, &extensions);
1065        assert_eq!(exts.resolve_function("coalesce", None).unwrap().anchor, 10);
1066    }
1067
1068    #[test]
1069    fn test_resolve_function_not_found() {
1070        let exts = SimpleExtensions::new();
1071        assert!(exts.resolve_function("nonexistent", None).is_err());
1072    }
1073
1074    #[test]
1075    fn test_compound_name_roundtrip_in_extensions_section() {
1076        // Verify that compound names survive a write → parse roundtrip through
1077        // the Extensions section text format.
1078        let urns = vec![new_urn(1, "substrait:functions_comparison")];
1079        let extensions = vec![
1080            new_ext_fn(1, 1, "equal:any_any"),
1081            new_ext_fn(2, 1, "equal:str_str"),
1082        ];
1083        let exts = unwrap_new_extensions(&urns, &extensions);
1084
1085        let text = exts.to_string("  ");
1086        assert!(
1087            text.contains("equal:any_any"),
1088            "compound name must appear in output"
1089        );
1090        assert!(
1091            text.contains("equal:str_str"),
1092            "compound name must appear in output"
1093        );
1094    }
1095}