polyglot_sql_function_catalogs/
lib.rs1#![forbid(unsafe_code)]
2
3#[cfg(feature = "dialect-clickhouse")]
4mod clickhouse;
5#[cfg(feature = "dialect-duckdb")]
6mod duckdb;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum FunctionNameCase {
11 #[default]
13 Insensitive,
14 Sensitive,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct FunctionSignature {
21 pub min_arity: usize,
23 pub max_arity: Option<usize>,
26}
27
28impl FunctionSignature {
29 pub const fn exact(arity: usize) -> Self {
31 Self {
32 min_arity: arity,
33 max_arity: Some(arity),
34 }
35 }
36
37 pub const fn range(min_arity: usize, max_arity: usize) -> Self {
39 Self {
40 min_arity,
41 max_arity: Some(max_arity),
42 }
43 }
44
45 pub const fn variadic(min_arity: usize) -> Self {
47 Self {
48 min_arity,
49 max_arity: None,
50 }
51 }
52}
53
54pub trait CatalogSink {
58 fn set_dialect_name_case(&mut self, dialect: &'static str, name_case: FunctionNameCase);
60
61 fn set_function_name_case(
63 &mut self,
64 dialect: &'static str,
65 function_name: &str,
66 name_case: FunctionNameCase,
67 );
68
69 fn register(
71 &mut self,
72 dialect: &'static str,
73 function_name: &str,
74 signatures: Vec<FunctionSignature>,
75 );
76}
77
78#[allow(unused_variables)]
80pub fn register_enabled_catalogs<S: CatalogSink>(sink: &mut S) {
81 #[cfg(feature = "dialect-clickhouse")]
82 clickhouse::register(sink);
83 #[cfg(feature = "dialect-duckdb")]
84 duckdb::register(sink);
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use std::collections::HashMap;
91
92 #[derive(Default)]
93 struct TestSink {
94 dialect_name_case: HashMap<&'static str, FunctionNameCase>,
95 entries: HashMap<&'static str, HashMap<String, Vec<FunctionSignature>>>,
96 }
97
98 impl CatalogSink for TestSink {
99 fn set_dialect_name_case(&mut self, dialect: &'static str, name_case: FunctionNameCase) {
100 self.dialect_name_case.insert(dialect, name_case);
101 }
102
103 fn set_function_name_case(
104 &mut self,
105 _dialect: &'static str,
106 _function_name: &str,
107 _name_case: FunctionNameCase,
108 ) {
109 }
110
111 fn register(
112 &mut self,
113 dialect: &'static str,
114 function_name: &str,
115 signatures: Vec<FunctionSignature>,
116 ) {
117 self.entries
118 .entry(dialect)
119 .or_default()
120 .insert(function_name.to_string(), signatures);
121 }
122 }
123
124 #[cfg(feature = "dialect-clickhouse")]
125 #[test]
126 fn clickhouse_catalog_exposes_common_functions() {
127 let mut sink = TestSink::default();
128 register_enabled_catalogs(&mut sink);
129
130 assert_eq!(
131 sink.dialect_name_case.get("clickhouse"),
132 Some(&FunctionNameCase::Insensitive)
133 );
134 let if_signatures = sink
135 .entries
136 .get("clickhouse")
137 .and_then(|entries| entries.get("if"))
138 .expect("expected clickhouse function 'if'");
139 assert!(if_signatures
140 .iter()
141 .any(|sig| sig.min_arity == 3 && sig.max_arity == Some(3)));
142 }
143
144 #[cfg(feature = "dialect-duckdb")]
145 #[test]
146 fn duckdb_catalog_exposes_common_functions() {
147 let mut sink = TestSink::default();
148 register_enabled_catalogs(&mut sink);
149
150 assert_eq!(
151 sink.dialect_name_case.get("duckdb"),
152 Some(&FunctionNameCase::Insensitive)
153 );
154 let abs_signatures = sink
155 .entries
156 .get("duckdb")
157 .and_then(|entries| entries.get("abs"))
158 .expect("expected duckdb function 'abs'");
159 assert!(abs_signatures
160 .iter()
161 .any(|sig| sig.min_arity == 1 && sig.max_arity == Some(1)));
162 }
163}