nd_async_rusqlite/
lib.rs

1mod async_connection;
2mod sync_wrapper;
3#[cfg(feature = "wal-pool")]
4mod wal_pool;
5
6pub use self::async_connection::AsyncConnection;
7pub use self::async_connection::AsyncConnectionBuilder;
8pub use self::sync_wrapper::SyncWrapper;
9#[cfg(feature = "wal-pool")]
10pub use self::wal_pool::WalPool;
11#[cfg(feature = "wal-pool")]
12pub use self::wal_pool::WalPoolBuilder;
13pub use rusqlite;
14
15/// The library error type
16#[non_exhaustive]
17#[derive(Debug)]
18pub enum Error {
19    /// A rusqlite error
20    Rusqlite(rusqlite::Error),
21
22    /// The request was aborted for some reason.
23    ///
24    /// This means that the background thread shutdown while this request was in-flight.
25    Aborted,
26
27    /// An access panicked.
28    ///
29    /// You can re-throw the panic data.
30    /// The background thread is still alive.
31    AccessPanic(SyncWrapper<Box<dyn std::any::Any + Send + 'static>>),
32
33    /// The WalPool attempted to put the database into WAL mode, but failed.
34    #[cfg(feature = "wal-pool")]
35    InvalidJournalMode(String),
36
37    /// A generic error
38    Generic(&'static str),
39}
40
41impl std::fmt::Display for Error {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            Self::Rusqlite(error) => error.fmt(f),
45            Self::Aborted => "the connection thread aborted the request".fmt(f),
46            Self::AccessPanic(_) => "a connection access panicked".fmt(f),
47            Self::Generic(message) => message.fmt(f),
48
49            #[cfg(feature = "wal-pool")]
50            Self::InvalidJournalMode(journal_mode) => {
51                write!(
52                    f,
53                    "failed to set journal_mode to WAL, journal_mode is \"{journal_mode}\""
54                )
55            }
56        }
57    }
58}
59
60impl std::error::Error for Error {
61    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
62        match self {
63            Self::Rusqlite(error) => Some(error),
64            Self::Aborted => None,
65            Self::AccessPanic(_) => None,
66            Self::Generic(_) => None,
67
68            #[cfg(feature = "wal-pool")]
69            Self::InvalidJournalMode(_) => None,
70        }
71    }
72}
73
74impl From<rusqlite::Error> for Error {
75    fn from(error: rusqlite::Error) -> Self {
76        Self::Rusqlite(error)
77    }
78}
79
80#[cfg(test)]
81mod test {
82    use super::*;
83    use std::path::Path;
84
85    pub const fn _assert_send<T>()
86    where
87        T: Send,
88    {
89    }
90    pub const fn _assert_sync<T>()
91    where
92        T: Sync,
93    {
94    }
95    pub const fn _assert_static_lifetime<T>()
96    where
97        T: 'static,
98    {
99    }
100
101    const _ERROR_IS_SEND: () = _assert_send::<Error>();
102    const _ERROR_IS_SYNC: () = _assert_sync::<Error>();
103    const _ERROR_HAS_A_STATIC_LIFETIME: () = _assert_static_lifetime::<Error>();
104
105    #[tokio::test]
106    async fn sanity() {
107        let temp_path = Path::new("test-temp");
108        std::fs::create_dir_all(temp_path).expect("failed to create temp dir");
109
110        let connection_error = AsyncConnection::builder()
111            .open(".")
112            .await
113            .expect_err("connection should not open on a directory");
114        assert!(matches!(connection_error, Error::Rusqlite(_)));
115
116        let connection_path = temp_path.join("sanity.db");
117        match std::fs::remove_file(&connection_path) {
118            Ok(()) => {}
119            Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
120            Err(error) => {
121                panic!("failed to remove old database: {error:?}");
122            }
123        }
124
125        let connection = AsyncConnection::builder()
126            .open(connection_path)
127            .await
128            .expect("connection should be open");
129
130        // Ensure connection is clone
131        let _connection1 = connection.clone();
132
133        // Ensure connection survives panic
134        let panic_error = connection
135            .access(|_connection| panic!("the connection should survive the panic"))
136            .await
137            .expect_err("the access should have failed");
138
139        assert!(matches!(panic_error, Error::AccessPanic(_)));
140
141        let setup_sql = "PRAGMA foreign_keys = ON; CREATE TABLE USERS (id INTEGER PRIMARY KEY, first_name TEXT NOT NULL, last_name TEXT NOT NULL) STRICT;";
142        connection
143            .access(|connection| connection.execute_batch(setup_sql))
144            .await
145            .expect("failed to create tables")
146            .expect("failed to execute");
147
148        connection
149            .close()
150            .await
151            .expect("an error occured while closing");
152    }
153}