async_rusqlite/
lib.rs

1//! # Async-rusqlite
2//!
3//! A tiny async wrapper around [`rusqlite`]. Use [`crate::Connection`]
4//! to open a connection, and then [`crate::Connection::call()`] to
5//! execute commands against it.
6//!
7//! ```rust
8//! # #[tokio::main]
9//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
10//! use async_rusqlite::Connection;
11//!
12//! #[derive(Debug)]
13//! struct Person {
14//!     id: i32,
15//!     name: String,
16//!     data: Option<Vec<u8>>,
17//! }
18//!
19//! let conn = Connection::open_in_memory().await?;
20//!
21//! conn.call(|conn| {
22//!     conn.execute(
23//!         "CREATE TABLE person (
24//!             id   INTEGER PRIMARY KEY,
25//!             name TEXT NOT NULL,
26//!             data BLOB
27//!         )",
28//!         (),
29//!     )
30//! }).await?;
31//!
32//! let me = Person {
33//!     id: 0,
34//!     name: "Steven".to_string(),
35//!     data: None,
36//! };
37//!
38//! conn.call(move |conn| {
39//!     conn.execute(
40//!         "INSERT INTO person (name, data) VALUES (?1, ?2)",
41//!         (&me.name, &me.data),
42//!     )
43//! }).await?;
44//!
45//! # Ok(())
46//! # }
47//! ```
48
49use asyncified::{ Asyncified, AsyncifiedBuilder };
50use std::path::Path;
51
52// re-export rusqlite types.
53pub use rusqlite;
54
55pub struct ConnectionBuilder {
56    asyncified_builder: AsyncifiedBuilder<Option<rusqlite::Connection>>
57}
58
59impl std::default::Default for ConnectionBuilder {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65impl ConnectionBuilder {
66    /// Configure and build a new [`Connection`].
67    pub fn new() -> Self {
68        Self {
69            asyncified_builder: AsyncifiedBuilder::new()
70        }
71    }
72
73    /// Configure the thread that the database connection will live on.
74    pub fn thread_builder(mut self, thread: std::thread::Builder) -> Self {
75        self.asyncified_builder = self.asyncified_builder.thread_builder(thread);
76        self
77    }
78
79    /// Configure how many functions can be queued to run on our connection
80    /// before `conn.call(..).await` will wait and backpressure will kick in.
81    pub fn channel_size(mut self, size: usize) -> Self {
82        self.asyncified_builder = self.asyncified_builder.channel_size(size);
83        self
84    }
85
86    /// Configure a function to be called exactly once when the connection is closed.
87    /// If the database has already been closed then it will be given `None`, else it
88    /// will be handed the database connection.
89    pub fn on_close<F: FnOnce(Option<rusqlite::Connection>) + Send + 'static>(mut self, f: F) -> Self {
90        self.asyncified_builder = self.asyncified_builder.on_close(move |o| f(o.take()));
91        self
92    }
93
94    /// Open a new connection to an SQLite database. If a database does not exist at the
95    /// path, one is created.
96    ///
97    /// # Failure
98    ///
99    /// Will return `Err` if `path` cannot be converted to a C-compatible string
100    /// or if the underlying SQLite open call fails.
101    pub async fn open<P: AsRef<Path>>(self, path: P) -> Result<Connection,rusqlite::Error> {
102        let path = path.as_ref().to_owned();
103        let conn = self.asyncified_builder
104            .build(move || rusqlite::Connection::open(path).map(Some))
105            .await?;
106        Ok(Connection { conn })
107    }
108
109    /// Open a new connection to an in-memory SQLite database.
110    ///
111    /// # Failure
112    ///
113    /// Will return `Err` if the underlying SQLite open call fails.
114    pub async fn open_in_memory(self) -> Result<Connection,rusqlite::Error> {
115        let conn = self.asyncified_builder
116            .build(|| rusqlite::Connection::open_in_memory().map(Some))
117            .await?;
118        Ok(Connection { conn })
119    }
120
121    /// Open a new connection to a SQLite database.
122    ///
123    /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid
124    /// flag combinations.
125    ///
126    /// # Failure
127    ///
128    /// Will return `Err` if `path` cannot be converted to a C-compatible
129    /// string or if the underlying SQLite open call fails.
130    pub async fn open_with_flags<P: AsRef<Path>>(self, path: P, flags: rusqlite::OpenFlags) -> Result<Connection,rusqlite::Error> {
131        let path = path.as_ref().to_owned();
132        let conn = self
133            .asyncified_builder
134            .build(move || rusqlite::Connection::open_with_flags(path, flags).map(Some))
135            .await?;
136        Ok(Connection { conn })
137    }
138
139    /// Open a new connection to a SQLite database using the specific flags and
140    /// vfs name.
141    ///
142    /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid
143    /// flag combinations.
144    ///
145    /// # Failure
146    ///
147    /// Will return `Err` if either `path` or `vfs` cannot be converted to a
148    /// C-compatible string or if the underlying SQLite open call fails.
149    pub async fn open_with_flags_and_vfs<P: AsRef<Path>, V: IntoName>(
150        self,
151        path: P,
152        flags: rusqlite::OpenFlags,
153        vfs: impl Into<V>,
154    ) -> Result<Connection,rusqlite::Error> {
155        let path = path.as_ref().to_owned();
156        let vfs = vfs.into();
157        let conn = self.asyncified_builder
158            .build(move || {
159                let vfs = vfs.into_name();
160                rusqlite::Connection::open_with_flags_and_vfs(path, flags, vfs).map(Some)
161            })
162            .await?;
163        Ok(Connection { conn })
164    }
165
166    /// Open a new connection to an in-memory SQLite database.
167    ///
168    /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid
169    /// flag combinations.
170    ///
171    /// # Failure
172    ///
173    /// Will return `Err` if the underlying SQLite open call fails.
174    pub async fn open_in_memory_with_flags(self, flags: rusqlite::OpenFlags) -> Result<Connection,rusqlite::Error> {
175        self.open_with_flags(":memory:", flags).await
176    }
177
178    /// Open a new connection to an in-memory SQLite database using the specific
179    /// flags and vfs name.
180    ///
181    /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid
182    /// flag combinations.
183    ///
184    /// # Failure
185    ///
186    /// Will return `Err` if `vfs` cannot be converted to a C-compatible
187    /// string or if the underlying SQLite open call fails.
188    pub async fn open_in_memory_with_flags_and_vfs<V: IntoName>(self, flags: rusqlite::OpenFlags, vfs: impl Into<V>) -> Result<Connection,rusqlite::Error> {
189        self.open_with_flags_and_vfs(":memory:", flags, vfs).await
190    }
191}
192/// A handle which allows access to the underlying [`rusqlite::Connection`]
193/// via [`Connection::call()`].
194#[derive(Debug, Clone)]
195pub struct Connection {
196    // None if connection is closed, else Some(connection).
197    conn: Asyncified<Option<rusqlite::Connection>>
198}
199
200impl Connection {
201    /// Open a new connection to an SQLite database. If a database does not exist at the
202    /// path, one is created. Shorthand for `Connection::builder().open(path).await`.
203    ///
204    /// # Failure
205    ///
206    /// Will return `Err` if `path` cannot be converted to a C-compatible string
207    /// or if the underlying SQLite open call fails.
208    pub async fn open<P: AsRef<Path>>(path: P) -> Result<Connection,rusqlite::Error> {
209        Self::builder().open(path).await
210    }
211
212    /// Open a new connection to an in-memory SQLite database. Shorthand for
213    /// `Connection::builder().open_in_memory().await`.
214    ///
215    /// # Failure
216    ///
217    /// Will return `Err` if the underlying SQLite open call fails.
218    pub async fn open_in_memory() -> Result<Connection,rusqlite::Error> {
219        Self::builder().open_in_memory().await
220    }
221
222    /// Configure and build a new connection.
223    pub fn builder() -> ConnectionBuilder {
224        ConnectionBuilder::new()
225    }
226
227    /// Close the SQLite connection.
228    ///
229    /// This is functionally equivalent to the `Drop` implementation for
230    /// [`Connection`] except that on failure, it returns the error. Unlike
231    /// the [`rusqlite`] version of this method, it does not need to consume
232    /// `self`.
233    ///
234    /// # Failure
235    ///
236    /// Will return `Err` if the underlying SQLite call fails.
237    pub async fn close(&self) -> Result<(),Error> {
238        self.conn.call(|conn| {
239            match conn.take() {
240                Some(c) => {
241                    match c.close() {
242                        Ok(_) => Ok(()),
243                        Err((c, err)) => {
244                            // close failed; replace the connection and
245                            // return the error.
246                            *conn = Some(c);
247                            Err(Error::Rusqlite(err))
248                        }
249                    }
250                },
251                // Already closed!
252                None => Err(Error::AlreadyClosed)
253            }
254        }).await
255    }
256
257    /// Run some arbitrary function against the [`rusqlite::Connection`] and return the result.
258    ///
259    /// # Failure
260    ///
261    /// Will return Err if the connection is closed, or if the provided function returns an error.
262    /// The error type must impl [`From<AlreadyClosed>`] to handle this possibility being emitted.
263    pub async fn call<R, E, F>(&self, f: F) -> Result<R,E>
264    where
265        R: Send + 'static,
266        E: Send + 'static + From<AlreadyClosed>,
267        F: Send + 'static + FnOnce(&mut rusqlite::Connection) -> Result<R, E>
268    {
269        self.conn.call(|conn| {
270            match conn {
271                Some(conn) => Ok(f(conn)?),
272                None => Err(AlreadyClosed.into())
273            }
274        }).await
275    }
276}
277
278/// If the connection is already closed, this will be returned
279/// for the user to convert into their own error type. This can be
280/// converted into [`Error`] and [`rusqlite::Error`] so that either
281/// can be returned in the [`Connection::call()`] function.
282#[derive(Clone,Copy,PartialEq,Eq,Debug)]
283pub struct AlreadyClosed;
284
285impl From<AlreadyClosed> for rusqlite::Error {
286    fn from(_: AlreadyClosed) -> Self {
287        // There's not an ideal match for this error, so
288        // just output something that is sortof sensible:
289        let e = rusqlite::ffi::Error {
290            code: rusqlite::ffi::ErrorCode::CannotOpen,
291            extended_code: rusqlite::ffi::SQLITE_CANTOPEN
292        };
293        rusqlite::Error::SqliteFailure(e, None)
294    }
295}
296
297/// An error emitted if closing the connection fails.
298#[derive(Debug, PartialEq)]
299#[non_exhaustive]
300pub enum Error {
301    /// The connection to SQLite has already been closed.
302    AlreadyClosed,
303    /// A `rusqlite` error occured trying to close the connection.
304    Rusqlite(rusqlite::Error),
305}
306
307impl std::fmt::Display for Error {
308    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309        match self {
310            Error::AlreadyClosed => write!(f, "The connection has already been closed"),
311            Error::Rusqlite(e) => write!(f, "Rusqlite error: {e}"),
312        }
313    }
314}
315
316impl std::error::Error for Error {
317    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
318        match self {
319            Error::AlreadyClosed => None,
320            Error::Rusqlite(e) => Some(e),
321        }
322    }
323}
324
325impl From<rusqlite::Error> for Error {
326    fn from(value: rusqlite::Error) -> Self {
327        Error::Rusqlite(value)
328    }
329}
330
331impl From<AlreadyClosed> for Error {
332    fn from(_: AlreadyClosed) -> Self {
333        Error::AlreadyClosed
334    }
335}
336
337
338pub trait IntoName: Send + 'static {
339    fn into_name(&self) -> impl rusqlite::Name;
340}
341
342impl IntoName for String {
343    fn into_name(&self) -> impl rusqlite::Name {
344        self.as_ref() as &str
345    }
346}
347impl IntoName for std::ffi::CString {
348    fn into_name(&self) -> impl rusqlite::Name {
349        self.as_ref() as &std::ffi::CStr
350    }
351}
352
353
354#[cfg(test)]
355mod test {
356    use super::*;
357
358    #[tokio::test]
359    async fn test_many_calls() -> Result<(), Error> {
360        let conn = Connection::open_in_memory().await?;
361
362        conn.call(|conn| {
363            conn.execute(
364                "CREATE TABLE numbers (
365                    id   INTEGER PRIMARY KEY,
366                    num  INTEGER NOT NULL
367                )",
368                (),
369            )
370        }).await?;
371
372        for n in 0..10000 {
373            conn.call(move |conn| {
374                conn.execute(
375                    "INSERT INTO numbers (num) VALUES (?1)",
376                    (n,)
377                )
378            }).await?;
379        }
380
381        let count: usize = conn.call(|conn| {
382            conn.query_row(
383                "SELECT count(num) FROM numbers",
384                (),
385                |r| r.get(0)
386            )
387        }).await?;
388
389        assert_eq!(count, 10000);
390        Ok(())
391    }
392
393    #[tokio::test]
394    async fn closes_once() {
395        let conn = Connection::open_in_memory().await.unwrap();
396
397        conn.close().await.expect("should close ok first time");
398        let err = conn.close().await.expect_err("should error second time");
399
400        assert_eq!(err, Error::AlreadyClosed);
401    }
402
403    #[tokio::test]
404    async fn cant_call_after_close() {
405        let conn = Connection::open_in_memory().await.unwrap();
406
407        conn.close().await.expect("should close ok");
408        let err = conn
409            .call(|_conn| Ok::<_,Error>(()))
410            .await
411            .expect_err("should error second time");
412
413        assert_eq!(err, Error::AlreadyClosed);
414    }
415
416    #[tokio::test]
417    async fn custom_call_error() {
418        // Custom error type that can capture possibility
419        // of connection being closed.
420        #[derive(Debug,PartialEq)]
421        pub enum MyErr { AlreadyClosed, Other(&'static str) }
422        impl From<AlreadyClosed> for MyErr {
423            fn from(_: AlreadyClosed) -> MyErr {
424                MyErr::AlreadyClosed
425            }
426        }
427
428        let conn = Connection::open_in_memory().await.unwrap();
429
430        let err = conn
431            .call(|_conn| Err::<(),_>(MyErr::Other("foo")))
432            .await
433            .expect_err("should error");
434
435        assert_eq!(err, MyErr::Other("foo"));
436
437        conn.close().await.unwrap();
438
439        let err = conn
440            .call(|_conn| Ok::<_,MyErr>(()))
441            .await
442            .expect_err("should error");
443
444        assert_eq!(err, MyErr::AlreadyClosed);
445    }
446
447    #[tokio::test]
448    async fn close_fn_called_on_drop() {
449        let (tx, rx) = tokio::sync::oneshot::channel();
450        let conn = Connection::builder()
451            .on_close(move |db| { let _ = tx.send(db); })
452            .open_in_memory()
453            .await
454            .unwrap();
455
456        drop(conn);
457
458        // This will wait forever if the close fn isn't called:
459        let db = rx.await.unwrap();
460        assert!(db.is_some());
461    }
462}