1use crate::utils::lock::MutexSafe;
11use async_trait::async_trait;
12use rusqlite::{params_from_iter, Connection, Result as SqliteResult, Row};
13use std::sync::{Arc, Mutex};
14
15pub type DbResult<T> = Result<T, String>;
17
18#[derive(Clone)]
20pub struct Query {
21 pub sql: String,
22 pub params: Vec<QueryParam>,
23}
24
25#[derive(Clone, Debug)]
27pub enum QueryParam {
28 String(String),
29 Int(i32),
30 Int64(i64),
31 Float(f64),
32 #[allow(dead_code)]
33 Bool(bool),
34 Null,
35}
36
37impl Query {
38 pub fn new(sql: impl Into<String>) -> Self {
39 Self {
40 sql: sql.into(),
41 params: Vec::new(),
42 }
43 }
44
45 pub fn with_params(sql: impl Into<String>, params: Vec<QueryParam>) -> Self {
46 Self {
47 sql: sql.into(),
48 params,
49 }
50 }
51}
52
53#[async_trait]
55pub trait DatabaseService: Send + Sync {
56 async fn execute(&self, query: Query) -> DbResult<usize>;
58
59 #[allow(dead_code)]
61 async fn execute_batch(&self, sql: &str) -> DbResult<()>;
62
63 async fn query_one<T, F>(&self, query: Query, mapper: F) -> DbResult<T>
65 where
66 T: Send + 'static,
67 F: Fn(&Row) -> SqliteResult<T> + Send + 'static;
68
69 async fn query_optional<T, F>(&self, query: Query, mapper: F) -> DbResult<Option<T>>
71 where
72 T: Send + 'static,
73 F: Fn(&Row) -> SqliteResult<T> + Send + 'static;
74
75 async fn query_many<T, F>(&self, query: Query, mapper: F) -> DbResult<Vec<T>>
77 where
78 T: Send + 'static,
79 F: Fn(&Row) -> SqliteResult<T> + Send + 'static;
80
81 async fn transaction<F, T>(&self, f: F) -> DbResult<T>
83 where
84 F: FnOnce(&mut Transaction) -> DbResult<T> + Send + 'static,
85 T: Send + 'static;
86
87 async fn last_insert_rowid(&self) -> DbResult<i64>;
89}
90
91pub struct Transaction<'a> {
93 conn: &'a Connection,
94}
95
96impl<'a> Transaction<'a> {
97 pub fn new(conn: &'a Connection) -> Self {
98 Self { conn }
99 }
100
101 pub fn execute(&mut self, query: Query) -> DbResult<usize> {
102 execute_query(self.conn, query)
103 }
104
105 pub fn query_many<T, F>(&self, query: Query, mapper: F) -> DbResult<Vec<T>>
106 where
107 F: Fn(&Row) -> SqliteResult<T>,
108 {
109 query_many(self.conn, query, mapper)
110 }
111}
112
113pub struct RusqliteService {
118 conn: Arc<Mutex<Connection>>,
119}
120
121impl RusqliteService {
122 pub fn new(conn: Arc<Mutex<Connection>>) -> Self {
123 Self { conn }
124 }
125}
126
127#[async_trait]
128impl DatabaseService for RusqliteService {
129 async fn execute(&self, query: Query) -> DbResult<usize> {
130 let conn = Arc::clone(&self.conn);
131 tokio::task::spawn_blocking(move || {
132 let conn = conn.lock_safe();
136 execute_query(&conn, query)
137 })
138 .await
139 .map_err(|e| format!("Task join error: {}", e))?
140 }
141
142 async fn execute_batch(&self, sql: &str) -> DbResult<()> {
143 let conn = Arc::clone(&self.conn);
144 let sql = sql.to_string();
145 tokio::task::spawn_blocking(move || {
146 let conn = conn.lock_safe();
150 conn.execute_batch(&sql)
151 .map_err(|e| format!("Execute batch failed: {}", e))
152 })
153 .await
154 .map_err(|e| format!("Task join error: {}", e))?
155 }
156
157 async fn query_one<T, F>(&self, query: Query, mapper: F) -> DbResult<T>
158 where
159 T: Send + 'static,
160 F: Fn(&Row) -> SqliteResult<T> + Send + 'static,
161 {
162 let conn = Arc::clone(&self.conn);
163 tokio::task::spawn_blocking(move || {
164 let conn = conn.lock_safe();
168 query_one(&conn, query, mapper)
169 })
170 .await
171 .map_err(|e| format!("Task join error: {}", e))?
172 }
173
174 async fn query_optional<T, F>(&self, query: Query, mapper: F) -> DbResult<Option<T>>
175 where
176 T: Send + 'static,
177 F: Fn(&Row) -> SqliteResult<T> + Send + 'static,
178 {
179 let conn = Arc::clone(&self.conn);
180 tokio::task::spawn_blocking(move || {
181 let conn = conn.lock_safe();
185 query_optional(&conn, query, mapper)
186 })
187 .await
188 .map_err(|e| format!("Task join error: {}", e))?
189 }
190
191 async fn query_many<T, F>(&self, query: Query, mapper: F) -> DbResult<Vec<T>>
192 where
193 T: Send + 'static,
194 F: Fn(&Row) -> SqliteResult<T> + Send + 'static,
195 {
196 let conn = Arc::clone(&self.conn);
197 tokio::task::spawn_blocking(move || {
198 let conn = conn.lock_safe();
202 query_many(&conn, query, mapper)
203 })
204 .await
205 .map_err(|e| format!("Task join error: {}", e))?
206 }
207
208 async fn transaction<F, T>(&self, f: F) -> DbResult<T>
209 where
210 F: FnOnce(&mut Transaction) -> DbResult<T> + Send + 'static,
211 T: Send + 'static,
212 {
213 let conn = Arc::clone(&self.conn);
214 tokio::task::spawn_blocking(move || {
215 let conn = conn.lock_safe();
219
220 conn.execute("BEGIN TRANSACTION", [])
221 .map_err(|e| format!("Failed to begin transaction: {}", e))?;
222
223 let mut transaction = Transaction::new(&conn);
224 let result = f(&mut transaction);
225
226 match result {
227 Ok(value) => {
228 conn.execute("COMMIT", [])
229 .map_err(|e| format!("Failed to commit transaction: {}", e))?;
230 Ok(value)
231 }
232 Err(e) => {
233 conn.execute("ROLLBACK", [])
234 .map_err(|e| format!("Failed to rollback transaction: {}", e))?;
235 Err(e)
236 }
237 }
238 })
239 .await
240 .map_err(|e| format!("Task join error: {}", e))?
241 }
242
243 async fn last_insert_rowid(&self) -> DbResult<i64> {
244 let conn = Arc::clone(&self.conn);
245 tokio::task::spawn_blocking(move || {
246 let conn = conn.lock_safe();
250 Ok(conn.last_insert_rowid())
251 })
252 .await
253 .map_err(|e| format!("Task join error: {}", e))?
254 }
255}
256
257fn execute_query(conn: &Connection, query: Query) -> DbResult<usize> {
260 let params = convert_params(&query.params);
261 conn.execute(&query.sql, params_from_iter(params.iter()))
262 .map_err(|e| format!("Execute failed: {}", e))
263}
264
265fn query_one<T, F>(conn: &Connection, query: Query, mapper: F) -> DbResult<T>
266where
267 F: Fn(&Row) -> SqliteResult<T>,
268{
269 let params = convert_params(&query.params);
270 conn.query_row(&query.sql, params_from_iter(params.iter()), mapper)
271 .map_err(|e| format!("Query one failed: {}", e))
272}
273
274fn query_optional<T, F>(conn: &Connection, query: Query, mapper: F) -> DbResult<Option<T>>
275where
276 F: Fn(&Row) -> SqliteResult<T>,
277{
278 match query_one(conn, query, mapper) {
279 Ok(value) => Ok(Some(value)),
280 Err(e) if e.contains("Query returned no rows") || e.contains("QueryReturnedNoRows") => {
281 Ok(None)
282 }
283 Err(e) => Err(e),
284 }
285}
286
287fn query_many<T, F>(conn: &Connection, query: Query, mapper: F) -> DbResult<Vec<T>>
288where
289 F: Fn(&Row) -> SqliteResult<T>,
290{
291 let params = convert_params(&query.params);
292 let mut stmt = conn
293 .prepare(&query.sql)
294 .map_err(|e| format!("Prepare failed: {}", e))?;
295
296 let rows = stmt
297 .query_map(params_from_iter(params.iter()), mapper)
298 .map_err(|e| format!("Query map failed: {}", e))?;
299
300 rows.collect::<SqliteResult<Vec<T>>>()
301 .map_err(|e| format!("Collect failed: {}", e))
302}
303
304fn convert_params(params: &[QueryParam]) -> Vec<rusqlite::types::Value> {
306 params
307 .iter()
308 .map(|p| match p {
309 QueryParam::String(s) => rusqlite::types::Value::Text(s.clone()),
310 QueryParam::Int(i) => rusqlite::types::Value::Integer(*i as i64),
311 QueryParam::Int64(i) => rusqlite::types::Value::Integer(*i),
312 QueryParam::Float(f) => rusqlite::types::Value::Real(*f),
313 QueryParam::Bool(b) => rusqlite::types::Value::Integer(if *b { 1 } else { 0 }),
314 QueryParam::Null => rusqlite::types::Value::Null,
315 })
316 .collect()
317}
318
319#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[tokio::test]
325 async fn test_execute_query() {
326 let conn = Connection::open_in_memory().unwrap();
327 conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
328 .unwrap();
329
330 let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
331
332 let query = Query::with_params(
333 "INSERT INTO test (name) VALUES (?)",
334 vec![QueryParam::String("Alice".to_string())],
335 );
336
337 let rows = service.execute(query).await.unwrap();
338 assert_eq!(rows, 1);
339 }
340
341 #[tokio::test]
342 async fn test_query_one() {
343 let conn = Connection::open_in_memory().unwrap();
344 conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
345 .unwrap();
346 conn.execute("INSERT INTO test (name) VALUES ('Bob')", [])
347 .unwrap();
348
349 let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
350
351 let query = Query::new("SELECT name FROM test WHERE id = 1");
352 let name: String = service.query_one(query, |row| row.get(0)).await.unwrap();
353
354 assert_eq!(name, "Bob");
355 }
356
357 #[tokio::test]
358 async fn test_query_many() {
359 let conn = Connection::open_in_memory().unwrap();
360 conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
361 .unwrap();
362 conn.execute("INSERT INTO test (name) VALUES ('Alice')", [])
363 .unwrap();
364 conn.execute("INSERT INTO test (name) VALUES ('Bob')", [])
365 .unwrap();
366
367 let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
368
369 let query = Query::new("SELECT name FROM test ORDER BY id");
370 let names: Vec<String> = service.query_many(query, |row| row.get(0)).await.unwrap();
371
372 assert_eq!(names, vec!["Alice", "Bob"]);
373 }
374
375 #[tokio::test]
376 async fn test_query_optional() {
377 let conn = Connection::open_in_memory().unwrap();
378 conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
379 .unwrap();
380
381 let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
382
383 let query = Query::new("SELECT name FROM test WHERE id = 999");
384 let result: Option<String> = service
385 .query_optional(query, |row| row.get(0))
386 .await
387 .unwrap();
388
389 assert_eq!(result, None);
390 }
391
392 #[tokio::test]
393 async fn test_transaction() {
394 let conn = Connection::open_in_memory().unwrap();
395 conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
396 .unwrap();
397
398 let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
399
400 let result = service
401 .transaction(|tx| {
402 tx.execute(Query::with_params(
403 "INSERT INTO test (name) VALUES (?)",
404 vec![QueryParam::String("Alice".to_string())],
405 ))?;
406 tx.execute(Query::with_params(
407 "INSERT INTO test (name) VALUES (?)",
408 vec![QueryParam::String("Bob".to_string())],
409 ))?;
410 Ok(())
411 })
412 .await;
413
414 assert!(result.is_ok());
415
416 let query = Query::new("SELECT COUNT(*) FROM test");
418 let count: i32 = service.query_one(query, |row| row.get(0)).await.unwrap();
419 assert_eq!(count, 2);
420 }
421 #[tokio::test]
433 async fn a_poisoned_connection_still_serves_queries() {
434 let conn = Arc::new(Mutex::new(Connection::open_in_memory().unwrap()));
435 {
436 let c = conn.lock_safe();
437 c.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY);")
438 .unwrap();
439 }
440
441 let poisoner = Arc::clone(&conn);
443 let hook = std::panic::take_hook();
444 std::panic::set_hook(Box::new(|_| {}));
445 let _ = std::thread::spawn(move || {
446 let _guard = poisoner.lock().unwrap();
447 panic!("a row mapper blew up while holding the connection");
448 })
449 .join();
450 std::panic::set_hook(hook);
451 assert!(conn.lock().is_err(), "the mutex should now be poisoned");
452
453 let service = RusqliteService::new(Arc::clone(&conn));
455 service
456 .execute(Query::new("INSERT INTO test (id) VALUES (1)"))
457 .await
458 .expect("execute must survive a poisoned connection");
459 let count: i32 = service
460 .query_one(Query::new("SELECT COUNT(*) FROM test"), |row| row.get(0))
461 .await
462 .expect("query_one must survive a poisoned connection");
463 assert_eq!(count, 1);
464 }
465}