sql_cli/sql/functions/
mod.rs1use anyhow::{anyhow, Result};
2use std::collections::HashMap;
3use std::fmt;
4
5use crate::data::datatable::DataValue;
6
7pub mod astronomy;
8pub mod chemistry;
9pub mod constants;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum FunctionCategory {
14 Constant, Mathematical, Astronomical, Chemical, Date, String, Aggregate, }
22
23impl fmt::Display for FunctionCategory {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 FunctionCategory::Constant => write!(f, "Constant"),
27 FunctionCategory::Mathematical => write!(f, "Mathematical"),
28 FunctionCategory::Astronomical => write!(f, "Astronomical"),
29 FunctionCategory::Chemical => write!(f, "Chemical"),
30 FunctionCategory::Date => write!(f, "Date"),
31 FunctionCategory::String => write!(f, "String"),
32 FunctionCategory::Aggregate => write!(f, "Aggregate"),
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
39pub enum ArgCount {
40 Fixed(usize),
42 Range(usize, usize),
44 Variadic,
46}
47
48impl ArgCount {
49 pub fn is_valid(&self, count: usize) -> bool {
50 match self {
51 ArgCount::Fixed(n) => count == *n,
52 ArgCount::Range(min, max) => count >= *min && count <= *max,
53 ArgCount::Variadic => true,
54 }
55 }
56
57 pub fn description(&self) -> String {
58 match self {
59 ArgCount::Fixed(0) => "no arguments".to_string(),
60 ArgCount::Fixed(1) => "1 argument".to_string(),
61 ArgCount::Fixed(n) => format!("{} arguments", n),
62 ArgCount::Range(min, max) => format!("{} to {} arguments", min, max),
63 ArgCount::Variadic => "any number of arguments".to_string(),
64 }
65 }
66}
67
68#[derive(Debug, Clone)]
70pub struct FunctionSignature {
71 pub name: &'static str,
72 pub category: FunctionCategory,
73 pub arg_count: ArgCount,
74 pub description: &'static str,
75 pub returns: &'static str,
76 pub examples: Vec<&'static str>,
77}
78
79pub trait SqlFunction: Send + Sync {
81 fn signature(&self) -> FunctionSignature;
83
84 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue>;
86
87 fn validate_args(&self, args: &[DataValue]) -> Result<()> {
89 let sig = self.signature();
90 if !sig.arg_count.is_valid(args.len()) {
91 return Err(anyhow!(
92 "{}() expects {}, got {}",
93 sig.name,
94 sig.arg_count.description(),
95 args.len()
96 ));
97 }
98 Ok(())
99 }
100}
101
102pub struct FunctionRegistry {
104 functions: HashMap<String, Box<dyn SqlFunction>>,
105 by_category: HashMap<FunctionCategory, Vec<String>>,
106}
107
108impl FunctionRegistry {
109 pub fn new() -> Self {
111 let mut registry = Self {
112 functions: HashMap::new(),
113 by_category: HashMap::new(),
114 };
115
116 registry.register_constants();
118 registry.register_astronomical_functions();
119 registry.register_chemical_functions();
120
121 registry
122 }
123
124 pub fn register(&mut self, func: Box<dyn SqlFunction>) {
126 let sig = func.signature();
127 let name = sig.name.to_uppercase();
128 let category = sig.category;
129
130 self.functions.insert(name.clone(), func);
132
133 self.by_category
135 .entry(category)
136 .or_insert_with(Vec::new)
137 .push(name);
138 }
139
140 pub fn get(&self, name: &str) -> Option<&dyn SqlFunction> {
142 self.functions.get(&name.to_uppercase()).map(|b| b.as_ref())
143 }
144
145 pub fn contains(&self, name: &str) -> bool {
147 self.functions.contains_key(&name.to_uppercase())
148 }
149
150 pub fn autocomplete(&self, prefix: &str) -> Vec<FunctionSignature> {
152 let prefix_upper = prefix.to_uppercase();
153 self.functions
154 .iter()
155 .filter(|(name, _)| name.starts_with(&prefix_upper))
156 .map(|(_, func)| func.signature())
157 .collect()
158 }
159
160 pub fn get_by_category(&self, category: FunctionCategory) -> Vec<FunctionSignature> {
162 self.by_category
163 .get(&category)
164 .map(|names| {
165 names
166 .iter()
167 .filter_map(|name| self.functions.get(name))
168 .map(|func| func.signature())
169 .collect()
170 })
171 .unwrap_or_default()
172 }
173
174 pub fn all_functions(&self) -> Vec<FunctionSignature> {
176 self.functions
177 .values()
178 .map(|func| func.signature())
179 .collect()
180 }
181
182 fn register_constants(&mut self) {
184 use constants::*;
185
186 self.register(Box::new(PiFunction));
187 self.register(Box::new(EFunction));
188 self.register(Box::new(MeFunction)); }
190
191 fn register_astronomical_functions(&mut self) {
193 use astronomy::*;
194
195 self.register(Box::new(MassEarthFunction));
196 self.register(Box::new(MassSunFunction));
197 self.register(Box::new(MassMoonFunction));
198 self.register(Box::new(AuFunction)); self.register(Box::new(LightYearFunction));
200 self.register(Box::new(ParsecFunction));
201
202 self.register(Box::new(MassMercuryFunction));
204 self.register(Box::new(MassVenusFunction));
205 self.register(Box::new(MassMarsFunction));
206 self.register(Box::new(MassJupiterFunction));
207 self.register(Box::new(MassSaturnFunction));
208 self.register(Box::new(MassUranusFunction));
209 self.register(Box::new(MassNeptuneFunction));
210
211 self.register(Box::new(RadiusSunFunction));
213 self.register(Box::new(RadiusEarthFunction));
214 self.register(Box::new(RadiusMoonFunction));
215 self.register(Box::new(RadiusMercuryFunction));
216 self.register(Box::new(RadiusVenusFunction));
217 self.register(Box::new(RadiusMarsFunction));
218 self.register(Box::new(RadiusJupiterFunction));
219 self.register(Box::new(RadiusSaturnFunction));
220 self.register(Box::new(RadiusUranusFunction));
221 self.register(Box::new(RadiusNeptuneFunction));
222 }
223
224 fn register_chemical_functions(&mut self) {
226 use chemistry::*;
227
228 self.register(Box::new(AvogadroFunction));
229 self.register(Box::new(AtomicMassFunction));
230 self.register(Box::new(AtomicNumberFunction));
231 }
232}
233
234impl Default for FunctionRegistry {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_registry_creation() {
246 let registry = FunctionRegistry::new();
247
248 assert!(registry.contains("PI"));
250 assert!(registry.contains("MASS_EARTH"));
251 assert!(registry.contains("ME"));
252 }
253
254 #[test]
255 fn test_case_insensitive_lookup() {
256 let registry = FunctionRegistry::new();
257
258 assert!(registry.get("pi").is_some());
259 assert!(registry.get("PI").is_some());
260 assert!(registry.get("Pi").is_some());
261 }
262
263 #[test]
264 fn test_autocomplete() {
265 let registry = FunctionRegistry::new();
266
267 let mass_functions = registry.autocomplete("MASS");
268 assert!(!mass_functions.is_empty());
269
270 let names: Vec<&str> = mass_functions.iter().map(|sig| sig.name).collect();
272 assert!(names.contains(&"MASS_EARTH"));
273 assert!(names.contains(&"MASS_SUN"));
274 }
275}