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}