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;
59
60 fn supports_sql_emission(&self) -> bool {
66 true
67 }
68
69 fn supports_upsert(&self) -> bool {
75 true
76 }
77
78 fn upsert_do_nothing_clause(&self, _conflict_cols: &[&str]) -> String {
85 String::new()
86 }
87
88 fn begin_sql(&self) -> &'static str {
90 "BEGIN"
91 }
92
93 fn commit_sql(&self) -> &'static str {
95 "COMMIT"
96 }
97
98 fn rollback_sql(&self) -> &'static str {
100 "ROLLBACK"
101 }
102}
103
104pub struct Postgres;
107
108pub struct Sqlite;
111
112pub struct Mysql;
122
123pub struct Mssql;
128
129pub struct Cql;
138
139pub struct NotSql;
145
146impl SqlDialect for Postgres {
147 fn placeholder(&self, i: usize) -> String {
148 format!("${}", i)
149 }
150 fn returning_clause(&self, cols: &str) -> String {
151 format!(" RETURNING {}", cols)
152 }
153 fn quote_ident(&self, i: &str) -> String {
154 format!("\"{}\"", i.replace('"', "\"\""))
155 }
156 fn supports_distinct_on(&self) -> bool {
157 true
158 }
159 fn upsert_clause(&self, c: &[&str], s: &str) -> String {
160 let quoted: Vec<String> = c.iter().map(|col| self.quote_ident(col)).collect();
161 format!(" ON CONFLICT ({}) DO UPDATE SET {}", quoted.join(", "), s)
162 }
163 fn upsert_do_nothing_clause(&self, c: &[&str]) -> String {
164 let quoted: Vec<String> = c.iter().map(|col| self.quote_ident(col)).collect();
165 format!(" ON CONFLICT ({}) DO NOTHING", quoted.join(", "))
166 }
167}
168
169impl SqlDialect for Sqlite {
170 fn placeholder(&self, i: usize) -> String {
171 format!("?{}", i)
172 }
173 fn returning_clause(&self, cols: &str) -> String {
174 format!(" RETURNING {}", cols)
175 }
176 fn quote_ident(&self, i: &str) -> String {
177 format!("\"{}\"", i.replace('"', "\"\""))
178 }
179 fn upsert_clause(&self, c: &[&str], s: &str) -> String {
180 let quoted: Vec<String> = c.iter().map(|col| self.quote_ident(col)).collect();
181 format!(" ON CONFLICT ({}) DO UPDATE SET {}", quoted.join(", "), s)
182 }
183 fn upsert_do_nothing_clause(&self, c: &[&str]) -> String {
184 let quoted: Vec<String> = c.iter().map(|col| self.quote_ident(col)).collect();
185 format!(" ON CONFLICT ({}) DO NOTHING", quoted.join(", "))
186 }
187}
188
189impl SqlDialect for Mysql {
190 fn placeholder(&self, _i: usize) -> String {
191 "?".into()
192 }
193 fn upsert_do_nothing_clause(&self, c: &[&str]) -> String {
194 let col = c.first().copied().unwrap_or("id");
195 format!(
196 " ON DUPLICATE KEY UPDATE {} = {}",
197 self.quote_ident(col),
198 self.quote_ident(col)
199 )
200 }
201 fn returning_clause(&self, _cols: &str) -> String {
202 String::new()
214 }
215 fn insert_has_returning(&self) -> bool {
216 false
217 }
218 fn quote_ident(&self, i: &str) -> String {
219 format!("`{}`", i.replace('`', "``"))
220 }
221 fn upsert_clause(&self, _c: &[&str], s: &str) -> String {
222 format!(" ON DUPLICATE KEY UPDATE {}", s)
223 }
224}
225
226impl SqlDialect for Mssql {
227 fn placeholder(&self, i: usize) -> String {
228 format!("@P{}", i)
229 }
230 fn returning_clause(&self, cols: &str) -> String {
231 if cols == "*" {
232 return " OUTPUT INSERTED.*".into();
236 }
237 let prefixed: Vec<String> = cols
238 .split(',')
239 .map(|c| format!("INSERTED.{}", c.trim()))
240 .collect();
241 format!(" OUTPUT {}", prefixed.join(", "))
242 }
243 fn quote_ident(&self, i: &str) -> String {
244 format!("[{}]", i.replace(']', "]]"))
245 }
246 fn upsert_clause(&self, _c: &[&str], _s: &str) -> String {
247 String::new()
248 }
249 fn begin_sql(&self) -> &'static str {
250 "BEGIN TRANSACTION"
251 }
252 fn commit_sql(&self) -> &'static str {
253 "COMMIT TRANSACTION"
254 }
255 fn rollback_sql(&self) -> &'static str {
256 "ROLLBACK TRANSACTION"
257 }
258}
259
260impl SqlDialect for Cql {
261 fn placeholder(&self, _i: usize) -> String {
262 "?".into()
263 }
264 fn returning_clause(&self, _cols: &str) -> String {
265 String::new()
269 }
270 fn insert_has_returning(&self) -> bool {
271 false
272 }
273 fn quote_ident(&self, i: &str) -> String {
274 format!("\"{}\"", i.replace('"', "\"\""))
277 }
278 fn upsert_clause(&self, _c: &[&str], _s: &str) -> String {
279 String::new()
283 }
284}
285
286impl SqlDialect for NotSql {
287 fn placeholder(&self, _i: usize) -> String {
288 unimplemented!(
289 "NotSql dialect does not emit SQL; engines that return NotSql from \
290 QueryEngine::dialect() must not route requests through the SQL \
291 operation builders (FindManyOperation, CreateOperation, etc.). \
292 Use a SQL-capable dialect (Postgres/Mysql/Sqlite/Mssql) or build \
293 queries natively (e.g. BSON for MongoDB)."
294 )
295 }
296 fn returning_clause(&self, _cols: &str) -> String {
297 unimplemented!("NotSql::returning_clause — see NotSql::placeholder for details")
298 }
299 fn quote_ident(&self, _ident: &str) -> String {
300 unimplemented!("NotSql::quote_ident — see NotSql::placeholder for details")
301 }
302 fn upsert_clause(&self, _c: &[&str], _s: &str) -> String {
303 unimplemented!("NotSql::upsert_clause — see NotSql::placeholder for details")
304 }
305 fn supports_sql_emission(&self) -> bool {
306 false
307 }
308 fn supports_upsert(&self) -> bool {
309 false
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn placeholders_per_dialect() {
319 assert_eq!(Postgres.placeholder(3), "$3");
320 assert_eq!(Sqlite.placeholder(3), "?3");
321 assert_eq!(Mysql.placeholder(3), "?");
322 assert_eq!(Mssql.placeholder(3), "@P3");
323 }
324
325 #[test]
326 fn returning_mssql_is_output_inserted() {
327 assert_eq!(Mssql.returning_clause("*"), " OUTPUT INSERTED.*");
328 assert_eq!(Mssql.returning_clause("id"), " OUTPUT INSERTED.id");
329 assert_eq!(
330 Mssql.returning_clause("id, email"),
331 " OUTPUT INSERTED.id, INSERTED.email"
332 );
333 assert_eq!(
334 Mssql.returning_clause("id,email,name"),
335 " OUTPUT INSERTED.id, INSERTED.email, INSERTED.name"
336 );
337 }
338
339 #[test]
340 fn upsert_mysql_is_on_duplicate_key() {
341 assert_eq!(
342 Mysql.upsert_clause(&[], "x = 1"),
343 " ON DUPLICATE KEY UPDATE x = 1"
344 );
345 }
346
347 #[test]
348 fn upsert_postgres_is_on_conflict() {
349 assert_eq!(
351 Postgres.upsert_clause(&["email"], "name = EXCLUDED.name"),
352 " ON CONFLICT (\"email\") DO UPDATE SET name = EXCLUDED.name"
353 );
354 }
355
356 #[test]
357 fn quote_ident_backends_escape_the_embedded_quote() {
358 assert_eq!(
359 Postgres.quote_ident(r#"col"with"quote"#),
360 r#""col""with""quote""#
361 );
362 assert_eq!(
363 Sqlite.quote_ident(r#"col"with"quote"#),
364 r#""col""with""quote""#
365 );
366 assert_eq!(Mysql.quote_ident("co`l"), "`co``l`");
367 assert_eq!(Mssql.quote_ident("col]ident"), "[col]]ident]");
368 }
369
370 #[test]
371 #[should_panic(expected = "NotSql dialect does not emit SQL")]
372 fn not_sql_placeholder_panics() {
373 let _ = NotSql.placeholder(1);
374 }
375
376 #[test]
377 #[should_panic]
378 fn not_sql_quote_ident_panics() {
379 let _ = NotSql.quote_ident("col");
380 }
381
382 #[test]
383 #[should_panic]
384 fn not_sql_returning_clause_panics() {
385 let _ = NotSql.returning_clause("*");
386 }
387
388 #[test]
389 #[should_panic]
390 fn not_sql_upsert_clause_panics() {
391 let _ = NotSql.upsert_clause(&[], "x = 1");
392 }
393
394 #[test]
395 fn mssql_transaction_keywords_are_distinct() {
396 assert_eq!(Mssql.begin_sql(), "BEGIN TRANSACTION");
397 assert_eq!(Mssql.commit_sql(), "COMMIT TRANSACTION");
398 assert_eq!(Mssql.rollback_sql(), "ROLLBACK TRANSACTION");
399 }
400
401 #[test]
402 fn distinct_on_support() {
403 assert!(Postgres.supports_distinct_on());
404 assert!(!Sqlite.supports_distinct_on());
405 assert!(!Mysql.supports_distinct_on());
406 assert!(!Mssql.supports_distinct_on());
407 assert!(!NotSql.supports_distinct_on());
408 }
409
410 #[test]
411 fn sealed_pattern_prevents_external_impl() {
412 use crate::dialect::{Postgres, SqlDialect};
419 let _p: &dyn SqlDialect = &Postgres;
420 }
421}