Skip to main content

alopex_sql/executor/evaluator/
registry.rs

1//! Runtime dispatch table for scalar functions.
2
3use 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    // Direct execution is handled by the executor, which owns the KV store.
75    // Keeping a registry entry lets the planner enforce the function contract.
76    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}