Skip to main content

keelson_sqlx/
sqlite.rs

1//! The SQLite driver: [`Pool`] implements
2//! [`Executor`], [`Begin`], [`BeginWith`] and [`StreamExecutor`].
3//!
4//! SQLite has no native storage class for the mapped types, so every one of
5//! them binds as its pinned text form from `docs/type-mappings.md`, rendered
6//! here by hand — byte-identical to the forms `Value` serialises to, which is
7//! what `FromValue`'s text acceptance reads back.
8
9use std::sync::Arc;
10
11use keelson_core::Value;
12use keelson_exec::{
13    Begin, BeginWith, Column, ExecError, ExecFuture, ExecResult, Executor, Family, RawConnection,
14    Row, RowStream, Statement, StreamExecutor, Transaction, TxConflict, TxConflictError, TxOptions,
15};
16use sqlx::sqlite::{SqliteArguments, SqliteRow};
17use sqlx::{Column as _, Row as _, Sqlite, TypeInfo as _, ValueRef as _};
18
19use crate::common::{decode_err, unhandled};
20
21/// A SQLite connection pool.
22#[derive(Debug, Clone)]
23pub struct Pool {
24    inner: sqlx::SqlitePool,
25}
26
27impl Pool {
28    /// Connect to `url` (`sqlite://path/to.db`, or `sqlite::memory:`).
29    ///
30    /// The database file is created if missing — for the no-server engine
31    /// that is almost always what a caller means.
32    pub async fn connect(url: &str) -> Result<Self, ExecError> {
33        use std::str::FromStr as _;
34        let opts = sqlx::sqlite::SqliteConnectOptions::from_str(url)
35            .map_err(ExecError::driver)?
36            .create_if_missing(true);
37        let inner = sqlx::sqlite::SqlitePoolOptions::new()
38            .connect_with(opts)
39            .await
40            .map_err(ExecError::driver)?;
41        Ok(Pool { inner })
42    }
43
44    /// Wrap an existing sqlx pool.
45    pub fn from_pool(inner: sqlx::SqlitePool) -> Self {
46        Pool { inner }
47    }
48
49    /// The wrapped sqlx pool.
50    pub fn inner(&self) -> &sqlx::SqlitePool {
51        &self.inner
52    }
53}
54
55impl Executor for Pool {
56    fn family(&self) -> Family {
57        Family::Sqlite
58    }
59
60    fn fetch(&self, stmt: Statement) -> ExecFuture<'_, Result<Vec<Row>, ExecError>> {
61        Box::pin(async move {
62            let Statement { sql, args, .. } = stmt;
63            do_fetch(&self.inner, &sql, args).await
64        })
65    }
66
67    fn execute(&self, stmt: Statement) -> ExecFuture<'_, Result<ExecResult, ExecError>> {
68        Box::pin(async move {
69            let Statement { sql, args, .. } = stmt;
70            do_execute(&self.inner, &sql, args).await
71        })
72    }
73}
74
75impl Begin for Pool {
76    fn begin(&self) -> ExecFuture<'_, Result<Transaction, ExecError>> {
77        Box::pin(async move {
78            let conn = self.inner.acquire().await.map_err(ExecError::driver)?;
79            Transaction::begin_on(Box::new(RawConn { conn })).await
80        })
81    }
82}
83
84impl BeginWith for Pool {
85    fn begin_with(&self, opts: TxOptions) -> ExecFuture<'_, Result<Transaction, ExecError>> {
86        Box::pin(async move {
87            // Refuse before taking a connection out of the pool — which is
88            // most of what SQLite does with this entry point, since the
89            // standard isolation levels are not on offer here.
90            opts.check(Family::Sqlite)?;
91            let conn = self.inner.acquire().await.map_err(ExecError::driver)?;
92            Transaction::begin_on_with(Box::new(RawConn { conn }), opts).await
93        })
94    }
95}
96
97impl StreamExecutor for Pool {
98    fn fetch_stream(&self, stmt: Statement) -> ExecFuture<'_, Result<RowStream, ExecError>> {
99        Box::pin(async move {
100            let pool = self.inner.clone();
101            let (tx, rx) = tokio::sync::mpsc::channel::<Result<Row, ExecError>>(64);
102            tokio::spawn(async move {
103                use futures_util::StreamExt as _;
104                let Statement { sql, args, .. } = stmt;
105                let q = match bind_args(&sql, args) {
106                    Ok(q) => q,
107                    Err(e) => {
108                        let _ = tx.send(Err(e)).await;
109                        return;
110                    }
111                };
112                let mut native = q.fetch(&pool);
113                let mut header: Option<Arc<[Column]>> = None;
114                while let Some(next) = native.next().await {
115                    let msg = match next {
116                        Ok(row) => decode_row(&row, &mut header),
117                        Err(e) => Err(ExecError::driver(e)),
118                    };
119                    let stop = msg.is_err();
120                    if tx.send(msg).await.is_err() || stop {
121                        return;
122                    }
123                }
124            });
125            Ok(RowStream::new(rx))
126        })
127    }
128}
129
130/// One checked-out connection, exclusively held by a [`Transaction`].
131#[derive(Debug)]
132struct RawConn {
133    conn: sqlx::pool::PoolConnection<Sqlite>,
134}
135
136impl RawConnection for RawConn {
137    fn family(&self) -> Family {
138        Family::Sqlite
139    }
140
141    fn fetch<'a>(
142        &'a mut self,
143        sql: &'a str,
144        args: Vec<Value>,
145    ) -> ExecFuture<'a, Result<Vec<Row>, ExecError>> {
146        Box::pin(async move { do_fetch(&mut *self.conn, sql, args).await })
147    }
148
149    fn execute<'a>(
150        &'a mut self,
151        sql: &'a str,
152        args: Vec<Value>,
153    ) -> ExecFuture<'a, Result<ExecResult, ExecError>> {
154        Box::pin(async move { do_execute(&mut *self.conn, sql, args).await })
155    }
156
157    fn abandon(self: Box<Self>) {
158        let _ = self.conn.detach();
159    }
160}
161
162/// A driver failure, with lock conflicts classified out of it.
163///
164/// sqlx reports SQLite's *extended* result code as a decimal string; the low
165/// byte is the primary code, so `SQLITE_BUSY` (5) covers `BUSY_SNAPSHOT`
166/// (517) and friends, and `SQLITE_LOCKED` (6) covers its extensions too.
167/// This is SQLite's whole concurrency-conflict vocabulary: it has no
168/// serialization failure because it never runs two writers at once.
169fn driver_err(e: sqlx::Error) -> ExecError {
170    const SQLITE_BUSY: i32 = 5;
171    const SQLITE_LOCKED: i32 = 6;
172    if let sqlx::Error::Database(db) = &e {
173        let primary = db
174            .code()
175            .and_then(|c| c.parse::<i32>().ok())
176            .map(|c| c & 0xff);
177        if matches!(primary, Some(SQLITE_BUSY | SQLITE_LOCKED)) {
178            let code = db.code().unwrap_or_default().into_owned();
179            let message = db.message().to_owned();
180            return TxConflictError::new(TxConflict::Busy, code, message)
181                .with_source(e)
182                .into_exec_error();
183        }
184    }
185    ExecError::driver(e)
186}
187
188async fn do_fetch<'e, E>(exec: E, sql: &str, args: Vec<Value>) -> Result<Vec<Row>, ExecError>
189where
190    E: sqlx::Executor<'e, Database = Sqlite>,
191{
192    let rows = bind_args(sql, args)?
193        .fetch_all(exec)
194        .await
195        .map_err(driver_err)?;
196    let mut header: Option<Arc<[Column]>> = None;
197    rows.iter().map(|r| decode_row(r, &mut header)).collect()
198}
199
200async fn do_execute<'e, E>(exec: E, sql: &str, args: Vec<Value>) -> Result<ExecResult, ExecError>
201where
202    E: sqlx::Executor<'e, Database = Sqlite>,
203{
204    // Zero-argument statements go over the driver's plain (unprepared) path:
205    // MySQL refuses transaction control (`BEGIN`, `SAVEPOINT …`) in the
206    // prepared-statement protocol, and nothing is gained by preparing an
207    // argument-less statement anyway.
208    let done = if args.is_empty() {
209        exec.execute(sql).await.map_err(driver_err)?
210    } else {
211        bind_args(sql, args)?
212            .execute(exec)
213            .await
214            .map_err(driver_err)?
215    };
216    let last = Some(done.last_insert_rowid()).filter(|id| *id != 0);
217    Ok(ExecResult::new(done.rows_affected(), last))
218}
219
220/// The total map `Value` → SQLite parameter. Everything the engine has a
221/// storage class for binds natively; every mapped type binds as its pinned
222/// text form (the forms are the ones `Value` serialises to — see
223/// `docs/type-mappings.md`).
224fn bind_args<'q>(
225    sql: &'q str,
226    args: Vec<Value>,
227) -> Result<sqlx::query::Query<'q, Sqlite, SqliteArguments<'q>>, ExecError> {
228    let mut q = sqlx::query(sql);
229    for v in args {
230        q = match v {
231            Value::Null => q.bind(Option::<String>::None),
232            Value::Bool(x) => q.bind(x),
233            Value::I8(x) => q.bind(i64::from(x)),
234            Value::I16(x) => q.bind(i64::from(x)),
235            Value::I32(x) => q.bind(i64::from(x)),
236            Value::I64(x) => q.bind(x),
237            Value::U8(x) => q.bind(i64::from(x)),
238            Value::U16(x) => q.bind(i64::from(x)),
239            Value::U32(x) => q.bind(i64::from(x)),
240            Value::U64(x) => {
241                q.bind(i64::try_from(x).map_err(|_| unsupported_value("u64 out of i64 range"))?)
242            }
243            // SQLite REAL is 8-byte; f32 widens losslessly.
244            Value::F32(x) => q.bind(f64::from(x)),
245            Value::F64(x) => q.bind(x),
246            Value::Text(x) => q.bind(x),
247            Value::Bytes(x) => q.bind(x),
248            #[cfg(feature = "chrono")]
249            Value::Date(x) => q.bind(x.format("%Y-%m-%d").to_string()),
250            #[cfg(feature = "chrono")]
251            Value::Time(x) => q.bind(x.format("%H:%M:%S%.f").to_string()),
252            #[cfg(feature = "chrono")]
253            Value::DateTime(x) => q.bind(x.format("%Y-%m-%dT%H:%M:%S%.f").to_string()),
254            #[cfg(feature = "chrono")]
255            Value::TimestampTz(x) => q.bind(x.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true)),
256            #[cfg(feature = "uuid")]
257            Value::Uuid(x) => q.bind(x.hyphenated().to_string()),
258            // `Decimal::to_string` preserves scale — `1.10` stays `1.10`,
259            // which TEXT storage round-trips exactly.
260            #[cfg(feature = "decimal")]
261            Value::Decimal(x) => q.bind(x.to_string()),
262            #[cfg(feature = "json")]
263            Value::Json(x) => q.bind(
264                serde_json::to_string(&x).map_err(|_| unsupported_value("unserialisable json"))?,
265            ),
266            other => return Err(unsupported_value(other.type_name())),
267        };
268    }
269    Ok(q)
270}
271
272fn unsupported_value(type_name: &'static str) -> ExecError {
273    ExecError::UnsupportedValue {
274        type_name,
275        family: Family::Sqlite,
276    }
277}
278
279fn decode_row(row: &SqliteRow, header: &mut Option<Arc<[Column]>>) -> Result<Row, ExecError> {
280    let columns = header
281        .get_or_insert_with(|| {
282            row.columns()
283                .iter()
284                .map(|c| Column::new(c.name()))
285                .collect::<Vec<_>>()
286                .into()
287        })
288        .clone();
289    let mut values = Vec::with_capacity(row.columns().len());
290    for i in 0..row.columns().len() {
291        values.push(decode_value(row, i)?);
292    }
293    Ok(Row::new(columns, values))
294}
295
296/// SQLite values decode by storage class — `INTEGER`/`REAL`/`TEXT`/`BLOB` —
297/// with one nicety: a column *declared* `BOOLEAN` reads as a real `bool`
298/// (core's `FromValue` for `bool` does not guess from integers, on purpose).
299/// A mapped type stored as `TEXT` comes back as `Value::Text`, and
300/// `FromValue`'s documented text acceptance turns it into the Rust type at
301/// the edge — the round-trip suite is what keeps that contract honest.
302fn decode_value(row: &SqliteRow, i: usize) -> Result<Value, ExecError> {
303    let col = &row.columns()[i];
304    let name = col.name();
305    let raw = row.try_get_raw(i).map_err(|e| decode_err(name, e))?;
306    if raw.is_null() {
307        return Ok(Value::Null);
308    }
309
310    macro_rules! take {
311        ($t:ty) => {
312            row.try_get::<$t, _>(i).map_err(|e| decode_err(name, e))?
313        };
314    }
315
316    // The declared type when the column has one (it names BOOLEAN and the
317    // date-ish declarations); the value's own storage class otherwise
318    // (expressions, RETURNING, aggregates).
319    let decl = col.type_info();
320    let decl = decl.name();
321    let ty = if decl == "NULL" {
322        let vt = raw.type_info();
323        vt.name().to_owned()
324    } else {
325        decl.to_owned()
326    };
327
328    Ok(match ty.as_str() {
329        "BOOLEAN" => Value::Bool(take!(bool)),
330        "INTEGER" | "INT8" => Value::I64(take!(i64)),
331        "REAL" => Value::F64(take!(f64)),
332        // Declared temporal/decimal columns are TEXT under the mappings
333        // table; SQLite's own extended declarations decode as text too and
334        // resolve at the `FromValue` edge.
335        "TEXT" | "DATE" | "TIME" | "DATETIME" | "NUMERIC" => Value::Text(take!(String)),
336        "BLOB" => Value::Bytes(take!(Vec<u8>)),
337        other => return Err(unhandled(name, other)),
338    })
339}