Skip to main content

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}