1use crate::utils::lock::MutexSafe;
23use async_trait::async_trait;
24use log::{debug, error};
25use rusqlite::{params_from_iter, Connection, Result as SqliteResult, Row};
26use std::panic::AssertUnwindSafe;
27use std::sync::mpsc;
28use std::sync::{Arc, Condvar, Mutex};
29
30pub type DbResult<T> = Result<T, String>;
32
33#[derive(Clone)]
35pub struct Query {
36 pub sql: String,
37 pub params: Vec<QueryParam>,
38}
39
40#[derive(Clone, Debug)]
42pub enum QueryParam {
43 String(String),
44 Int(i32),
45 Int64(i64),
46 Float(f64),
47 #[allow(dead_code)]
48 Bool(bool),
49 Null,
50}
51
52impl Query {
53 pub fn new(sql: impl Into<String>) -> Self {
54 Self {
55 sql: sql.into(),
56 params: Vec::new(),
57 }
58 }
59
60 pub fn with_params(sql: impl Into<String>, params: Vec<QueryParam>) -> Self {
61 Self {
62 sql: sql.into(),
63 params,
64 }
65 }
66}
67
68#[async_trait]
70pub trait DatabaseService: Send + Sync {
71 async fn execute(&self, query: Query) -> DbResult<usize>;
73
74 #[allow(dead_code)]
76 async fn execute_batch(&self, sql: &str) -> DbResult<()>;
77
78 async fn query_one<T, F>(&self, query: Query, mapper: F) -> DbResult<T>
80 where
81 T: Send + 'static,
82 F: Fn(&Row) -> SqliteResult<T> + Send + 'static;
83
84 async fn query_optional<T, F>(&self, query: Query, mapper: F) -> DbResult<Option<T>>
86 where
87 T: Send + 'static,
88 F: Fn(&Row) -> SqliteResult<T> + Send + 'static;
89
90 async fn query_many<T, F>(&self, query: Query, mapper: F) -> DbResult<Vec<T>>
92 where
93 T: Send + 'static,
94 F: Fn(&Row) -> SqliteResult<T> + Send + 'static;
95
96 async fn transaction<F, T>(&self, f: F) -> DbResult<T>
98 where
99 F: FnOnce(&mut Transaction) -> DbResult<T> + Send + 'static,
100 T: Send + 'static;
101
102 async fn transaction_without_foreign_keys<F, T>(&self, f: F) -> DbResult<T>
110 where
111 F: FnOnce(&mut Transaction) -> DbResult<T> + Send + 'static,
112 T: Send + 'static;
113
114 async fn insert(&self, query: Query) -> DbResult<i64>;
119
120 fn execute_detached(&self, query: Query);
124}
125
126pub struct Transaction<'a> {
128 conn: &'a Connection,
129}
130
131impl<'a> Transaction<'a> {
132 pub fn new(conn: &'a Connection) -> Self {
133 Self { conn }
134 }
135
136 pub fn execute(&mut self, query: Query) -> DbResult<usize> {
137 execute_query(self.conn, query)
138 }
139
140 pub fn query_many<T, F>(&self, query: Query, mapper: F) -> DbResult<Vec<T>>
141 where
142 F: Fn(&Row) -> SqliteResult<T>,
143 {
144 query_many(self.conn, query, mapper)
145 }
146}
147
148type Job = Box<dyn FnOnce(&Connection) + Send>;
149
150struct Writer {
154 jobs: mpsc::Sender<Job>,
155}
156
157impl Writer {
158 fn spawn(conn: Arc<Mutex<Connection>>) -> Self {
159 let (jobs, queue) = mpsc::channel::<Job>();
160 std::thread::Builder::new()
161 .name("db-writer".into())
162 .spawn(move || {
163 for job in queue {
164 let conn = conn.lock_safe();
168 if std::panic::catch_unwind(AssertUnwindSafe(|| job(&conn))).is_err() {
173 error!("[db] a database job panicked; the writer carries on");
174 if !conn.is_autocommit() {
179 let _ = conn.execute_batch("ROLLBACK");
180 }
181 let _ = conn.execute_batch("PRAGMA foreign_keys = ON");
182 }
183 }
184 })
185 .expect("failed to spawn the database writer thread");
186 Self { jobs }
187 }
188
189 async fn run<T, F>(&self, f: F) -> DbResult<T>
190 where
191 T: Send + 'static,
192 F: FnOnce(&Connection) -> DbResult<T> + Send + 'static,
193 {
194 let (reply, result) = tokio::sync::oneshot::channel();
195 self.jobs
196 .send(Box::new(move |conn| {
197 let _ = reply.send(f(conn));
198 }))
199 .map_err(|_| "database writer has stopped".to_string())?;
200 result
201 .await
202 .map_err(|_| "database job panicked".to_string())?
203 }
204
205 fn run_detached(&self, f: impl FnOnce(&Connection) + Send + 'static) {
206 if self.jobs.send(Box::new(f)).is_err() {
207 debug!("[db] writer stopped; dropped a detached write");
208 }
209 }
210}
211
212struct ReaderPool {
215 idle: Mutex<Vec<Connection>>,
216 returned: Condvar,
217}
218
219impl ReaderPool {
220 fn run<T>(&self, f: impl FnOnce(&Connection) -> T) -> T {
222 let conn = {
223 let mut idle = self.idle.lock_safe();
224 loop {
225 if let Some(conn) = idle.pop() {
226 break conn;
227 }
228 idle = self
229 .returned
230 .wait(idle)
231 .unwrap_or_else(|poisoned| poisoned.into_inner());
232 }
233 };
234 let checkout = Checkout {
236 pool: self,
237 conn: Some(conn),
238 };
239 f(checkout.conn.as_ref().expect("checked-out connection"))
240 }
241}
242
243struct Checkout<'a> {
244 pool: &'a ReaderPool,
245 conn: Option<Connection>,
246}
247
248impl Drop for Checkout<'_> {
249 fn drop(&mut self) {
250 if let Some(conn) = self.conn.take() {
251 self.pool.idle.lock_safe().push(conn);
252 self.pool.returned.notify_one();
253 }
254 }
255}
256
257#[derive(Clone)]
260pub struct RusqliteService {
261 writer: Arc<Writer>,
262 readers: Option<Arc<ReaderPool>>,
263}
264
265impl RusqliteService {
266 #[cfg_attr(not(test), allow(dead_code))]
270 pub fn new(conn: Arc<Mutex<Connection>>) -> Self {
271 Self {
272 writer: Arc::new(Writer::spawn(conn)),
273 readers: None,
274 }
275 }
276
277 pub fn with_readers(conn: Arc<Mutex<Connection>>, readers: Vec<Connection>) -> Self {
280 let readers = (!readers.is_empty()).then(|| {
281 Arc::new(ReaderPool {
282 idle: Mutex::new(readers),
283 returned: Condvar::new(),
284 })
285 });
286 Self {
287 writer: Arc::new(Writer::spawn(conn)),
288 readers,
289 }
290 }
291
292 async fn read<T, F>(&self, f: F) -> DbResult<T>
293 where
294 T: Send + 'static,
295 F: FnOnce(&Connection) -> DbResult<T> + Send + 'static,
296 {
297 match &self.readers {
298 Some(pool) => {
299 let pool = Arc::clone(pool);
300 tokio::task::spawn_blocking(move || pool.run(f))
301 .await
302 .map_err(|e| format!("Task join error: {}", e))?
303 }
304 None => self.writer.run(f).await,
305 }
306 }
307}
308
309#[async_trait]
310impl DatabaseService for RusqliteService {
311 async fn execute(&self, query: Query) -> DbResult<usize> {
312 self.writer
313 .run(move |conn| execute_query(conn, query))
314 .await
315 }
316
317 async fn execute_batch(&self, sql: &str) -> DbResult<()> {
318 let sql = sql.to_string();
319 self.writer
320 .run(move |conn| {
321 conn.execute_batch(&sql)
322 .map_err(|e| format!("Execute batch failed: {}", e))
323 })
324 .await
325 }
326
327 async fn query_one<T, F>(&self, query: Query, mapper: F) -> DbResult<T>
328 where
329 T: Send + 'static,
330 F: Fn(&Row) -> SqliteResult<T> + Send + 'static,
331 {
332 self.read(move |conn| query_one(conn, query, mapper)).await
333 }
334
335 async fn query_optional<T, F>(&self, query: Query, mapper: F) -> DbResult<Option<T>>
336 where
337 T: Send + 'static,
338 F: Fn(&Row) -> SqliteResult<T> + Send + 'static,
339 {
340 self.read(move |conn| query_optional(conn, query, mapper))
341 .await
342 }
343
344 async fn query_many<T, F>(&self, query: Query, mapper: F) -> DbResult<Vec<T>>
345 where
346 T: Send + 'static,
347 F: Fn(&Row) -> SqliteResult<T> + Send + 'static,
348 {
349 self.read(move |conn| query_many(conn, query, mapper)).await
350 }
351
352 async fn transaction<F, T>(&self, f: F) -> DbResult<T>
353 where
354 F: FnOnce(&mut Transaction) -> DbResult<T> + Send + 'static,
355 T: Send + 'static,
356 {
357 self.writer.run(move |conn| run_transaction(conn, f)).await
358 }
359
360 async fn transaction_without_foreign_keys<F, T>(&self, f: F) -> DbResult<T>
361 where
362 F: FnOnce(&mut Transaction) -> DbResult<T> + Send + 'static,
363 T: Send + 'static,
364 {
365 self.writer
366 .run(move |conn| {
367 conn.execute_batch("PRAGMA foreign_keys = OFF")
368 .map_err(|e| format!("Failed to disable foreign keys: {}", e))?;
369 let result = run_transaction(conn, f);
370 if let Err(e) = conn.execute_batch("PRAGMA foreign_keys = ON") {
372 error!("[db] failed to re-enable foreign keys: {}", e);
373 }
374 result
375 })
376 .await
377 }
378
379 async fn insert(&self, query: Query) -> DbResult<i64> {
380 self.writer
381 .run(move |conn| {
382 execute_query(conn, query)?;
383 Ok(conn.last_insert_rowid())
384 })
385 .await
386 }
387
388 fn execute_detached(&self, query: Query) {
389 self.writer.run_detached(move |conn| {
390 if let Err(e) = execute_query(conn, query) {
391 debug!("[db] detached write failed: {}", e);
392 }
393 });
394 }
395}
396
397fn run_transaction<F, T>(conn: &Connection, f: F) -> DbResult<T>
398where
399 F: FnOnce(&mut Transaction) -> DbResult<T>,
400{
401 conn.execute("BEGIN TRANSACTION", [])
402 .map_err(|e| format!("Failed to begin transaction: {}", e))?;
403
404 let mut transaction = Transaction::new(conn);
405 match f(&mut transaction) {
406 Ok(value) => {
407 conn.execute("COMMIT", [])
408 .map_err(|e| format!("Failed to commit transaction: {}", e))?;
409 Ok(value)
410 }
411 Err(e) => {
412 conn.execute("ROLLBACK", [])
413 .map_err(|e| format!("Failed to rollback transaction: {}", e))?;
414 Err(e)
415 }
416 }
417}
418
419fn execute_query(conn: &Connection, query: Query) -> DbResult<usize> {
422 let params = convert_params(&query.params);
423 conn.execute(&query.sql, params_from_iter(params.iter()))
424 .map_err(|e| format!("Execute failed: {}", e))
425}
426
427fn query_one<T, F>(conn: &Connection, query: Query, mapper: F) -> DbResult<T>
428where
429 F: Fn(&Row) -> SqliteResult<T>,
430{
431 let params = convert_params(&query.params);
432 conn.query_row(&query.sql, params_from_iter(params.iter()), mapper)
433 .map_err(|e| format!("Query one failed: {}", e))
434}
435
436fn query_optional<T, F>(conn: &Connection, query: Query, mapper: F) -> DbResult<Option<T>>
437where
438 F: Fn(&Row) -> SqliteResult<T>,
439{
440 match query_one(conn, query, mapper) {
441 Ok(value) => Ok(Some(value)),
442 Err(e) if e.contains("Query returned no rows") || e.contains("QueryReturnedNoRows") => {
443 Ok(None)
444 }
445 Err(e) => Err(e),
446 }
447}
448
449fn query_many<T, F>(conn: &Connection, query: Query, mapper: F) -> DbResult<Vec<T>>
450where
451 F: Fn(&Row) -> SqliteResult<T>,
452{
453 let params = convert_params(&query.params);
454 let mut stmt = conn
455 .prepare(&query.sql)
456 .map_err(|e| format!("Prepare failed: {}", e))?;
457
458 let rows = stmt
459 .query_map(params_from_iter(params.iter()), mapper)
460 .map_err(|e| format!("Query map failed: {}", e))?;
461
462 rows.collect::<SqliteResult<Vec<T>>>()
463 .map_err(|e| format!("Collect failed: {}", e))
464}
465
466fn convert_params(params: &[QueryParam]) -> Vec<rusqlite::types::Value> {
468 params
469 .iter()
470 .map(|p| match p {
471 QueryParam::String(s) => rusqlite::types::Value::Text(s.clone()),
472 QueryParam::Int(i) => rusqlite::types::Value::Integer(*i as i64),
473 QueryParam::Int64(i) => rusqlite::types::Value::Integer(*i),
474 QueryParam::Float(f) => rusqlite::types::Value::Real(*f),
475 QueryParam::Bool(b) => rusqlite::types::Value::Integer(if *b { 1 } else { 0 }),
476 QueryParam::Null => rusqlite::types::Value::Null,
477 })
478 .collect()
479}
480
481#[cfg(test)]
483mod tests {
484 use super::*;
485
486 #[tokio::test]
487 async fn test_execute_query() {
488 let conn = Connection::open_in_memory().unwrap();
489 conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
490 .unwrap();
491
492 let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
493
494 let query = Query::with_params(
495 "INSERT INTO test (name) VALUES (?)",
496 vec![QueryParam::String("Alice".to_string())],
497 );
498
499 let rows = service.execute(query).await.unwrap();
500 assert_eq!(rows, 1);
501 }
502
503 #[tokio::test]
504 async fn test_query_one() {
505 let conn = Connection::open_in_memory().unwrap();
506 conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
507 .unwrap();
508 conn.execute("INSERT INTO test (name) VALUES ('Bob')", [])
509 .unwrap();
510
511 let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
512
513 let query = Query::new("SELECT name FROM test WHERE id = 1");
514 let name: String = service.query_one(query, |row| row.get(0)).await.unwrap();
515
516 assert_eq!(name, "Bob");
517 }
518
519 #[tokio::test]
520 async fn test_query_many() {
521 let conn = Connection::open_in_memory().unwrap();
522 conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
523 .unwrap();
524 conn.execute("INSERT INTO test (name) VALUES ('Alice')", [])
525 .unwrap();
526 conn.execute("INSERT INTO test (name) VALUES ('Bob')", [])
527 .unwrap();
528
529 let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
530
531 let query = Query::new("SELECT name FROM test ORDER BY id");
532 let names: Vec<String> = service.query_many(query, |row| row.get(0)).await.unwrap();
533
534 assert_eq!(names, vec!["Alice", "Bob"]);
535 }
536
537 #[tokio::test]
538 async fn test_query_optional() {
539 let conn = Connection::open_in_memory().unwrap();
540 conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
541 .unwrap();
542
543 let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
544
545 let query = Query::new("SELECT name FROM test WHERE id = 999");
546 let result: Option<String> = service
547 .query_optional(query, |row| row.get(0))
548 .await
549 .unwrap();
550
551 assert_eq!(result, None);
552 }
553
554 #[tokio::test]
555 async fn test_transaction() {
556 let conn = Connection::open_in_memory().unwrap();
557 conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
558 .unwrap();
559
560 let service = RusqliteService::new(Arc::new(Mutex::new(conn)));
561
562 let result = service
563 .transaction(|tx| {
564 tx.execute(Query::with_params(
565 "INSERT INTO test (name) VALUES (?)",
566 vec![QueryParam::String("Alice".to_string())],
567 ))?;
568 tx.execute(Query::with_params(
569 "INSERT INTO test (name) VALUES (?)",
570 vec![QueryParam::String("Bob".to_string())],
571 ))?;
572 Ok(())
573 })
574 .await;
575
576 assert!(result.is_ok());
577
578 let query = Query::new("SELECT COUNT(*) FROM test");
580 let count: i32 = service.query_one(query, |row| row.get(0)).await.unwrap();
581 assert_eq!(count, 2);
582 }
583 #[tokio::test]
595 async fn a_poisoned_connection_still_serves_queries() {
596 let conn = Arc::new(Mutex::new(Connection::open_in_memory().unwrap()));
597 {
598 let c = conn.lock_safe();
599 c.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY);")
600 .unwrap();
601 }
602
603 let poisoner = Arc::clone(&conn);
605 let hook = std::panic::take_hook();
606 std::panic::set_hook(Box::new(|_| {}));
607 let _ = std::thread::spawn(move || {
608 let _guard = poisoner.lock().unwrap();
609 panic!("a row mapper blew up while holding the connection");
610 })
611 .join();
612 std::panic::set_hook(hook);
613 assert!(conn.lock().is_err(), "the mutex should now be poisoned");
614
615 let service = RusqliteService::new(Arc::clone(&conn));
617 service
618 .execute(Query::new("INSERT INTO test (id) VALUES (1)"))
619 .await
620 .expect("execute must survive a poisoned connection");
621 let count: i32 = service
622 .query_one(Query::new("SELECT COUNT(*) FROM test"), |row| row.get(0))
623 .await
624 .expect("query_one must survive a poisoned connection");
625 assert_eq!(count, 1);
626 }
627}