1mod sealed {
15 pub trait Sealed {}
16 impl Sealed for super::Postgres {}
17 impl Sealed for super::Sqlite {}
18 impl Sealed for super::Mysql {}
19 impl Sealed for super::Mssql {}
20 impl Sealed for super::Cql {}
21 impl Sealed for super::NotSql {}
22}
23
24pub trait SqlDialect: Send + Sync + sealed::Sealed {
29 fn placeholder(&self, i: usize) -> String;
31
32 fn returning_clause(&self, cols: &str) -> String;
36
37 fn quote_ident(&self, ident: &str) -> String;
39
40 fn supports_distinct_on(&self) -> bool {
43 false
44 }
45
46 fn insert_has_returning(&self) -> bool {
49 true
50 }
51
52 fn upsert_clause(&self, conflict_cols: &[&str], update_set: &str) -> String;
55
56 fn begin_sql(&self) -> &'static str {
58 "BEGIN"
59 }
60
61 fn commit_sql(&self) -> &'static str {
63 "COMMIT"
64 }
65
66 fn rollback_sql(&self) -> &'static str {
68 "ROLLBACK"
69 }
70}
71
72pub struct Postgres;
75
76pub struct Sqlite;
79
80pub struct Mysql;
90
91pub struct Mssql;
96
97pub struct Cql;
106
107pub struct NotSql;
113
114impl SqlDialect for Postgres {
115 fn placeholder(&self, i: usize) -> String {
116 format!("${}", i)
117 }
118 fn returning_clause(&self, cols: &str) -> String {
119 format!(" RETURNING {}", cols)
120 }
121 fn quote_ident(&self, i: &str) -> String {
122 format!("\"{}\"", i.replace('"', "\"\""))
123 }
124 fn supports_distinct_on(&self) -> bool {
125 true
126 }
127 fn upsert_clause(&self, c: &[&str], s: &str) -> String {
128 format!(" ON CONFLICT ({}) DO UPDATE SET {}", c.join(", "), s)
129 }
130}
131
132impl SqlDialect for Sqlite {
133 fn placeholder(&self, i: usize) -> String {
134 format!("?{}", i)
135 }
136 fn returning_clause(&self, cols: &str) -> String {
137 format!(" RETURNING {}", cols)
138 }
139 fn quote_ident(&self, i: &str) -> String {
140 format!("\"{}\"", i.replace('"', "\"\""))
141 }
142 fn upsert_clause(&self, c: &[&str], s: &str) -> String {
143 format!(" ON CONFLICT ({}) DO UPDATE SET {}", c.join(", "), s)
144 }
145}
146
147impl SqlDialect for Mysql {
148 fn placeholder(&self, _i: usize) -> String {
149 "?".into()
150 }
151 fn returning_clause(&self, _cols: &str) -> String {
152 String::new()
164 }
165 fn insert_has_returning(&self) -> bool {
166 false
167 }
168 fn quote_ident(&self, i: &str) -> String {
169 format!("`{}`", i.replace('`', "``"))
170 }
171 fn upsert_clause(&self, _c: &[&str], s: &str) -> String {
172 format!(" ON DUPLICATE KEY UPDATE {}", s)
173 }
174}
175
176impl SqlDialect for Mssql {
177 fn placeholder(&self, i: usize) -> String {
178 format!("@P{}", i)
179 }
180 fn returning_clause(&self, cols: &str) -> String {
181 if cols == "*" {
182 return " OUTPUT INSERTED.*".into();
186 }
187 let prefixed: Vec<String> = cols
188 .split(',')
189 .map(|c| format!("INSERTED.{}", c.trim()))
190 .collect();
191 format!(" OUTPUT {}", prefixed.join(", "))
192 }
193 fn quote_ident(&self, i: &str) -> String {
194 format!("[{}]", i.replace(']', "]]"))
195 }
196 fn upsert_clause(&self, _c: &[&str], _s: &str) -> String {
197 String::new()
198 }
199 fn begin_sql(&self) -> &'static str {
200 "BEGIN TRANSACTION"
201 }
202 fn commit_sql(&self) -> &'static str {
203 "COMMIT TRANSACTION"
204 }
205 fn rollback_sql(&self) -> &'static str {
206 "ROLLBACK TRANSACTION"
207 }
208}
209
210impl SqlDialect for Cql {
211 fn placeholder(&self, _i: usize) -> String {
212 "?".into()
213 }
214 fn returning_clause(&self, _cols: &str) -> String {
215 String::new()
219 }
220 fn insert_has_returning(&self) -> bool {
221 false
222 }
223 fn quote_ident(&self, i: &str) -> String {
224 format!("\"{}\"", i.replace('"', "\"\""))
227 }
228 fn upsert_clause(&self, _c: &[&str], _s: &str) -> String {
229 String::new()
233 }
234}
235
236impl SqlDialect for NotSql {
237 fn placeholder(&self, _i: usize) -> String {
238 unimplemented!(
239 "NotSql dialect does not emit SQL; engines that return NotSql from \
240 QueryEngine::dialect() must not route requests through the SQL \
241 operation builders (FindManyOperation, CreateOperation, etc.). \
242 Use a SQL-capable dialect (Postgres/Mysql/Sqlite/Mssql) or build \
243 queries natively (e.g. BSON for MongoDB)."
244 )
245 }
246 fn returning_clause(&self, _cols: &str) -> String {
247 unimplemented!("NotSql::returning_clause — see NotSql::placeholder for details")
248 }
249 fn quote_ident(&self, _ident: &str) -> String {
250 unimplemented!("NotSql::quote_ident — see NotSql::placeholder for details")
251 }
252 fn upsert_clause(&self, _c: &[&str], _s: &str) -> String {
253 unimplemented!("NotSql::upsert_clause — see NotSql::placeholder for details")
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn placeholders_per_dialect() {
263 assert_eq!(Postgres.placeholder(3), "$3");
264 assert_eq!(Sqlite.placeholder(3), "?3");
265 assert_eq!(Mysql.placeholder(3), "?");
266 assert_eq!(Mssql.placeholder(3), "@P3");
267 }
268
269 #[test]
270 fn returning_mssql_is_output_inserted() {
271 assert_eq!(Mssql.returning_clause("*"), " OUTPUT INSERTED.*");
272 assert_eq!(Mssql.returning_clause("id"), " OUTPUT INSERTED.id");
273 assert_eq!(
274 Mssql.returning_clause("id, email"),
275 " OUTPUT INSERTED.id, INSERTED.email"
276 );
277 assert_eq!(
278 Mssql.returning_clause("id,email,name"),
279 " OUTPUT INSERTED.id, INSERTED.email, INSERTED.name"
280 );
281 }
282
283 #[test]
284 fn upsert_mysql_is_on_duplicate_key() {
285 assert_eq!(
286 Mysql.upsert_clause(&[], "x = 1"),
287 " ON DUPLICATE KEY UPDATE x = 1"
288 );
289 }
290
291 #[test]
292 fn upsert_postgres_is_on_conflict() {
293 assert_eq!(
294 Postgres.upsert_clause(&["email"], "name = EXCLUDED.name"),
295 " ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name"
296 );
297 }
298
299 #[test]
300 fn quote_ident_backends_escape_the_embedded_quote() {
301 assert_eq!(
302 Postgres.quote_ident(r#"col"with"quote"#),
303 r#""col""with""quote""#
304 );
305 assert_eq!(
306 Sqlite.quote_ident(r#"col"with"quote"#),
307 r#""col""with""quote""#
308 );
309 assert_eq!(Mysql.quote_ident("co`l"), "`co``l`");
310 assert_eq!(Mssql.quote_ident("col]ident"), "[col]]ident]");
311 }
312
313 #[test]
314 #[should_panic(expected = "NotSql dialect does not emit SQL")]
315 fn not_sql_placeholder_panics() {
316 let _ = NotSql.placeholder(1);
317 }
318
319 #[test]
320 #[should_panic]
321 fn not_sql_quote_ident_panics() {
322 let _ = NotSql.quote_ident("col");
323 }
324
325 #[test]
326 #[should_panic]
327 fn not_sql_returning_clause_panics() {
328 let _ = NotSql.returning_clause("*");
329 }
330
331 #[test]
332 #[should_panic]
333 fn not_sql_upsert_clause_panics() {
334 let _ = NotSql.upsert_clause(&[], "x = 1");
335 }
336
337 #[test]
338 fn mssql_transaction_keywords_are_distinct() {
339 assert_eq!(Mssql.begin_sql(), "BEGIN TRANSACTION");
340 assert_eq!(Mssql.commit_sql(), "COMMIT TRANSACTION");
341 assert_eq!(Mssql.rollback_sql(), "ROLLBACK TRANSACTION");
342 }
343
344 #[test]
345 fn distinct_on_support() {
346 assert!(Postgres.supports_distinct_on());
347 assert!(!Sqlite.supports_distinct_on());
348 assert!(!Mysql.supports_distinct_on());
349 assert!(!Mssql.supports_distinct_on());
350 assert!(!NotSql.supports_distinct_on());
351 }
352
353 #[test]
354 fn sealed_pattern_prevents_external_impl() {
355 use crate::dialect::{Postgres, SqlDialect};
362 let _p: &dyn SqlDialect = &Postgres;
363 }
364}