Skip to main content

fletch_orm/
dialect.rs

1//! SQL dialect abstraction for multi-database support.
2//!
3//! Each database has different syntax for bind parameters, identifier quoting,
4//! and RETURNING clauses. The [`Dialect`] trait abstracts these differences so
5//! the query builder can generate correct SQL for any supported backend.
6
7/// Describes how a specific database handles SQL syntax differences.
8pub trait Dialect: Send + Sync {
9    /// Returns the dialect name (e.g. "sqlite", "postgres", "mysql").
10    fn name(&self) -> &'static str;
11
12    /// Quote an identifier (table name, column name).
13    ///
14    /// - Postgres/SQLite: `"identifier"`
15    /// - MySQL: `` `identifier` ``
16    fn quote_identifier(&self, ident: &str) -> String;
17
18    /// Return the bind parameter placeholder for the given 1-based index.
19    ///
20    /// - Postgres: `$1`, `$2`, ...
21    /// - SQLite/MySQL: `?`
22    fn bind_parameter(&self, index: usize) -> String;
23
24    /// Whether this dialect supports the `RETURNING` clause on INSERT/UPDATE/DELETE.
25    fn supports_returning(&self) -> bool;
26}
27
28/// SQLite dialect.
29#[derive(Debug, Clone, Copy, Default)]
30pub struct SqliteDialect;
31
32impl Dialect for SqliteDialect {
33    fn name(&self) -> &'static str {
34        "sqlite"
35    }
36
37    fn quote_identifier(&self, ident: &str) -> String {
38        format!("\"{}\"", ident.replace('"', "\"\""))
39    }
40
41    fn bind_parameter(&self, _index: usize) -> String {
42        "?".to_owned()
43    }
44
45    fn supports_returning(&self) -> bool {
46        true
47    }
48}
49
50/// PostgreSQL dialect.
51#[derive(Debug, Clone, Copy, Default)]
52pub struct PostgresDialect;
53
54impl Dialect for PostgresDialect {
55    fn name(&self) -> &'static str {
56        "postgres"
57    }
58
59    fn quote_identifier(&self, ident: &str) -> String {
60        format!("\"{}\"", ident.replace('"', "\"\""))
61    }
62
63    fn bind_parameter(&self, index: usize) -> String {
64        format!("${index}")
65    }
66
67    fn supports_returning(&self) -> bool {
68        true
69    }
70}
71
72/// MySQL dialect.
73#[derive(Debug, Clone, Copy, Default)]
74pub struct MySqlDialect;
75
76impl Dialect for MySqlDialect {
77    fn name(&self) -> &'static str {
78        "mysql"
79    }
80
81    fn quote_identifier(&self, ident: &str) -> String {
82        format!("`{}`", ident.replace('`', "``"))
83    }
84
85    fn bind_parameter(&self, _index: usize) -> String {
86        "?".to_owned()
87    }
88
89    fn supports_returning(&self) -> bool {
90        false
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn sqlite_quote_identifier() {
100        let d = SqliteDialect;
101        assert_eq!(d.quote_identifier("users"), "\"users\"");
102        assert_eq!(d.quote_identifier("user\"name"), "\"user\"\"name\"");
103    }
104
105    #[test]
106    fn sqlite_bind_parameter() {
107        let d = SqliteDialect;
108        assert_eq!(d.bind_parameter(1), "?");
109        assert_eq!(d.bind_parameter(5), "?");
110    }
111
112    #[test]
113    fn sqlite_supports_returning() {
114        assert!(SqliteDialect.supports_returning());
115    }
116
117    #[test]
118    fn postgres_quote_identifier() {
119        let d = PostgresDialect;
120        assert_eq!(d.quote_identifier("users"), "\"users\"");
121    }
122
123    #[test]
124    fn postgres_bind_parameter() {
125        let d = PostgresDialect;
126        assert_eq!(d.bind_parameter(1), "$1");
127        assert_eq!(d.bind_parameter(3), "$3");
128    }
129
130    #[test]
131    fn postgres_supports_returning() {
132        assert!(PostgresDialect.supports_returning());
133    }
134
135    #[test]
136    fn mysql_quote_identifier() {
137        let d = MySqlDialect;
138        assert_eq!(d.quote_identifier("users"), "`users`");
139        assert_eq!(d.quote_identifier("user`name"), "`user``name`");
140    }
141
142    #[test]
143    fn mysql_bind_parameter() {
144        let d = MySqlDialect;
145        assert_eq!(d.bind_parameter(1), "?");
146    }
147
148    #[test]
149    fn mysql_does_not_support_returning() {
150        assert!(!MySqlDialect.supports_returning());
151    }
152}