1use std::collections::{HashMap, hash_map::Entry};
14
15use super::{ExtensionFile, SimpleExtensions, SimpleExtensionsError, types::CustomType};
16use crate::urn::Urn;
17
18#[derive(Debug)]
23pub struct Registry {
24 extensions: HashMap<Urn, SimpleExtensions>,
26}
27
28impl Registry {
29 pub fn new<I: IntoIterator<Item = ExtensionFile>>(
33 extensions: I,
34 ) -> Result<Self, SimpleExtensionsError> {
35 let mut map = HashMap::new();
36 for ExtensionFile { urn, extension } in extensions {
37 match map.entry(urn.clone()) {
38 Entry::Occupied(_) => return Err(SimpleExtensionsError::DuplicateUrn(urn)),
39 Entry::Vacant(entry) => {
40 entry.insert(extension);
41 }
42 }
43 }
44 Ok(Self { extensions: map })
45 }
46
47 pub fn extensions(&self) -> impl Iterator<Item = (&Urn, &SimpleExtensions)> {
49 self.extensions.iter()
50 }
51
52 #[cfg(feature = "extensions")]
54 pub fn from_core_extensions() -> Self {
55 use crate::extensions::EXTENSIONS;
56
57 let extensions: HashMap<Urn, SimpleExtensions> = EXTENSIONS
59 .iter()
60 .map(|(orig_urn, simple_extensions)| {
61 let ExtensionFile { urn, extension } = ExtensionFile::create(simple_extensions.clone())
62 .unwrap_or_else(|err| panic!("Core extensions should be valid, but failed to create extension file for {orig_urn}: {err}"));
63 debug_assert_eq!(orig_urn, &urn);
64 (urn, extension)
65 })
66 .collect();
67
68 Self { extensions }
69 }
70
71 fn get_extension(&self, urn: &Urn) -> Option<&SimpleExtensions> {
72 self.extensions.get(urn)
73 }
74
75 pub fn get_type(&self, urn: &Urn, name: &str) -> Option<&CustomType> {
77 self.get_extension(urn)?.get_type(name)
78 }
79
80 pub fn get_scalar_function(&self, urn: &Urn, name: &str) -> Option<&super::ScalarFunction> {
85 self.get_extension(urn)?.get_scalar_function(name)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::{ExtensionFile, Registry};
92 use crate::parse::text::simple_extensions::{
93 SimpleExtensionsError, scalar_functions::ScalarFunctionError, types::ExtensionTypeError,
94 };
95 use crate::text::simple_extensions::{SimpleExtensions, SimpleExtensionsTypesItem};
96 use crate::urn::Urn;
97 use std::str::FromStr;
98
99 fn extension_file(urn: &str, type_names: &[&str]) -> ExtensionFile {
100 let types = type_names
101 .iter()
102 .map(|name| SimpleExtensionsTypesItem {
103 name: (*name).to_string(),
104 deprecated: None,
105 description: None,
106 metadata: Default::default(),
107 parameters: None,
108 structure: None,
109 variadic: None,
110 })
111 .collect();
112
113 let raw = SimpleExtensions {
114 scalar_functions: vec![],
115 aggregate_functions: vec![],
116 window_functions: vec![],
117 dependencies: Default::default(),
118 metadata: Default::default(),
119 type_variations: vec![],
120 types,
121 urn: urn.to_string(),
122 };
123
124 ExtensionFile::create(raw).expect("valid extension file")
125 }
126
127 #[test]
128 fn test_registry_iteration() {
129 let urns = vec![
130 "extension:example.com:first",
131 "extension:example.com:second",
132 ];
133 let registry =
134 Registry::new(urns.iter().map(|&urn| extension_file(urn, &["type"]))).unwrap();
135
136 let collected: Vec<&Urn> = registry.extensions().map(|(urn, _)| urn).collect();
137 assert_eq!(collected.len(), 2);
138 for urn in urns {
139 assert!(
140 collected
141 .iter()
142 .any(|candidate| candidate.to_string() == urn)
143 );
144 }
145 }
146
147 #[test]
148 fn test_type_lookup() {
149 let urn = Urn::from_str("extension:example.com:test").unwrap();
150 let registry =
151 Registry::new(vec![extension_file(&urn.to_string(), &["test_type"])]).unwrap();
152 let other_urn = Urn::from_str("extension:example.com:other").unwrap();
153
154 let cases = vec![
155 (&urn, "test_type", true),
156 (&urn, "missing", false),
157 (&other_urn, "test_type", false),
158 ];
159
160 for (query_urn, type_name, expected) in cases {
161 assert_eq!(
162 registry.get_type(query_urn, type_name).is_some(),
163 expected,
164 "unexpected lookup result for {query_urn}:{type_name}"
165 );
166 }
167 }
168
169 #[cfg(feature = "extensions")]
170 #[test]
171 fn test_from_core_extensions() {
172 let registry = Registry::from_core_extensions();
173 assert!(registry.extensions().count() > 0);
174
175 let urn = Urn::from_str("extension:io.substrait:functions_geometry").unwrap();
177 let core_extension = registry
178 .get_extension(&urn)
179 .expect("Should find functions_geometry extension");
180
181 let geometry_type = core_extension.get_type("geometry");
182 assert!(
183 geometry_type.is_some(),
184 "Should find 'geometry' type in functions_geometry extension"
185 );
186
187 let type_via_registry = registry.get_type(&urn, "geometry");
189 assert!(type_via_registry.is_some());
190
191 let unsigned_integers_urn =
193 Urn::from_str("extension:io.substrait:unsigned_integers").unwrap();
194 assert!(
195 registry.get_extension(&unsigned_integers_urn).is_some(),
196 "unsigned_integers should be a core extension"
197 );
198 }
199
200 #[test]
201 fn test_unknown_type_without_prefix_fails() {
202 use crate::text::simple_extensions;
203
204 let invalid_extension = SimpleExtensions {
206 scalar_functions: vec![simple_extensions::ScalarFunction {
207 name: "bad_function".to_string(),
208 description: None,
209 metadata: Default::default(),
210 deprecated: None,
211 impls: vec![simple_extensions::ScalarFunctionImplsItem {
212 args: None,
213 deprecated: None,
214 description: None,
215 options: None,
216 variadic: None,
217 session_dependent: None,
218 deterministic: None,
219 nullability: None,
220 return_: simple_extensions::ReturnValue(simple_extensions::Type::String(
221 "point".to_string(), )),
223 implementation: None,
224 }],
225 }],
226 aggregate_functions: vec![],
227 window_functions: vec![],
228 dependencies: Default::default(),
229 metadata: Default::default(),
230 type_variations: vec![],
231 types: vec![],
232 urn: "extension:example.com:invalid".to_string(),
233 };
234
235 let result = ExtensionFile::create(invalid_extension);
236 assert!(
237 result.is_err(),
238 "Should fail when type is missing u! prefix"
239 );
240
241 match result {
242 Err(SimpleExtensionsError::ScalarFunctionError(ScalarFunctionError::TypeError(
243 ExtensionTypeError::UnknownTypeName { name },
244 ))) => {
245 assert_eq!(name, "point");
246 }
247 other => panic!("Expected UnknownTypeName error, got {:?}", other),
248 }
249 }
250
251 fn extension_with_custom_type_reference(
253 urn: &str,
254 function_name: &str,
255 return_type: &str,
256 defined_types: Vec<&str>,
257 ) -> SimpleExtensions {
258 use crate::text::simple_extensions;
259
260 SimpleExtensions {
261 scalar_functions: vec![simple_extensions::ScalarFunction {
262 name: function_name.to_string(),
263 description: None,
264 metadata: Default::default(),
265 deprecated: None,
266 impls: vec![simple_extensions::ScalarFunctionImplsItem {
267 args: None,
268 deprecated: None,
269 description: None,
270 options: None,
271 variadic: None,
272 session_dependent: None,
273 deterministic: None,
274 nullability: None,
275 return_: simple_extensions::ReturnValue(simple_extensions::Type::String(
276 return_type.to_string(),
277 )),
278 implementation: None,
279 }],
280 }],
281 aggregate_functions: vec![],
282 window_functions: vec![],
283 dependencies: Default::default(),
284 metadata: Default::default(),
285 type_variations: vec![],
286 types: defined_types
287 .into_iter()
288 .map(|name| SimpleExtensionsTypesItem {
289 name: name.to_string(),
290 deprecated: None,
291 description: None,
292 metadata: Default::default(),
293 parameters: None,
294 structure: None,
295 variadic: None,
296 })
297 .collect(),
298 urn: urn.to_string(),
299 }
300 }
301
302 #[test]
303 fn test_custom_type_reference_valid() {
304 let extension = extension_with_custom_type_reference(
305 "extension:example.com:valid",
306 "get_point",
307 "u!point",
308 vec!["point"],
309 );
310
311 let result = ExtensionFile::create(extension);
312 assert!(
313 result.is_ok(),
314 "Should succeed when referenced type exists with u! prefix"
315 );
316 }
317
318 #[test]
319 fn test_custom_type_reference_missing() {
320 let extension = extension_with_custom_type_reference(
321 "extension:example.com:invalid",
322 "get_rectangle",
323 "u!rectangle",
324 vec![], );
326
327 let result = ExtensionFile::create(extension);
328 assert!(
329 result.is_err(),
330 "Should fail when referenced type doesn't exist"
331 );
332
333 match result {
334 Err(SimpleExtensionsError::UnresolvedTypeReference { type_name }) => {
335 assert_eq!(type_name, "rectangle");
336 }
337 other => panic!("Expected UnresolvedTypeReference error, got {:?}", other),
338 }
339 }
340
341 #[cfg(feature = "extensions")]
342 #[test]
343 fn test_core_functions_with_variable_returns() {
344 let registry = Registry::from_core_extensions();
345 let urn = Urn::from_str("extension:io.substrait:functions_comparison").unwrap();
346 for name in ["coalesce", "nullif"] {
347 assert!(
348 registry.get_scalar_function(&urn, name).is_some(),
349 "{name} should not be skipped because of its variable return type"
350 );
351 }
352 }
353
354 #[test]
355 fn test_numbered_variable_returns() {
356 use super::super::{Type, TypeKind, argument::ArgumentsItem};
357
358 for (return_type, id, nullable) in [("any1", 1, false), ("any2?", 2, true)] {
359 let yaml = format!(
360 r#"urn: extension:example.com:variables
361scalar_functions:
362 - name: identity
363 impls:
364 - args:
365 - value: {return_type}
366 return: {return_type}
367"#
368 );
369 let file = ExtensionFile::read_from_str(yaml).unwrap();
370 let urn = file.urn().clone();
371 let registry = Registry::new(vec![file]).unwrap();
372 let function = registry.get_scalar_function(&urn, "identity").unwrap();
373 assert_eq!(
374 function.impls[0].return_type,
375 Type {
376 kind: TypeKind::Variable { id },
377 nullable
378 }
379 );
380
381 let ArgumentsItem::ValueArgument(argument) = function.impls[0].args[0].clone() else {
382 panic!("expected value argument");
383 };
384 let raw: crate::text::simple_extensions::ValueArg = argument.into();
385 assert_eq!(
386 raw.value,
387 crate::text::simple_extensions::Type::String(return_type.to_string())
388 );
389 }
390 }
391
392 #[cfg(feature = "extensions")]
393 #[test]
394 fn test_core_functions_with_numeric_parameter_returns() {
395 let registry = Registry::from_core_extensions();
396 let urn = Urn::from_str("extension:io.substrait:functions_string").unwrap();
397 for name in ["lower", "upper"] {
398 assert!(
399 registry.get_scalar_function(&urn, name).is_some(),
400 "{name} should not be skipped because of its named numeric parameters"
401 );
402 }
403 }
404
405 #[test]
406 fn test_numeric_parameter_returns() {
407 use super::super::{IntegerParameter, Type, TypeKind};
408
409 let yaml = r#"urn: extension:example.com:numeric_parameters
410scalar_functions:
411 - name: upper
412 impls:
413 - args:
414 - value: varchar<L>
415 return: varchar<L>
416"#;
417 let file = ExtensionFile::read_from_str(yaml).unwrap();
418 let urn = file.urn().clone();
419 let registry = Registry::new(vec![file]).unwrap();
420 let function = registry.get_scalar_function(&urn, "upper").unwrap();
421 assert_eq!(
422 function.impls[0].return_type,
423 Type::new(
424 TypeKind::VarChar {
425 length: IntegerParameter::Variable("L".to_string()),
426 },
427 false
428 )
429 );
430 }
431
432 #[test]
433 fn test_multi_digit_any_names_are_rejected_in_numeric_parameters() {
434 for name in ["any12", "ANY12"] {
435 let yaml = format!(
436 r#"urn: extension:example.com:numeric_parameters
437scalar_functions:
438 - name: decimal_identity
439 impls:
440 - return: decimal<{name}, 0>
441"#
442 );
443 assert!(
444 ExtensionFile::read_from_str(yaml).is_err(),
445 "{name} must produce an error rather than silently skip the function",
446 );
447 }
448 }
449
450 #[cfg(feature = "extensions")]
451 #[test]
452 fn test_core_function_with_nested_symbolic_return() {
453 use super::super::{IntegerParameter, Type, TypeKind};
454
455 let registry = Registry::from_core_extensions();
456 let urn = Urn::from_str("extension:io.substrait:functions_string").unwrap();
457 let function = registry
458 .get_scalar_function(&urn, "regexp_match_substring_all")
459 .expect("regexp_match_substring_all should not be skipped because it returns List<varchar<L1>>");
460 assert_eq!(
461 function.impls[0].return_type,
462 Type::list(
463 Type::new(
464 TypeKind::VarChar {
465 length: IntegerParameter::Variable("L1".to_string()),
466 },
467 false,
468 ),
469 false,
470 )
471 );
472 }
473
474 #[test]
475 fn test_function_type_returns() {
476 use super::super::{Type, TypeKind};
477
478 let yaml = r#"urn: extension:example.com:function_types
479scalar_functions:
480 - name: transform_values
481 impls:
482 - args:
483 - value: func<any1 -> any2>
484 name: transformer
485 return: func<any1 -> any2>
486"#;
487 let file = ExtensionFile::read_from_str(yaml).unwrap();
488 let urn = file.urn().clone();
489 let registry = Registry::new(vec![file]).unwrap();
490 let function = registry
491 .get_scalar_function(&urn, "transform_values")
492 .unwrap();
493 assert_eq!(
494 function.impls[0].return_type,
495 Type::new(
496 TypeKind::Func {
497 parameters: vec![Type::new(TypeKind::Variable { id: 1 }, false)],
498 return_type: Box::new(Type::new(TypeKind::Variable { id: 2 }, false)),
499 },
500 false,
501 )
502 );
503 }
504
505 #[test]
506 fn test_nested_return_preserves_object_arguments() {
507 use super::super::{Type, TypeKind, argument::ArgumentsItem};
508 use crate::text::simple_extensions as raw;
509
510 let yaml = r#"urn: extension:example.com:nested
511scalar_functions:
512 - name: wrap_field
513 impls:
514 - args:
515 - value: {field: any1}
516 return: list<any1>
517"#;
518 let file = ExtensionFile::read_from_str(yaml).unwrap();
519 let urn = file.urn().clone();
520 let registry = Registry::new(vec![file]).unwrap();
521 let function = registry.get_scalar_function(&urn, "wrap_field").unwrap();
522 assert_eq!(
523 function.impls[0].return_type,
524 Type::list(Type::new(TypeKind::Variable { id: 1 }, false), false)
525 );
526 let ArgumentsItem::ValueArgument(argument) = function.impls[0].args[0].clone() else {
527 panic!("expected value argument");
528 };
529 let argument: raw::ValueArg = argument.into();
530 let raw::Type::Object(fields) = argument.value else {
531 panic!("expected object-form type to be preserved");
532 };
533 assert_eq!(
534 fields["field"],
535 serde_json::Value::String("any1".to_string())
536 );
537 }
538
539 #[test]
540 fn test_missing_symbolic_type_reference() {
541 let raw = extension_with_custom_type_reference(
542 "extension:example.com:missing",
543 "wrap",
544 "list<u!missing<any1>>",
545 vec![],
546 );
547 assert!(matches!(
548 ExtensionFile::create(raw),
549 Err(SimpleExtensionsError::UnresolvedTypeReference { type_name }) if type_name == "missing"
550 ));
551 }
552
553 #[test]
554 fn test_numeric_expression_returns_are_still_skipped() {
555 for return_type in ["varchar<L + 1>", "decimal<P + 1, S>"] {
556 let raw = extension_with_custom_type_reference(
557 "extension:example.com:unsupported",
558 "unsupported",
559 return_type,
560 vec![],
561 );
562 let file = ExtensionFile::create(raw).unwrap();
563 assert!(
564 file.extension()
565 .get_scalar_function("unsupported")
566 .is_none()
567 );
568 }
569 }
570
571 #[cfg(feature = "extensions")]
572 #[test]
573 fn test_scalar_function_parses_completely() {
574 use super::super::{
575 argument::ArgumentsItem,
576 scalar_functions::{Impl, NullabilityHandling, Options},
577 types::*,
578 };
579 use crate::parse::Parse;
580 use crate::text::simple_extensions;
581 use std::collections::HashMap;
582
583 let registry = Registry::from_core_extensions();
584 let functions_arithmetic_urn =
585 Urn::from_str("extension:io.substrait:functions_arithmetic").unwrap();
586
587 let add = registry
588 .get_scalar_function(&functions_arithmetic_urn, "add")
589 .expect("add function should exist");
590
591 assert_eq!(add.name, "add");
593 assert_eq!(add.description, Some("Add two values.".to_string()));
594 assert!(
595 !add.impls.is_empty(),
596 "add should have at least one implementation"
597 );
598
599 let mut ctx = super::super::extensions::TypeContext::default();
601 let expected_impl = Impl {
602 args: vec![
603 ArgumentsItem::ValueArgument(
604 simple_extensions::ValueArg {
605 name: Some("x".to_string()),
606 description: None,
607 value: simple_extensions::Type::String("i8".to_string()),
608 constant: None,
609 }
610 .parse(&mut ctx)
611 .unwrap(),
612 ),
613 ArgumentsItem::ValueArgument(
614 simple_extensions::ValueArg {
615 name: Some("y".to_string()),
616 description: None,
617 value: simple_extensions::Type::String("i8".to_string()),
618 constant: None,
619 }
620 .parse(&mut ctx)
621 .unwrap(),
622 ),
623 ],
624 options: Options({
625 let mut map = HashMap::new();
626 map.insert(
627 "overflow".to_string(),
628 vec![
629 "SILENT".to_string(),
630 "SATURATE".to_string(),
631 "ERROR".to_string(),
632 ],
633 );
634 map
635 }),
636 variadic: None,
637 session_dependent: false,
638 deterministic: true,
639 nullability: NullabilityHandling::Mirror,
640 return_type: Type::new(TypeKind::I8, false),
641 implementation: HashMap::new(),
642 };
643
644 assert_eq!(&add.impls[0], &expected_impl);
645 }
646}