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