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#[non_exhaustive]
17#[derive(Debug)]
18pub enum Error {
19 Rusqlite(rusqlite::Error),
21
22 Aborted,
26
27 AccessPanic(SyncWrapper<Box<dyn std::any::Any + Send + 'static>>),
32
33 #[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 let _connection1 = connection.clone();
127
128 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}