1use crate::Value;
6
7pub trait Dialect: Clone + Copy {
9 fn param(&self, idx: usize) -> String;
11
12 fn bool_lit(&self, val: bool) -> &'static str;
14
15 fn regex_op(&self) -> &'static str;
18
19 fn in_clause(&self, field: &str, values: &[Value], start_idx: usize) -> (String, Vec<Value>);
22
23 fn not_in_clause(
25 &self,
26 field: &str,
27 values: &[Value],
28 start_idx: usize,
29 ) -> (String, Vec<Value>);
30
31 fn supports_ilike(&self) -> bool;
33
34 fn starts_with_clause(&self, field: &str, idx: usize) -> String;
36
37 fn ends_with_clause(&self, field: &str, idx: usize) -> String;
39
40 fn contains_clause(&self, field: &str, idx: usize) -> String;
42}
43
44#[derive(Debug, Clone, Copy, Default)]
46pub struct Postgres;
47
48impl Dialect for Postgres {
49 #[inline]
50 fn param(&self, idx: usize) -> String {
51 format!("${idx}")
52 }
53
54 #[inline]
55 fn bool_lit(&self, val: bool) -> &'static str {
56 if val { "TRUE" } else { "FALSE" }
57 }
58
59 #[inline]
60 fn regex_op(&self) -> &'static str {
61 "~"
62 }
63
64 fn in_clause(&self, field: &str, values: &[Value], start_idx: usize) -> (String, Vec<Value>) {
65 let sql = format!("{field} = ANY(${start_idx})");
67 (sql, vec![Value::Array(values.to_vec())])
68 }
69
70 fn not_in_clause(
71 &self,
72 field: &str,
73 values: &[Value],
74 start_idx: usize,
75 ) -> (String, Vec<Value>) {
76 let sql = format!("{field} != ALL(${start_idx})");
77 (sql, vec![Value::Array(values.to_vec())])
78 }
79
80 #[inline]
81 fn supports_ilike(&self) -> bool {
82 true
83 }
84
85 #[inline]
86 fn starts_with_clause(&self, field: &str, idx: usize) -> String {
87 format!("{field} LIKE ${idx} || '%'")
88 }
89
90 #[inline]
91 fn ends_with_clause(&self, field: &str, idx: usize) -> String {
92 format!("{field} LIKE '%' || ${idx}")
93 }
94
95 #[inline]
96 fn contains_clause(&self, field: &str, idx: usize) -> String {
97 format!("{field} LIKE '%' || ${idx} || '%'")
98 }
99}
100
101#[derive(Debug, Clone, Copy, Default)]
103pub struct Sqlite;
104
105impl Dialect for Sqlite {
106 #[inline]
107 fn param(&self, idx: usize) -> String {
108 format!("?{idx}")
109 }
110
111 #[inline]
112 fn bool_lit(&self, val: bool) -> &'static str {
113 if val { "1" } else { "0" }
114 }
115
116 #[inline]
117 fn regex_op(&self) -> &'static str {
118 "LIKE"
120 }
121
122 fn in_clause(&self, field: &str, values: &[Value], start_idx: usize) -> (String, Vec<Value>) {
123 let placeholders: Vec<String> = (0..values.len())
125 .map(|i| format!("?{}", start_idx + i))
126 .collect();
127 let sql = format!("{} IN ({})", field, placeholders.join(", "));
128 (sql, values.to_vec())
129 }
130
131 fn not_in_clause(
132 &self,
133 field: &str,
134 values: &[Value],
135 start_idx: usize,
136 ) -> (String, Vec<Value>) {
137 let placeholders: Vec<String> = (0..values.len())
138 .map(|i| format!("?{}", start_idx + i))
139 .collect();
140 let sql = format!("{} NOT IN ({})", field, placeholders.join(", "));
141 (sql, values.to_vec())
142 }
143
144 #[inline]
145 fn supports_ilike(&self) -> bool {
146 false
148 }
149
150 #[inline]
151 fn starts_with_clause(&self, field: &str, idx: usize) -> String {
152 format!("{field} LIKE ?{idx} || '%'")
153 }
154
155 #[inline]
156 fn ends_with_clause(&self, field: &str, idx: usize) -> String {
157 format!("{field} LIKE '%' || ?{idx}")
158 }
159
160 #[inline]
161 fn contains_clause(&self, field: &str, idx: usize) -> String {
162 format!("{field} LIKE '%' || ?{idx} || '%'")
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_postgres_params() {
172 let pg = Postgres;
173 assert_eq!(pg.param(1), "$1");
174 assert_eq!(pg.param(10), "$10");
175 }
176
177 #[test]
178 fn test_sqlite_params() {
179 let sqlite = Sqlite;
180 assert_eq!(sqlite.param(1), "?1");
181 assert_eq!(sqlite.param(10), "?10");
182 }
183
184 #[test]
185 fn test_postgres_bool() {
186 let pg = Postgres;
187 assert_eq!(pg.bool_lit(true), "TRUE");
188 assert_eq!(pg.bool_lit(false), "FALSE");
189 }
190
191 #[test]
192 fn test_sqlite_bool() {
193 let sqlite = Sqlite;
194 assert_eq!(sqlite.bool_lit(true), "1");
195 assert_eq!(sqlite.bool_lit(false), "0");
196 }
197
198 #[test]
199 fn test_postgres_in_clause() {
200 let pg = Postgres;
201 let values = vec![Value::String("a".into()), Value::String("b".into())];
202 let (sql, params) = pg.in_clause("status", &values, 1);
203
204 assert_eq!(sql, "status = ANY($1)");
205 assert_eq!(params.len(), 1); }
207
208 #[test]
209 fn test_sqlite_in_clause() {
210 let sqlite = Sqlite;
211 let values = vec![Value::String("a".into()), Value::String("b".into())];
212 let (sql, params) = sqlite.in_clause("status", &values, 1);
213
214 assert_eq!(sql, "status IN (?1, ?2)");
215 assert_eq!(params.len(), 2); }
217}