Skip to main content

orbit_orm/
driver.rs

1//! The databases Orbit supports, and what differs between them in SQL.
2//!
3//! Each database is a Cargo feature: `mysql` (the default), `postgres` and `sqlite`. Only the
4//! enabled ones exist in the build, so a crate built with `sqlite` alone has no MySQL types.
5
6use sqlx::FromRow;
7
8use crate::Value;
9
10/// A database that Orbit can talk to. The variants are the enabled Cargo features.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum Driver {
13    /// MySQL and MariaDB.
14    #[cfg(feature = "mysql")]
15    MySql,
16    /// PostgreSQL.
17    #[cfg(feature = "postgres")]
18    Postgres,
19    /// SQLite, in a file or in memory.
20    #[cfg(feature = "sqlite")]
21    Sqlite,
22}
23
24impl Driver {
25    /// The name of the database, for messages.
26    pub fn name(self) -> &'static str {
27        match self {
28            #[cfg(feature = "mysql")]
29            Self::MySql => "MySQL",
30            #[cfg(feature = "postgres")]
31            Self::Postgres => "PostgreSQL",
32            #[cfg(feature = "sqlite")]
33            Self::Sqlite => "SQLite",
34        }
35    }
36
37    /// Quotes a table or column name. The name is checked by the caller: it is never empty and
38    /// has no quote character.
39    pub(crate) fn quote(self, name: &str) -> String {
40        match self {
41            #[cfg(feature = "mysql")]
42            Self::MySql => format!("`{name}`"),
43            #[cfg(any(feature = "postgres", feature = "sqlite"))]
44            _ => format!("\"{name}\""),
45        }
46    }
47
48    /// The placeholder for the `n`th bound value, counted from 1. PostgreSQL numbers them;
49    /// MySQL and SQLite take `?`.
50    #[cfg_attr(not(feature = "postgres"), allow(unused_variables))]
51    pub(crate) fn placeholder(self, n: usize) -> String {
52        match self {
53            #[cfg(feature = "mysql")]
54            Self::MySql => "?".to_owned(),
55            #[cfg(feature = "postgres")]
56            Self::Postgres => format!("${n}"),
57            #[cfg(feature = "sqlite")]
58            Self::Sqlite => "?".to_owned(),
59        }
60    }
61
62    /// The clause that locks the selected rows, or nothing. SQLite has no row locks: its writers
63    /// are serialised by the database, so the clause is left out, as Laravel's SQLite grammar
64    /// does.
65    pub(crate) fn lock_clause(self) -> &'static str {
66        match self {
67            #[cfg(feature = "mysql")]
68            Self::MySql => " FOR UPDATE",
69            #[cfg(feature = "postgres")]
70            Self::Postgres => " FOR UPDATE",
71            #[cfg(feature = "sqlite")]
72            Self::Sqlite => "",
73        }
74    }
75
76    /// The `LIMIT` that an `OFFSET` without a `LIMIT` needs. MySQL and SQLite require one;
77    /// PostgreSQL does not.
78    pub(crate) fn offset_only_limit(self) -> Option<&'static str> {
79        match self {
80            #[cfg(feature = "mysql")]
81            Self::MySql => Some("18446744073709551615"),
82            #[cfg(feature = "postgres")]
83            Self::Postgres => None,
84            #[cfg(feature = "sqlite")]
85            Self::Sqlite => Some("-1"),
86        }
87    }
88
89    /// The part of an `INSERT` that has no columns.
90    pub(crate) fn empty_insert(self) -> &'static str {
91        match self {
92            #[cfg(feature = "mysql")]
93            Self::MySql => "() VALUES ()",
94            #[cfg(any(feature = "postgres", feature = "sqlite"))]
95            _ => "DEFAULT VALUES",
96        }
97    }
98
99    /// The integer type for a `CAST`, wide enough for a count.
100    pub(crate) fn bigint(self) -> &'static str {
101        match self {
102            #[cfg(feature = "mysql")]
103            Self::MySql => "SIGNED",
104            #[cfg(feature = "postgres")]
105            Self::Postgres => "BIGINT",
106            #[cfg(feature = "sqlite")]
107            Self::Sqlite => "INTEGER",
108        }
109    }
110
111    /// Whether `replace` can run. MySQL and SQLite have `REPLACE INTO`; PostgreSQL has no such
112    /// statement, and `INSERT ... ON CONFLICT` would not delete the old row, so it is refused.
113    pub(crate) fn supports_replace(self) -> bool {
114        match self {
115            #[cfg(feature = "mysql")]
116            Self::MySql => true,
117            #[cfg(feature = "postgres")]
118            Self::Postgres => false,
119            #[cfg(feature = "sqlite")]
120            Self::Sqlite => true,
121        }
122    }
123
124    /// Whether a `NULL` value is written into the SQL, not bound. PostgreSQL types a bound
125    /// parameter, so a bound `NULL` would not fit every column; a literal `NULL` fits any.
126    pub(crate) fn inlines_null(self) -> bool {
127        match self {
128            #[cfg(feature = "mysql")]
129            Self::MySql => false,
130            #[cfg(feature = "postgres")]
131            Self::Postgres => true,
132            #[cfg(feature = "sqlite")]
133            Self::Sqlite => false,
134        }
135    }
136
137    /// Whether the insert id comes from `RETURNING` (PostgreSQL), not from the statement result.
138    pub(crate) fn insert_returns_id(self) -> bool {
139        match self {
140            #[cfg(feature = "mysql")]
141            Self::MySql => false,
142            #[cfg(feature = "postgres")]
143            Self::Postgres => true,
144            #[cfg(feature = "sqlite")]
145            Self::Sqlite => false,
146        }
147    }
148
149    /// Prepares a raw statement: `?` placeholders become this driver's placeholders, and the
150    /// values are the ones that stay bound. On PostgreSQL a `NULL` is written into the SQL, as
151    /// for the query builder, so it is not bound. Question marks inside quotes are left alone.
152    pub(crate) fn prepare(self, sql: &str, binds: &[Value]) -> (String, Vec<Value>) {
153        match self {
154            #[cfg(feature = "mysql")]
155            Self::MySql => (sql.to_owned(), binds.to_vec()),
156            #[cfg(feature = "postgres")]
157            Self::Postgres => prepare_postgres(sql, binds),
158            #[cfg(feature = "sqlite")]
159            Self::Sqlite => (sql.to_owned(), binds.to_vec()),
160        }
161    }
162}
163
164/// Numbers the `?` placeholders of `sql` as `$1`, `$2`, ..., skipping any inside a string,
165/// identifier or comment. A placeholder whose value is `NULL` becomes the literal `NULL`.
166#[cfg(feature = "postgres")]
167fn prepare_postgres(sql: &str, binds: &[Value]) -> (String, Vec<Value>) {
168    let mut out = String::with_capacity(sql.len() + 8);
169    let mut kept = Vec::new();
170    let mut seen = 0usize;
171    let mut chars = sql.chars().peekable();
172    let mut quote: Option<char> = None;
173    while let Some(c) = chars.next() {
174        match quote {
175            Some(q) => {
176                out.push(c);
177                if c == q {
178                    quote = None;
179                }
180            }
181            None => match c {
182                '\'' | '"' => {
183                    quote = Some(c);
184                    out.push(c);
185                }
186                '-' if chars.peek() == Some(&'-') => {
187                    out.push(c);
188                    for next in chars.by_ref() {
189                        out.push(next);
190                        if next == '\n' {
191                            break;
192                        }
193                    }
194                }
195                '?' => {
196                    let value = binds.get(seen).cloned().unwrap_or(Value::Null);
197                    seen += 1;
198                    if matches!(value, Value::Null) {
199                        out.push_str("NULL");
200                    } else {
201                        kept.push(value);
202                        out.push_str(&format!("${}", kept.len()));
203                    }
204                }
205                _ => out.push(c),
206            },
207        }
208    }
209    (out, kept)
210}
211
212/// Row decoding for each enabled driver. A model must decode from every enabled database's row
213/// type, so `Model` bounds on these. Each trait is implemented for every type when its driver is
214/// not enabled, so it adds nothing to the bound then.
215#[doc(hidden)]
216pub mod rows {
217    #[cfg(feature = "mysql")]
218    pub trait RowMySql: for<'r> super::FromRow<'r, sqlx::mysql::MySqlRow> {}
219    #[cfg(feature = "mysql")]
220    impl<T: for<'r> super::FromRow<'r, sqlx::mysql::MySqlRow>> RowMySql for T {}
221    #[cfg(not(feature = "mysql"))]
222    pub trait RowMySql {}
223    #[cfg(not(feature = "mysql"))]
224    impl<T> RowMySql for T {}
225
226    #[cfg(feature = "postgres")]
227    pub trait RowPostgres: for<'r> super::FromRow<'r, sqlx::postgres::PgRow> {}
228    #[cfg(feature = "postgres")]
229    impl<T: for<'r> super::FromRow<'r, sqlx::postgres::PgRow>> RowPostgres for T {}
230    #[cfg(not(feature = "postgres"))]
231    pub trait RowPostgres {}
232    #[cfg(not(feature = "postgres"))]
233    impl<T> RowPostgres for T {}
234
235    #[cfg(feature = "sqlite")]
236    pub trait RowSqlite: for<'r> super::FromRow<'r, sqlx::sqlite::SqliteRow> {}
237    #[cfg(feature = "sqlite")]
238    impl<T: for<'r> super::FromRow<'r, sqlx::sqlite::SqliteRow>> RowSqlite for T {}
239    #[cfg(not(feature = "sqlite"))]
240    pub trait RowSqlite {}
241    #[cfg(not(feature = "sqlite"))]
242    impl<T> RowSqlite for T {}
243
244    /// Decodes from every enabled driver's row type.
245    pub trait DriverRows: RowMySql + RowPostgres + RowSqlite {}
246    impl<T: RowMySql + RowPostgres + RowSqlite> DriverRows for T {}
247}
248
249#[cfg(not(any(feature = "mysql", feature = "postgres", feature = "sqlite")))]
250compile_error!("orbit-orm needs at least one database feature: mysql, postgres or sqlite");