1use crate::dialects::DialectType;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase", deny_unknown_fields)]
8pub struct FunctionCatalogSpec {
9 #[serde(default)]
10 pub name_case: FunctionNameCase,
11 pub functions: Vec<FunctionCatalogEntry>,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase", deny_unknown_fields)]
16pub struct FunctionCatalogEntry {
17 pub name: String,
18 pub signatures: Vec<FunctionSignature>,
19 #[serde(default)]
20 pub name_case: Option<FunctionNameCase>,
21}
22
23impl FunctionCatalogSpec {
24 pub fn build(&self, dialect: DialectType) -> Result<HashMapFunctionCatalog, String> {
27 let mut catalog = HashMapFunctionCatalog::default();
28 catalog.set_dialect_name_case(dialect, self.name_case);
29 let mut cases = HashMap::new();
30 for function in &self.functions {
31 if function.name.trim().is_empty() || function.signatures.is_empty() {
32 return Err("Catalog functions require a nonempty name and signatures".into());
33 }
34 for signature in &function.signatures {
35 if signature
36 .max_arity
37 .is_some_and(|max| max < signature.min_arity)
38 {
39 return Err(format!("Invalid arity range for '{}'", function.name));
40 }
41 }
42 let case = function.name_case.unwrap_or(self.name_case);
43 if cases
44 .insert(function.name.to_lowercase(), case)
45 .is_some_and(|old| old != case)
46 {
47 return Err(format!("Conflicting nameCase for '{}'", function.name));
48 }
49 catalog.set_function_name_case(dialect, &function.name, case);
50 catalog.register(dialect, &function.name, function.signatures.clone());
51 }
52 Ok(catalog)
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
58#[serde(rename_all = "snake_case")]
59pub enum FunctionNameCase {
60 #[default]
62 Insensitive,
63 Sensitive,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase", deny_unknown_fields)]
70pub struct FunctionSignature {
71 pub min_arity: usize,
73 pub max_arity: Option<usize>,
76}
77
78impl FunctionSignature {
79 pub const fn exact(arity: usize) -> Self {
81 Self {
82 min_arity: arity,
83 max_arity: Some(arity),
84 }
85 }
86
87 pub const fn range(min_arity: usize, max_arity: usize) -> Self {
89 Self {
90 min_arity,
91 max_arity: Some(max_arity),
92 }
93 }
94
95 pub const fn variadic(min_arity: usize) -> Self {
97 Self {
98 min_arity,
99 max_arity: None,
100 }
101 }
102
103 pub fn matches_arity(&self, arity: usize) -> bool {
105 if arity < self.min_arity {
106 return false;
107 }
108 match self.max_arity {
109 Some(max) => arity <= max,
110 None => true,
111 }
112 }
113
114 pub fn describe_arity(&self) -> String {
116 match self.max_arity {
117 Some(max) if max == self.min_arity => self.min_arity.to_string(),
118 Some(max) => format!("{}..{}", self.min_arity, max),
119 None => format!("{}+", self.min_arity),
120 }
121 }
122}
123
124pub trait FunctionCatalog: Send + Sync {
128 fn lookup(
133 &self,
134 dialect: DialectType,
135 raw_function_name: &str,
136 normalized_name: &str,
137 ) -> Option<&[FunctionSignature]>;
138}
139
140#[derive(Debug, Clone, Default)]
142pub struct HashMapFunctionCatalog {
143 entries_normalized: HashMap<DialectType, HashMap<String, Vec<FunctionSignature>>>,
144 entries_exact: HashMap<DialectType, HashMap<String, Vec<FunctionSignature>>>,
145 dialect_name_case: HashMap<DialectType, FunctionNameCase>,
146 function_name_case_overrides: HashMap<DialectType, HashMap<String, FunctionNameCase>>,
147}
148
149impl HashMapFunctionCatalog {
150 pub fn set_dialect_name_case(&mut self, dialect: DialectType, name_case: FunctionNameCase) {
152 self.dialect_name_case.insert(dialect, name_case);
153 }
154
155 pub fn set_function_name_case(
159 &mut self,
160 dialect: DialectType,
161 function_name: impl Into<String>,
162 name_case: FunctionNameCase,
163 ) {
164 self.function_name_case_overrides
165 .entry(dialect)
166 .or_default()
167 .insert(function_name.into().to_lowercase(), name_case);
168 }
169
170 pub fn register(
172 &mut self,
173 dialect: DialectType,
174 function_name: impl Into<String>,
175 signatures: Vec<FunctionSignature>,
176 ) {
177 let function_name = function_name.into();
178 let normalized_name = function_name.to_lowercase();
179
180 let normalized_entry = self
181 .entries_normalized
182 .entry(dialect)
183 .or_default()
184 .entry(normalized_name)
185 .or_default();
186 let exact_entry = self
187 .entries_exact
188 .entry(dialect)
189 .or_default()
190 .entry(function_name)
191 .or_default();
192
193 for sig in signatures {
194 if !normalized_entry.contains(&sig) {
195 normalized_entry.push(sig.clone());
196 }
197 if !exact_entry.contains(&sig) {
198 exact_entry.push(sig);
199 }
200 }
201 }
202
203 fn effective_name_case(&self, dialect: DialectType, normalized_name: &str) -> FunctionNameCase {
204 if let Some(overrides) = self.function_name_case_overrides.get(&dialect) {
205 if let Some(name_case) = overrides.get(normalized_name) {
206 return *name_case;
207 }
208 }
209 self.dialect_name_case
210 .get(&dialect)
211 .copied()
212 .unwrap_or_default()
213 }
214}
215
216impl FunctionCatalog for HashMapFunctionCatalog {
217 fn lookup(
218 &self,
219 dialect: DialectType,
220 raw_function_name: &str,
221 normalized_name: &str,
222 ) -> Option<&[FunctionSignature]> {
223 match self.effective_name_case(dialect, normalized_name) {
224 FunctionNameCase::Insensitive => self
225 .entries_normalized
226 .get(&dialect)
227 .and_then(|entries| entries.get(normalized_name))
228 .map(|v| v.as_slice()),
229 FunctionNameCase::Sensitive => self
230 .entries_exact
231 .get(&dialect)
232 .and_then(|entries| entries.get(raw_function_name))
233 .map(|v| v.as_slice()),
234 }
235 }
236}