chain_builder/dialect/mod.rs
1//! SQL dialect markers and the [`Dialect`] trait.
2//!
3//! Each supported database is represented by a zero-sized marker type
4//! (`MySql`, `Postgres`, `Sqlite`) implementing [`Dialect`]. The trait exposes
5//! the dialect-specific bits the builder needs: the identifier quote character,
6//! placeholder syntax, and whether `RETURNING` is supported.
7
8mod mysql;
9mod postgres;
10mod sqlite;
11
12pub use mysql::MySql;
13pub use postgres::Postgres;
14pub use sqlite::Sqlite;
15
16/// How a dialect expresses an upsert (`INSERT … ON CONFLICT`).
17///
18/// Postgres and SQLite use `ON CONFLICT (...)` clauses, while MySQL uses
19/// `ON DUPLICATE KEY UPDATE` / `INSERT IGNORE`.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum UpsertStyle {
22 /// Postgres / SQLite: `ON CONFLICT (...) DO {NOTHING|UPDATE SET …}`.
23 OnConflict,
24 /// MySQL: `ON DUPLICATE KEY UPDATE …` and `INSERT IGNORE`.
25 OnDuplicateKey,
26}
27
28/// Compile-time description of a SQL dialect.
29pub trait Dialect: Sized + Send + Sync + 'static {
30 /// The identifier quote character (e.g. backtick for MySQL, `"` for ANSI).
31 fn quote_char() -> char;
32
33 /// Write the bind placeholder for the `n`-th parameter (1-based) into `out`.
34 ///
35 /// Dialects with positional placeholders (`?`) ignore `n`.
36 fn write_placeholder(out: &mut String, n: usize);
37
38 /// Whether this dialect supports the `RETURNING` clause.
39 fn supports_returning() -> bool;
40
41 /// How this dialect expresses an upsert. Defaults to
42 /// [`UpsertStyle::OnConflict`] (Postgres / SQLite); MySQL overrides to
43 /// [`UpsertStyle::OnDuplicateKey`].
44 fn upsert_style() -> UpsertStyle {
45 UpsertStyle::OnConflict
46 }
47
48 /// Whether this dialect supports `SELECT DISTINCT ON (cols)`. Defaults to
49 /// `false`; only Postgres overrides to `true`. Compiling a `distinct_on`
50 /// query against a dialect that returns `false` panics.
51 fn supports_distinct_on() -> bool {
52 false
53 }
54
55 /// Whether this dialect has a native case-insensitive `ILIKE` operator.
56 /// Defaults to `false`; only Postgres overrides to `true`. When `false`,
57 /// `where_ilike` is compiled as `LOWER(col) LIKE LOWER(?)`.
58 fn ilike_is_native() -> bool {
59 false
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn postgres_placeholders_are_numbered() {
69 let mut out = String::new();
70 Postgres::write_placeholder(&mut out, 1);
71 Postgres::write_placeholder(&mut out, 2);
72 assert_eq!(out, "$1$2");
73 }
74
75 #[test]
76 fn mysql_placeholders_are_question_marks() {
77 let mut out = String::new();
78 MySql::write_placeholder(&mut out, 1);
79 MySql::write_placeholder(&mut out, 2);
80 assert_eq!(out, "??");
81 }
82
83 #[test]
84 fn sqlite_placeholders_are_question_marks() {
85 let mut out = String::new();
86 Sqlite::write_placeholder(&mut out, 1);
87 Sqlite::write_placeholder(&mut out, 2);
88 assert_eq!(out, "??");
89 }
90
91 #[test]
92 fn quote_chars() {
93 assert_eq!(MySql::quote_char(), '\u{60}');
94 assert_eq!(Postgres::quote_char(), '"');
95 assert_eq!(Sqlite::quote_char(), '"');
96 }
97
98 #[test]
99 fn returning_support() {
100 assert!(Postgres::supports_returning());
101 assert!(Sqlite::supports_returning());
102 assert!(!MySql::supports_returning());
103 }
104}