Skip to main content

drizzle_types/
dialect.rs

1//! The [`Dialect`] enum, shared by every drizzle crate that needs to name a database.
2
3/// A supported SQL database: `SQLite`, `PostgreSQL` or `MySQL`.
4///
5/// Parse one from a name with [`Dialect::parse`] or [`str::parse`]; print it
6/// with `Display` (`"sqlite"`, `"postgresql"`, `"mysql"`).
7///
8/// # Examples
9///
10/// ```
11/// use drizzle_types::Dialect;
12///
13/// let dialect = Dialect::PostgreSQL;
14/// assert!(dialect.uses_numbered_placeholders());
15///
16/// let sqlite = Dialect::SQLite;
17/// assert!(!sqlite.uses_numbered_placeholders());
18/// ```
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
22pub enum Dialect {
23    /// `SQLite`, with `?` positional placeholders.
24    ///
25    /// Compatible with: rusqlite, libsql, turso
26    #[default]
27    SQLite,
28
29    /// `PostgreSQL`, with `$1, $2, ...` numbered placeholders.
30    ///
31    /// Compatible with: tokio-postgres, postgres
32    PostgreSQL,
33
34    /// `MySQL`, with `?` positional placeholders.
35    ///
36    /// Compatible with: mysql
37    MySQL,
38}
39
40impl Dialect {
41    /// Returns `true` if the dialect uses numbered placeholders (`$1, $2, ...`).
42    ///
43    /// Only `PostgreSQL` does; `SQLite` and `MySQL` use `?`.
44    #[inline]
45    #[must_use]
46    pub const fn uses_numbered_placeholders(&self) -> bool {
47        matches!(self, Self::PostgreSQL)
48    }
49
50    /// Parses a dialect name, ignoring case. Returns `None` for an unknown name.
51    ///
52    /// Accepted names:
53    /// - `SQLite`: `"sqlite"`, `"turso"`, `"libsql"`
54    /// - `PostgreSQL`: `"postgresql"`, `"postgres"`, `"pg"`
55    /// - `MySQL`: `"mysql"`
56    ///
57    /// # Examples
58    ///
59    /// ```
60    /// use drizzle_types::Dialect;
61    ///
62    /// assert_eq!(Dialect::parse("sqlite"), Some(Dialect::SQLite));
63    /// assert_eq!(Dialect::parse("postgres"), Some(Dialect::PostgreSQL));
64    /// assert_eq!(Dialect::parse("pg"), Some(Dialect::PostgreSQL));
65    /// assert_eq!(Dialect::parse("unknown"), None);
66    /// ```
67    #[must_use]
68    pub const fn parse(s: &str) -> Option<Self> {
69        // Use eq_ignore_ascii_case for no_std compatibility (no allocation)
70        if s.eq_ignore_ascii_case("sqlite")
71            || s.eq_ignore_ascii_case("turso")
72            || s.eq_ignore_ascii_case("libsql")
73        {
74            Some(Self::SQLite)
75        } else if s.eq_ignore_ascii_case("postgresql")
76            || s.eq_ignore_ascii_case("postgres")
77            || s.eq_ignore_ascii_case("pg")
78        {
79            Some(Self::PostgreSQL)
80        } else if s.eq_ignore_ascii_case("mysql") {
81            Some(Self::MySQL)
82        } else {
83            None
84        }
85    }
86
87    /// Returns the start of the table attribute used in generated code, such
88    /// as `"#[PostgresTable"`.
89    #[must_use]
90    pub const fn table_prefix(&self) -> &'static str {
91        match self {
92            Self::SQLite => "#[SQLiteTable",
93            Self::PostgreSQL => "#[PostgresTable",
94            Self::MySQL => "#[MySQLTable",
95        }
96    }
97
98    /// Returns the start of the index attribute used in generated code, such
99    /// as `"#[PostgresIndex"`.
100    #[must_use]
101    pub const fn index_prefix(&self) -> &'static str {
102        match self {
103            Self::SQLite => "#[SQLiteIndex",
104            Self::PostgreSQL => "#[PostgresIndex",
105            Self::MySQL => "#[MySQLIndex",
106        }
107    }
108
109    /// Returns the schema derive attribute used in generated code, such as
110    /// `"#[derive(PostgresSchema)]"`.
111    #[must_use]
112    pub const fn schema_derive(&self) -> &'static str {
113        match self {
114            Self::SQLite => "#[derive(SQLiteSchema)]",
115            Self::PostgreSQL => "#[derive(PostgresSchema)]",
116            Self::MySQL => "#[derive(MySQLSchema)]",
117        }
118    }
119
120    /// Returns the lowercase name: `"sqlite"`, `"postgresql"` or `"mysql"`.
121    #[must_use]
122    pub const fn as_str(&self) -> &'static str {
123        match self {
124            Self::SQLite => "sqlite",
125            Self::PostgreSQL => "postgresql",
126            Self::MySQL => "mysql",
127        }
128    }
129}
130
131impl core::fmt::Display for Dialect {
132    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
133        f.write_str(self.as_str())
134    }
135}
136
137impl core::str::FromStr for Dialect {
138    type Err = DialectParseError;
139
140    fn from_str(s: &str) -> Result<Self, Self::Err> {
141        Self::parse(s).ok_or(DialectParseError)
142    }
143}
144
145/// Error from parsing an unknown dialect name with [`str::parse`].
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub struct DialectParseError;
148
149impl core::fmt::Display for DialectParseError {
150    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
151        f.write_str("unknown dialect")
152    }
153}
154
155#[cfg(feature = "std")]
156impl std::error::Error for DialectParseError {}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_dialect_parse() {
164        assert_eq!(Dialect::parse("sqlite"), Some(Dialect::SQLite));
165        assert_eq!(Dialect::parse("SQLite"), Some(Dialect::SQLite));
166        assert_eq!(Dialect::parse("turso"), Some(Dialect::SQLite));
167        assert_eq!(Dialect::parse("libsql"), Some(Dialect::SQLite));
168
169        assert_eq!(Dialect::parse("postgresql"), Some(Dialect::PostgreSQL));
170        assert_eq!(Dialect::parse("postgres"), Some(Dialect::PostgreSQL));
171        assert_eq!(Dialect::parse("pg"), Some(Dialect::PostgreSQL));
172        assert_eq!(Dialect::parse("PG"), Some(Dialect::PostgreSQL));
173
174        assert_eq!(Dialect::parse("mysql"), Some(Dialect::MySQL));
175        assert_eq!(Dialect::parse("MySQL"), Some(Dialect::MySQL));
176
177        assert_eq!(Dialect::parse("unknown"), None);
178        assert_eq!(Dialect::parse(""), None);
179    }
180
181    #[test]
182    fn test_dialect_placeholders() {
183        assert!(!Dialect::SQLite.uses_numbered_placeholders());
184        assert!(Dialect::PostgreSQL.uses_numbered_placeholders());
185        assert!(!Dialect::MySQL.uses_numbered_placeholders());
186    }
187
188    #[test]
189    fn test_dialect_display() {
190        assert_eq!(format!("{}", Dialect::SQLite), "sqlite");
191        assert_eq!(format!("{}", Dialect::PostgreSQL), "postgresql");
192        assert_eq!(format!("{}", Dialect::MySQL), "mysql");
193    }
194}