1pub mod builtin;
25pub mod registry;
26pub mod signature;
27pub mod udf;
28
29mod custom;
30mod table;
31
32pub use custom::{
33 AggregateFinalCallback, AggregateStepCallback, CFunctionContext, CustomFunction,
34 CustomFunctionImpl, ScalarFunctionCallback,
35};
36pub use table::{BuiltinTableFunction, TableFunction};
37
38pub mod fulltext;
40pub use fulltext::{FulltextExecutionContext, FulltextFunction};
41
42pub use builtin::vector::VectorFunction;
44
45pub use builtin::sequence::SequenceFunction;
47
48pub use registry::{global_registry, global_registry_ref, FunctionRegistry};
49pub use signature::ValueType;
50pub use udf::{SharedPlugin, UdfError, UdfPlugin};
51
52pub use builtin::container::ContainerFunction;
54pub use builtin::conversion::ConversionFunction;
55pub use builtin::datetime::DateTimeFunction;
56pub use builtin::geography::GeographyFunction;
57pub use builtin::graph::GraphFunction;
58pub use builtin::math::MathFunction;
59pub use builtin::path::PathFunction;
60pub use builtin::regex::RegexFunction;
61pub use builtin::string::StringFunction;
62pub use builtin::utility::UtilityFunction;
63
64use crate::executor::expression::evaluation_context::graph_storage::GraphStorageRef;
65use crate::executor::expression::{ExpressionError, ExpressionErrorType};
66use linkrs_core::types::operators::AggregateFunction;
67use linkrs_core::Value;
68
69#[derive(Debug, Clone)]
71pub enum FunctionRef<'a> {
72 Builtin(&'a BuiltinFunction),
74 Custom(&'a CustomFunction),
76}
77
78#[derive(Debug, Clone)]
80pub enum OwnedFunctionRef {
81 Builtin(BuiltinFunction),
83 Custom(CustomFunction),
85}
86
87impl<'a> From<FunctionRef<'a>> for OwnedFunctionRef {
88 fn from(func_ref: FunctionRef<'a>) -> Self {
89 match func_ref {
90 FunctionRef::Builtin(f) => OwnedFunctionRef::Builtin(f.clone()),
91 FunctionRef::Custom(f) => OwnedFunctionRef::Custom(f.clone()),
92 }
93 }
94}
95
96impl OwnedFunctionRef {
97 pub fn name(&self) -> &str {
98 match self {
99 OwnedFunctionRef::Builtin(f) => f.name(),
100 OwnedFunctionRef::Custom(f) => f.name(),
101 }
102 }
103
104 pub fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
105 match self {
106 OwnedFunctionRef::Builtin(f) => f.execute(args),
107 OwnedFunctionRef::Custom(f) => f.execute(args),
108 }
109 }
110
111 pub fn execute_with_storage(
112 &self,
113 args: &[Value],
114 storage: &GraphStorageRef,
115 ) -> Result<Value, ExpressionError> {
116 match self {
117 OwnedFunctionRef::Builtin(f) => f.execute_with_storage(args, storage),
118 OwnedFunctionRef::Custom(f) => f.execute(args),
119 }
120 }
121
122 pub fn execute_with_cache(
123 &self,
124 args: &[Value],
125 _cache: &mut (),
126 ) -> Result<Value, ExpressionError> {
127 match self {
128 OwnedFunctionRef::Builtin(f) => f.execute_with_cache(args, _cache),
129 OwnedFunctionRef::Custom(f) => f.execute(args), }
131 }
132}
133
134pub trait ExpressionFunction: Send + Sync {
136 fn name(&self) -> &str;
138
139 fn arity(&self) -> usize;
141
142 fn is_variadic(&self) -> bool;
144
145 fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError>;
147
148 fn description(&self) -> &str;
150}
151
152#[derive(Debug, Clone)]
154pub enum BuiltinFunction {
155 Math(MathFunction),
157 String(StringFunction),
159 Regex(RegexFunction),
161 Aggregate(AggregateFunction),
163 Conversion(ConversionFunction),
165 DateTime(DateTimeFunction),
167 Geography(GeographyFunction),
169 Utility(UtilityFunction),
171 Graph(GraphFunction),
173 Container(ContainerFunction),
175 Path(PathFunction),
177 Fulltext(FulltextFunction),
179 Vector(VectorFunction),
181 Window(crate::executor::expression::functions::builtin::window::WindowFunction),
183 Sequence(SequenceFunction),
185}
186
187impl BuiltinFunction {
188 pub fn name(&self) -> &str {
190 match self {
191 BuiltinFunction::Math(f) => f.name(),
192 BuiltinFunction::String(f) => f.name(),
193 BuiltinFunction::Regex(f) => f.name(),
194 BuiltinFunction::Aggregate(f) => f.name(),
195 BuiltinFunction::Conversion(f) => f.name(),
196 BuiltinFunction::DateTime(f) => f.name(),
197 BuiltinFunction::Geography(f) => f.name(),
198 BuiltinFunction::Utility(f) => f.name(),
199 BuiltinFunction::Graph(f) => f.name(),
200 BuiltinFunction::Container(f) => f.name(),
201 BuiltinFunction::Path(f) => f.name(),
202 BuiltinFunction::Fulltext(f) => f.name(),
203 BuiltinFunction::Vector(f) => f.name(),
204 BuiltinFunction::Window(f) => f.name(),
205 BuiltinFunction::Sequence(f) => f.name(),
206 }
207 }
208
209 pub fn arity(&self) -> usize {
211 match self {
212 BuiltinFunction::Math(f) => f.arity(),
213 BuiltinFunction::String(f) => f.arity(),
214 BuiltinFunction::Regex(f) => f.arity(),
215 BuiltinFunction::Aggregate(f) => f.arity(),
216 BuiltinFunction::Conversion(f) => f.arity(),
217 BuiltinFunction::DateTime(f) => f.arity(),
218 BuiltinFunction::Geography(f) => f.arity(),
219 BuiltinFunction::Utility(f) => f.arity(),
220 BuiltinFunction::Graph(f) => f.arity(),
221 BuiltinFunction::Container(f) => f.arity(),
222 BuiltinFunction::Path(f) => f.arity(),
223 BuiltinFunction::Fulltext(f) => f.arity(),
224 BuiltinFunction::Vector(f) => f.arity(),
225 BuiltinFunction::Window(f) => f.arity(),
226 BuiltinFunction::Sequence(f) => f.arity(),
227 }
228 }
229
230 pub fn is_variadic(&self) -> bool {
232 match self {
233 BuiltinFunction::Math(f) => f.is_variadic(),
234 BuiltinFunction::String(f) => f.is_variadic(),
235 BuiltinFunction::Regex(f) => f.is_variadic(),
236 BuiltinFunction::Aggregate(f) => f.is_variadic(),
237 BuiltinFunction::Conversion(f) => f.is_variadic(),
238 BuiltinFunction::DateTime(f) => f.is_variadic(),
239 BuiltinFunction::Geography(f) => f.is_variadic(),
240 BuiltinFunction::Utility(f) => f.is_variadic(),
241 BuiltinFunction::Graph(f) => f.is_variadic(),
242 BuiltinFunction::Container(f) => f.is_variadic(),
243 BuiltinFunction::Path(f) => f.is_variadic(),
244 BuiltinFunction::Fulltext(f) => f.is_variadic(),
245 BuiltinFunction::Vector(f) => f.is_variadic(),
246 BuiltinFunction::Window(f) => f.is_variadic(),
247 BuiltinFunction::Sequence(f) => f.is_variadic(),
248 }
249 }
250
251 pub fn is_pure(&self) -> bool {
270 match self {
271 BuiltinFunction::String(_)
274 | BuiltinFunction::Regex(_)
275 | BuiltinFunction::Conversion(_)
276 | BuiltinFunction::Geography(_)
277 | BuiltinFunction::Container(_)
278 | BuiltinFunction::Path(_)
279 | BuiltinFunction::Vector(_) => true,
280
281 BuiltinFunction::Math(f) => Self::pure_math(f),
284 BuiltinFunction::DateTime(f) => Self::pure_datetime(f),
285 BuiltinFunction::Utility(f) => Self::pure_utility(f),
286
287 BuiltinFunction::Aggregate(_)
289 | BuiltinFunction::Graph(_)
290 | BuiltinFunction::Fulltext(_)
291 | BuiltinFunction::Window(_)
292 | BuiltinFunction::Sequence(_) => false,
293 }
294 }
295
296 fn pure_math(f: &MathFunction) -> bool {
300 matches!(
301 f,
302 MathFunction::Abs
303 | MathFunction::Sqrt
304 | MathFunction::Pow
305 | MathFunction::Log
306 | MathFunction::Log10
307 | MathFunction::Sin
308 | MathFunction::Cos
309 | MathFunction::Tan
310 | MathFunction::Round
311 | MathFunction::Ceil
312 | MathFunction::Floor
313 | MathFunction::Asin
314 | MathFunction::Acos
315 | MathFunction::Atan
316 | MathFunction::Cbrt
317 | MathFunction::Hypot
318 | MathFunction::Sign
319 | MathFunction::E
320 | MathFunction::Pi
321 | MathFunction::Exp2
322 | MathFunction::Log2
323 | MathFunction::Radians
324 | MathFunction::BitAnd
325 | MathFunction::BitOr
326 | MathFunction::BitXor
327 | MathFunction::Atan2
328 | MathFunction::Sinh
329 | MathFunction::Cosh
330 | MathFunction::Tanh
331 | MathFunction::Degrees
332 | MathFunction::Gcd
333 | MathFunction::Lcm
334 | MathFunction::Factorial
335 | MathFunction::Gamma
336 | MathFunction::Lgamma
337 | MathFunction::Negate
338 | MathFunction::Even
339 | MathFunction::BitShiftLeft
340 | MathFunction::BitShiftRight
341 )
342 }
343
344 fn pure_datetime(f: &DateTimeFunction) -> bool {
349 matches!(
350 f,
351 DateTimeFunction::Date
352 | DateTimeFunction::Time
353 | DateTimeFunction::DateTime
354 | DateTimeFunction::Year
355 | DateTimeFunction::Month
356 | DateTimeFunction::Day
357 | DateTimeFunction::Hour
358 | DateTimeFunction::Minute
359 | DateTimeFunction::Second
360 | DateTimeFunction::DateAdd
361 | DateTimeFunction::DateSub
362 | DateTimeFunction::DateDiff
363 | DateTimeFunction::DateTrunc
364 | DateTimeFunction::ToChar
365 | DateTimeFunction::ToDate
366 | DateTimeFunction::Age
367 | DateTimeFunction::LastDay
368 | DateTimeFunction::GenerateSeries
369 | DateTimeFunction::ToYears
370 | DateTimeFunction::ToMonths
371 | DateTimeFunction::ToDays
372 | DateTimeFunction::ToHours
373 | DateTimeFunction::ToMinutes
374 | DateTimeFunction::ToSeconds
375 | DateTimeFunction::ToMilliseconds
376 | DateTimeFunction::ToMicroseconds
377 | DateTimeFunction::Century
378 | DateTimeFunction::EpochMs
379 | DateTimeFunction::ToTimestamp
380 | DateTimeFunction::ToEpochMs
381 | DateTimeFunction::DatePart
382 | DateTimeFunction::DayName
383 | DateTimeFunction::MonthName
384 )
385 }
386
387 fn pure_utility(f: &UtilityFunction) -> bool {
394 matches!(
395 f,
396 UtilityFunction::Coalesce
397 | UtilityFunction::Hash
398 | UtilityFunction::JsonExtract
399 | UtilityFunction::JsonBuildObject
400 | UtilityFunction::JsonBuildArray
401 | UtilityFunction::JsonObjectKeys
402 | UtilityFunction::NullIf
403 | UtilityFunction::Greatest
404 | UtilityFunction::Least
405 | UtilityFunction::JsonEach
406 | UtilityFunction::JsonTypeOf
407 | UtilityFunction::JsonStripNulls
408 | UtilityFunction::IfNull
409 | UtilityFunction::TypeOf
410 | UtilityFunction::Version
411 | UtilityFunction::Corr
412 | UtilityFunction::CovarPop
413 | UtilityFunction::CovarSamp
414 | UtilityFunction::OctetLength
415 | UtilityFunction::Encode
416 | UtilityFunction::Decode
417 | UtilityFunction::UnionValue
418 | UtilityFunction::UnionTag
419 | UtilityFunction::UnionExtract
420 )
421 }
422
423 pub fn description(&self) -> &str {
425 match self {
426 BuiltinFunction::Math(f) => f.description(),
427 BuiltinFunction::String(f) => f.description(),
428 BuiltinFunction::Regex(f) => f.description(),
429 BuiltinFunction::Aggregate(f) => f.description(),
430 BuiltinFunction::Conversion(f) => f.description(),
431 BuiltinFunction::DateTime(f) => f.description(),
432 BuiltinFunction::Geography(f) => f.description(),
433 BuiltinFunction::Utility(f) => f.description(),
434 BuiltinFunction::Graph(f) => f.description(),
435 BuiltinFunction::Container(f) => f.description(),
436 BuiltinFunction::Path(f) => f.description(),
437 BuiltinFunction::Fulltext(f) => f.description(),
438 BuiltinFunction::Vector(f) => f.description(),
439 BuiltinFunction::Window(f) => f.description(),
440 BuiltinFunction::Sequence(f) => f.description(),
441 }
442 }
443
444 pub fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
446 if !self.is_variadic() && args.len() != self.arity() {
447 return Err(ExpressionError::invalid_arity(
448 self.name(),
449 self.arity(),
450 args.len(),
451 ));
452 }
453 match self {
454 BuiltinFunction::Math(f) => f.execute(args),
455 BuiltinFunction::String(f) => f.execute(args),
456 BuiltinFunction::Regex(f) => f.execute(args),
457 BuiltinFunction::Aggregate(_) => Err(ExpressionError::new(
458 ExpressionErrorType::InvalidOperation,
459 "Aggregation functions need to be executed within the aggregation context"
460 .to_string(),
461 )),
462 BuiltinFunction::Conversion(f) => f.execute(args),
463 BuiltinFunction::DateTime(f) => f.execute(args),
464 BuiltinFunction::Geography(f) => f.execute(args),
465 BuiltinFunction::Utility(f) => f.execute(args),
466 BuiltinFunction::Graph(f) => f.execute(args),
467 BuiltinFunction::Container(f) => f.execute(args),
468 BuiltinFunction::Path(f) => f.execute(args),
469 BuiltinFunction::Fulltext(_f) => {
470 Err(ExpressionError::new(
473 ExpressionErrorType::InvalidOperation,
474 "The full-text search function needs to be executed within the context of full-text search".to_string(),
475 ))
476 }
477 BuiltinFunction::Vector(f) => f.execute(args),
478 BuiltinFunction::Window(f) => f.execute(args),
479 BuiltinFunction::Sequence(f) => f.execute(args),
480 }
481 }
482
483 pub fn execute_with_storage(
486 &self,
487 args: &[Value],
488 storage: &GraphStorageRef,
489 ) -> Result<Value, ExpressionError> {
490 match self {
491 BuiltinFunction::Graph(f) => f.execute_with_storage(args, storage),
492 _ => self.execute(args),
493 }
494 }
495
496 pub fn execute_with_cache(
500 &self,
501 args: &[Value],
502 _cache: &mut (),
503 ) -> Result<Value, ExpressionError> {
504 self.execute(args)
505 }
506}
507
508impl ExpressionFunction for BuiltinFunction {
509 fn name(&self) -> &str {
510 self.name()
511 }
512
513 fn arity(&self) -> usize {
514 self.arity()
515 }
516
517 fn is_variadic(&self) -> bool {
518 self.is_variadic()
519 }
520
521 fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
522 self.execute(args)
523 }
524
525 fn description(&self) -> &str {
526 self.description()
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use super::*;
533
534 #[test]
537 fn test_is_pure_impure_functions_never_fold() {
538 let registry = global_registry();
539 for name in [
540 "rand",
541 "rand32",
542 "rand64",
543 "now",
544 "timestamp",
545 "current_date",
546 "current_timestamp",
547 "gen_random_uuid",
548 "current_user",
549 "current_database",
550 ] {
551 let f = registry
552 .get_builtin(name)
553 .unwrap_or_else(|| panic!("function `{name}` must be registered"));
554 assert!(!f.is_pure(), "`{name}` is non-pure and must never fold");
555 }
556 }
557
558 #[test]
560 fn test_is_pure_representative_pure_functions() {
561 let registry = global_registry();
562 for name in [
563 "abs",
564 "sqrt",
565 "pow",
566 "upper",
567 "length",
568 "regex_match",
569 "to_string",
570 "to_int",
571 "year",
572 "to_char",
573 "st_distance",
574 "coalesce",
575 "json_extract",
576 "version",
577 "keys",
578 "nodes",
579 "cosine_similarity",
580 ] {
581 let f = registry
582 .get_builtin(name)
583 .unwrap_or_else(|| panic!("function `{name}` must be registered"));
584 assert!(f.is_pure(), "`{name}` is a deterministic value operator");
585 }
586 }
587
588 #[test]
595 fn test_is_pure_classification_is_explicit_for_all_registered() {
596 let registry = global_registry();
597 for name in registry.function_names() {
598 let f = registry
599 .get_builtin(name)
600 .unwrap_or_else(|| panic!("function `{name}` must be registered"));
601 let impure_by_category = matches!(
602 f,
603 BuiltinFunction::Aggregate(_)
604 | BuiltinFunction::Graph(_)
605 | BuiltinFunction::Fulltext(_)
606 | BuiltinFunction::Window(_)
607 | BuiltinFunction::Sequence(_)
608 );
609 let impure_by_variant = matches!(
610 f,
611 BuiltinFunction::Math(f)
612 if matches!(f, MathFunction::Rand | MathFunction::Rand32 | MathFunction::Rand64 | MathFunction::SetSeed)
613 ) || matches!(
614 f,
615 BuiltinFunction::DateTime(f)
616 if matches!(
617 f,
618 DateTimeFunction::Now
619 | DateTimeFunction::TimeStamp
620 | DateTimeFunction::CurrentDate
621 | DateTimeFunction::CurrentTimestamp
622 )
623 ) || matches!(
624 f,
625 BuiltinFunction::Utility(f)
626 if matches!(
627 f,
628 UtilityFunction::GenRandomUuid
629 | UtilityFunction::CurrentUser
630 | UtilityFunction::CurrentDatabase
631 )
632 );
633 assert_eq!(
634 f.is_pure(),
635 !(impure_by_category || impure_by_variant),
636 "function `{name}` is not explicitly classified as pure or non-pure"
637 );
638 }
639 }
640
641 #[test]
644 fn test_central_arity_fixed_functions() {
645 let registry = global_registry();
646 assert!(registry.execute("length", &[Value::string("hi")]).is_ok());
648 assert!(registry
649 .execute(
650 "substring",
651 &[Value::string("hi"), Value::Int(0), Value::Int(1)]
652 )
653 .is_ok());
654 let cases: &[(&str, Vec<Value>, Vec<Value>)] = &[
656 ("length", vec![], vec![Value::Int(1), Value::Int(2)]),
657 ("size", vec![], vec![Value::Int(1), Value::Int(2)]),
658 (
659 "upper",
660 vec![],
661 vec![Value::string("a"), Value::string("b")],
662 ),
663 (
664 "substring",
665 vec![Value::string("a")],
666 vec![
667 Value::string("a"),
668 Value::Int(0),
669 Value::Int(1),
670 Value::Int(2),
671 ],
672 ),
673 ("abs", vec![], vec![Value::Int(1), Value::Int(2)]),
674 ("pow", vec![Value::Int(1)], vec![Value::Int(1); 3]),
675 ("head", vec![], vec![Value::Int(1), Value::Int(2)]),
676 ("id", vec![], vec![Value::Int(1), Value::Int(2)]),
677 (
678 "row_number",
679 vec![Value::Int(1)],
680 vec![Value::Int(1), Value::Int(2)],
681 ),
682 (
683 "regex_match",
684 vec![Value::string("a")],
685 vec![Value::string("a"); 3],
686 ),
687 (
688 "cosine_similarity",
689 vec![Value::string("a")],
690 vec![Value::string("a"); 3],
691 ),
692 ("to_string", vec![], vec![Value::Int(1), Value::Int(2)]),
693 ("year", vec![], vec![Value::Int(1), Value::Int(2)]),
694 (
695 "octet_length",
696 vec![],
697 vec![Value::string("a"), Value::string("b")],
698 ),
699 (
700 "curr_val",
701 vec![],
702 vec![Value::string("a"), Value::string("b")],
703 ),
704 ("lead", vec![Value::Int(1)], vec![Value::Int(1); 3]),
705 ];
706 for (name, too_few, too_many) in cases {
707 for args in [too_few, too_many] {
708 let err = registry.execute(name, args).unwrap_err();
709 assert_eq!(
710 err.error_type,
711 ExpressionErrorType::InvalidArgumentCount,
712 "`{name}` with wrong arity must report InvalidArgumentCount"
713 );
714 assert!(
715 err.message.contains(*name),
716 "`{name}` arity error must name the function"
717 );
718 }
719 }
720 }
721
722 #[test]
724 fn test_central_arity_error_format() {
725 let registry = global_registry();
726 let err = registry.execute("length", &[]).unwrap_err();
727 assert_eq!(err.error_type, ExpressionErrorType::InvalidArgumentCount);
728 assert_eq!(
729 err.message,
730 "function 'length' expects 1 argument(s), got 0"
731 );
732 let err = registry
733 .execute("substring", &[Value::string("a"), Value::Int(0)])
734 .unwrap_err();
735 assert_eq!(err.error_type, ExpressionErrorType::InvalidArgumentCount);
736 assert!(err.message.contains("'substring'"));
737 assert!(err.message.contains("3"));
738 assert!(err.message.contains("2"));
739 }
740
741 #[test]
743 fn test_central_arity_variadic_boundaries() {
744 let registry = global_registry();
745 assert!(registry.execute("concat", &[]).is_ok());
747 assert!(registry.execute("coalesce", &[]).is_ok());
748 assert!(registry.execute("date", &[]).is_ok());
749 assert!(registry.execute("rand32", &[]).is_ok());
750 assert!(registry
751 .execute("rand32", &[Value::Int(1), Value::Int(5)])
752 .is_ok());
753 assert!(registry.execute("range", &[Value::Int(1)]).is_err());
755 assert!(registry
756 .execute("concat_ws", &[Value::string(",")])
757 .is_err());
758 assert!(registry.execute("format", &[Value::string("{0}")]).is_err());
759 assert!(registry.execute("list_concat", &[Value::Int(1)]).is_err());
760 assert!(registry
761 .execute("struct_pack", &[Value::string("k")])
762 .is_err());
763 assert!(registry
764 .execute("rand32", &[Value::Int(1), Value::Int(2), Value::Int(3)])
765 .is_err());
766 assert!(registry
767 .execute(
768 "range",
769 &[Value::Int(1), Value::Int(5), Value::Int(1), Value::Int(2)]
770 )
771 .is_err());
772 }
773
774 #[test]
777 fn test_central_arity_before_context_interception() {
778 let count = BuiltinFunction::Aggregate(AggregateFunction::Count);
779 let err = count.execute(&[]).unwrap_err();
780 assert_eq!(err.error_type, ExpressionErrorType::InvalidArgumentCount);
781 let err = count.execute(&[Value::Int(1)]).unwrap_err();
782 assert_eq!(err.error_type, ExpressionErrorType::InvalidOperation);
783
784 let score = BuiltinFunction::Fulltext(FulltextFunction::Score);
785 let err = score.execute(&[Value::Int(1)]).unwrap_err();
786 assert_eq!(err.error_type, ExpressionErrorType::InvalidArgumentCount);
787 let err = score.execute(&[]).unwrap_err();
788 assert_eq!(err.error_type, ExpressionErrorType::InvalidOperation);
789 }
790}