alopex_sql/executor/evaluator/
registry.rs1use std::collections::HashMap;
4use std::sync::OnceLock;
5
6use crate::executor::{ExecutorError, Result};
7use crate::planner::typed_expr::TypedExpr;
8use crate::scalar::{self, ScalarSignature};
9use crate::storage::SqlValue;
10
11use super::{
12 EvalContext, conditional, datetime, fts, hash, json, nested, numeric, string, type_fn,
13};
14
15pub type EvalFn = fn(&[SqlValue]) -> Result<SqlValue>;
16pub type LazyEvalFn = fn(&[TypedExpr], &EvalContext<'_>) -> Result<SqlValue>;
17
18pub struct ScalarFunction {
19 pub signature: &'static ScalarSignature,
20 pub eval: EvalFn,
21 pub eval_lazy: Option<LazyEvalFn>,
22}
23
24pub struct ScalarFunctionRegistry {
25 fns: HashMap<&'static str, ScalarFunction>,
26}
27
28impl ScalarFunctionRegistry {
29 fn build() -> Self {
30 let mut fns = HashMap::new();
31 for signature in scalar::signatures() {
32 let (eval, eval_lazy) = evaluator_for(signature.name);
33 fns.insert(
34 signature.name,
35 ScalarFunction {
36 signature,
37 eval,
38 eval_lazy,
39 },
40 );
41 }
42 Self { fns }
43 }
44
45 pub fn get(&self, name: &str) -> Option<&ScalarFunction> {
46 let lower = name.to_ascii_lowercase();
47 self.fns.get(lower.as_str())
48 }
49
50 pub fn contains(&self, name: &str) -> bool {
51 self.get(name).is_some()
52 }
53
54 pub fn names(&self) -> impl Iterator<Item = &'static str> + '_ {
55 self.fns.keys().copied()
56 }
57}
58
59pub fn scalar_registry() -> &'static ScalarFunctionRegistry {
60 static REGISTRY: OnceLock<ScalarFunctionRegistry> = OnceLock::new();
61 REGISTRY.get_or_init(ScalarFunctionRegistry::build)
62}
63
64fn unsupported(values: &[SqlValue]) -> Result<SqlValue> {
65 Err(ExecutorError::Evaluation(
66 crate::executor::EvaluationError::UnsupportedFunction(format!(
67 "unregistered scalar with {} argument(s)",
68 values.len()
69 )),
70 ))
71}
72
73fn system_placeholder(_values: &[SqlValue]) -> Result<SqlValue> {
74 Ok(SqlValue::Null)
77}
78
79fn evaluator_for(name: &str) -> (EvalFn, Option<LazyEvalFn>) {
80 match name {
81 "memory_stats" | "io_stats" | "clear_cache" => (system_placeholder, None),
82 "now" | "current_timestamp" => (datetime::eval_now_values, Some(datetime::eval_now_lazy)),
83 "vector_similarity" => (super::function_call::eval_vector_similarity_values, None),
84 "vector_distance" => (super::function_call::eval_vector_distance_values, None),
85 "vector_dims" => (super::function_call::eval_vector_dims_values, None),
86 "vector_norm" => (super::function_call::eval_vector_norm_values, None),
87 _ => {
88 if let Some(eval) = datetime::eval_for(name) {
89 (eval, None)
90 } else if let Some(eval) = numeric::eval_for(name) {
91 (eval, None)
92 } else if let Some(eval) = string::eval_for(name) {
93 (eval, None)
94 } else if let Some(eval) = conditional::eval_for(name) {
95 (eval, conditional::lazy_eval_for(name))
96 } else if let Some(eval) = type_fn::eval_for(name) {
97 (eval, None)
98 } else if let Some(eval) = hash::eval_for(name) {
99 (eval, None)
100 } else if let Some(eval) = fts::eval_for(name) {
101 (eval, None)
102 } else if let Some(eval) = json::eval_for(name) {
103 (eval, None)
104 } else if let Some(eval) = nested::eval_for(name) {
105 (eval, None)
106 } else {
107 (unsupported, None)
108 }
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use std::collections::HashSet;
117
118 #[test]
119 fn registry_keys_match_signatures() {
120 let signatures: HashSet<_> = scalar::signatures().iter().map(|s| s.name).collect();
121 let registry: HashSet<_> = scalar_registry().names().collect();
122 assert_eq!(signatures, registry);
123 }
124}