1pub mod models;
6pub mod schema;
7
8use duckdb::Connection;
9use std::path::Path;
10use std::sync::{Arc, Mutex};
11
12pub struct Database {
14 conn: Arc<Mutex<Connection>>,
15 path: Option<String>,
16}
17
18impl Database {
19 pub fn open<P: AsRef<Path>>(path: P) -> crate::Result<Self> {
21 let path_str = path.as_ref().to_string_lossy().to_string();
22 let conn = Connection::open(&path_str)
23 .map_err(|e| crate::error::GarminError::Database(e.to_string()))?;
24
25 let db = Self {
26 conn: Arc::new(Mutex::new(conn)),
27 path: Some(path_str),
28 };
29
30 db.migrate()?;
32
33 Ok(db)
34 }
35
36 pub fn in_memory() -> crate::Result<Self> {
38 let conn = Connection::open_in_memory()
39 .map_err(|e| crate::error::GarminError::Database(e.to_string()))?;
40
41 let db = Self {
42 conn: Arc::new(Mutex::new(conn)),
43 path: None,
44 };
45
46 db.migrate()?;
47
48 Ok(db)
49 }
50
51 pub fn migrate(&self) -> crate::Result<()> {
53 let conn = self.conn.lock().unwrap();
54 schema::migrate(&conn)
55 }
56
57 pub fn execute(&self, sql: &str) -> crate::Result<usize> {
59 let conn = self.conn.lock().unwrap();
60 conn.execute(sql, [])
61 .map_err(|e| crate::error::GarminError::Database(e.to_string()))
62 }
63
64 pub fn execute_params<P: duckdb::Params>(&self, sql: &str, params: P) -> crate::Result<usize> {
66 let conn = self.conn.lock().unwrap();
67 conn.execute(sql, params)
68 .map_err(|e| crate::error::GarminError::Database(e.to_string()))
69 }
70
71 pub fn connection(&self) -> Arc<Mutex<Connection>> {
74 Arc::clone(&self.conn)
75 }
76
77 pub fn path(&self) -> Option<&str> {
79 self.path.as_deref()
80 }
81}
82
83impl Clone for Database {
84 fn clone(&self) -> Self {
85 Self {
86 conn: Arc::clone(&self.conn),
87 path: self.path.clone(),
88 }
89 }
90}
91
92pub fn default_db_path() -> crate::Result<String> {
94 let data_dir = crate::config::data_dir()?;
95 Ok(data_dir.join("garmin.duckdb").to_string_lossy().to_string())
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_in_memory_database() {
104 let db = Database::in_memory().expect("Failed to create in-memory db");
105 assert!(db.path().is_none());
106 }
107
108 #[test]
109 fn test_execute_query() {
110 let db = Database::in_memory().expect("Failed to create db");
111 let result = db.execute("SELECT 1");
112 assert!(result.is_ok());
113 }
114}