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
38impl std::fmt::Display for Error {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            Self::Rusqlite(error) => error.fmt(f),
42            Self::Aborted => "the connection thread aborted the request".fmt(f),
43            Self::AccessPanic(_) => "a connection access panicked".fmt(f),
44
45            #[cfg(feature = "wal-pool")]
46            Self::InvalidJournalMode(journal_mode) => {
47                write!(
48                    f,
49                    "failed to set journal_mode to WAL, journal_mode is \"{journal_mode}\""
50                )
51            }
52        }
53    }
54}
55
56impl std::error::Error for Error {
57    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
58        match self {
59            Self::Rusqlite(error) => Some(error),
60            Self::Aborted => None,
61            Self::AccessPanic(_) => None,
62
63            #[cfg(feature = "wal-pool")]
64            Self::InvalidJournalMode(_) => None,
65        }
66    }
67}
68
69impl From<rusqlite::Error> for Error {
70    fn from(error: rusqlite::Error) -> Self {
71        Self::Rusqlite(error)
72    }
73}
74
75#[cfg(test)]
76mod test {
77    use super::*;
78    use std::path::Path;
79
80    pub const fn _assert_send<T>()
81    where
82        T: Send,
83    {
84    }
85    pub const fn _assert_sync<T>()
86    where
87        T: Sync,
88    {
89    }
90    pub const fn _assert_static_lifetime<T>()
91    where
92        T: 'static,
93    {
94    }
95
96    const _ERROR_IS_SEND: () = _assert_send::<Error>();
97    const _ERROR_IS_SYNC: () = _assert_sync::<Error>();
98    const _ERROR_HAS_A_STATIC_LIFETIME: () = _assert_static_lifetime::<Error>();
99
100    #[tokio::test]
101    async fn sanity() {
102        let temp_path = Path::new("test-temp");
103        std::fs::create_dir_all(temp_path).expect("failed to create temp dir");
104
105        let connection_error = AsyncConnection::builder()
106            .open(".")
107            .await
108            .expect_err("connection should not open on a directory");
109        assert!(matches!(connection_error, Error::Rusqlite(_)));
110
111        let connection_path = temp_path.join("sanity.db");
112        match std::fs::remove_file(&connection_path) {
113            Ok(()) => {}
114            Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
115            Err(error) => {
116                panic!("failed to remove old database: {error:?}");
117            }
118        }
119
120        let connection = AsyncConnection::builder()
121            .open(connection_path)
122            .await
123            .expect("connection should be open");
124
125        // Ensure connection is clone
126        let _connection1 = connection.clone();
127
128        // Ensure connection survives panic
129        let panic_error = connection
130            .access(|_connection| panic!("the connection should survive the panic"))
131            .await
132            .expect_err("the access should have failed");
133
134        assert!(matches!(panic_error, Error::AccessPanic(_)));
135
136        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;";
137        connection
138            .access(|connection| connection.execute_batch(setup_sql))
139            .await
140            .expect("failed to create tables")
141            .expect("failed to execute");
142
143        connection
144            .close()
145            .await
146            .expect("an error occured while closing");
147    }
148}