1mod conv;
2mod errors;
3mod manager;
4pub mod migration;
5pub mod sqlite;
6
7use std::num::NonZeroUsize;
8use std::path::Path;
9
10pub use conv::{DatabaseTypeConversionError, SqlTypeConvert};
11use diesel::{RunQueryDsl, SqliteConnection};
12pub use errors::{DatabaseError, SchemaVerificationError};
13pub use manager::{ConnectionManager, ConnectionManagerError, configure_connection_on_creation};
14use tracing::Instrument;
15
16pub type Result<T, E = DatabaseError> = std::result::Result<T, E>;
17
18pub fn default_connection_pool_size() -> NonZeroUsize {
23 let available_cores = std::thread::available_parallelism().map_or(1, NonZeroUsize::get);
24 let connection_count = available_cores.saturating_mul(2);
25 NonZeroUsize::new(connection_count).expect("connection count must be non-zero")
26}
27
28#[derive(Clone)]
31pub struct Db {
32 pool: deadpool_diesel::Pool<ConnectionManager, deadpool::managed::Object<ConnectionManager>>,
33}
34
35impl Db {
36 pub fn new(database_filepath: &Path) -> Result<Self, DatabaseError> {
38 Self::new_with_pool_size(database_filepath, default_connection_pool_size())
39 }
40
41 pub fn new_with_pool_size(
43 database_filepath: &Path,
44 connection_pool_size: NonZeroUsize,
45 ) -> Result<Self, DatabaseError> {
46 let manager = ConnectionManager::new(database_filepath.to_str().unwrap());
47 let pool = deadpool_diesel::Pool::builder(manager)
48 .max_size(connection_pool_size.get())
49 .build()?;
50 Ok(Self { pool })
51 }
52
53 pub async fn pinned_connection(&self) -> Result<PinnedConnection, DatabaseError> {
59 let conn = self
60 .pool
61 .get()
62 .in_current_span()
63 .await
64 .map_err(|e| DatabaseError::ConnectionPoolObtainError(Box::new(e)))?;
65 Ok(PinnedConnection { conn })
66 }
67
68 pub async fn transact<R, E, Q, M>(&self, msg: M, query: Q) -> std::result::Result<R, E>
70 where
71 Q: Send
72 + for<'a, 't> FnOnce(&'a mut SqliteConnection) -> std::result::Result<R, E>
73 + 'static,
74 R: Send + 'static,
75 M: Send + ToString,
76 E: From<diesel::result::Error>,
77 E: From<DatabaseError>,
78 E: std::error::Error + Send + Sync + 'static,
79 {
80 self.pinned_connection().await.map_err(E::from)?.transact(msg, query).await
81 }
82
83 pub async fn query<R, E, Q, M>(&self, msg: M, query: Q) -> std::result::Result<R, E>
85 where
86 Q: Send + FnOnce(&mut SqliteConnection) -> std::result::Result<R, E> + 'static,
87 R: Send + 'static,
88 M: Send + ToString,
89 E: From<DatabaseError>,
90 E: std::error::Error + Send + Sync + 'static,
91 {
92 self.pinned_connection().await.map_err(E::from)?.query(msg, query).await
93 }
94}
95
96pub struct PinnedConnection {
103 conn: deadpool::managed::Object<ConnectionManager>,
104}
105
106impl PinnedConnection {
107 pub async fn transact<R, E, Q, M>(&self, msg: M, query: Q) -> std::result::Result<R, E>
110 where
111 Q: Send
112 + for<'a, 't> FnOnce(&'a mut SqliteConnection) -> std::result::Result<R, E>
113 + 'static,
114 R: Send + 'static,
115 M: Send + ToString,
116 E: From<diesel::result::Error>,
117 E: From<DatabaseError>,
118 E: std::error::Error + Send + Sync + 'static,
119 {
120 let span = tracing::Span::current();
121 self.conn
122 .interact(move |conn| {
123 let _guard = span.enter();
124 <_ as diesel::Connection>::transaction::<R, E, Q>(conn, query)
125 })
126 .await
127 .map_err(|err| E::from(DatabaseError::interact(&msg.to_string(), &err)))?
128 }
129
130 pub async fn query<R, E, Q, M>(&self, msg: M, query: Q) -> std::result::Result<R, E>
132 where
133 Q: Send + FnOnce(&mut SqliteConnection) -> std::result::Result<R, E> + 'static,
134 R: Send + 'static,
135 M: Send + ToString,
136 E: From<DatabaseError>,
137 E: std::error::Error + Send + Sync + 'static,
138 {
139 let span = tracing::Span::current();
140 self.conn
141 .interact(move |conn| {
142 let _guard = span.enter();
143 query(conn)
144 })
145 .await
146 .map_err(|err| E::from(DatabaseError::interact(&msg.to_string(), &err)))?
147 }
148}