1use std::{collections::HashMap, path::Path};
11
12use diesel::{sql_query, Connection, ConnectionError, RunQueryDsl, SqliteConnection};
13use tracing::{debug, trace};
14
15use crate::migration::{apply_migrations, migrate_json_to_jsonb, DbType};
16
17pub struct DbConnection {
19 conn: SqliteConnection,
20}
21
22impl DbConnection {
23 pub fn open<P: AsRef<Path>>(path: P, db_type: DbType) -> Result<Self, ConnectionError> {
34 let path_str = path.as_ref().to_string_lossy();
35 debug!(path = %path_str, db_type = ?db_type, "opening database connection");
36
37 let mut conn = SqliteConnection::establish(&path_str)?;
38 trace!("database connection established");
39
40 sql_query("PRAGMA journal_mode = WAL;")
41 .execute(&mut conn)
42 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
43 trace!("WAL journal mode enabled");
44
45 apply_migrations(&mut conn, &db_type)
46 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
47 trace!("migrations applied");
48
49 if matches!(db_type, DbType::Core | DbType::Nest) {
52 migrate_json_to_jsonb(&mut conn, db_type)
53 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
54 trace!("JSON to JSONB migration completed");
55 }
56
57 debug!(path = %path_str, "database opened successfully");
58 Ok(Self {
59 conn,
60 })
61 }
62
63 pub fn open_without_migrations<P: AsRef<Path>>(path: P) -> Result<Self, ConnectionError> {
67 let path_str = path.as_ref().to_string_lossy();
68 debug!(path = %path_str, "opening database without migrations");
69
70 let mut conn = SqliteConnection::establish(&path_str)?;
71 trace!("database connection established");
72
73 sql_query("PRAGMA journal_mode = WAL;")
75 .execute(&mut conn)
76 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
77 trace!("WAL journal mode enabled");
78
79 debug!(path = %path_str, "database opened successfully");
80 Ok(Self {
81 conn,
82 })
83 }
84
85 pub fn open_metadata<P: AsRef<Path>>(path: P) -> Result<Self, ConnectionError> {
92 let path_str = path.as_ref().to_string_lossy();
93 debug!(path = %path_str, "opening metadata database");
94
95 let mut conn = SqliteConnection::establish(&path_str)?;
96 trace!("metadata database connection established");
97
98 sql_query("PRAGMA journal_mode = WAL;")
100 .execute(&mut conn)
101 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
102 trace!("WAL journal mode enabled");
103
104 migrate_json_to_jsonb(&mut conn, DbType::Metadata)
106 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
107 trace!("JSON to JSONB migration completed");
108
109 debug!(path = %path_str, "metadata database opened successfully");
110 Ok(Self {
111 conn,
112 })
113 }
114
115 pub fn conn(&mut self) -> &mut SqliteConnection {
117 &mut self.conn
118 }
119}
120
121impl std::ops::Deref for DbConnection {
122 type Target = SqliteConnection;
123
124 fn deref(&self) -> &Self::Target {
125 &self.conn
126 }
127}
128
129impl std::ops::DerefMut for DbConnection {
130 fn deref_mut(&mut self) -> &mut Self::Target {
131 &mut self.conn
132 }
133}
134
135pub struct DatabaseManager {
158 core: DbConnection,
160 metadata: HashMap<String, DbConnection>,
162 nests: DbConnection,
164}
165
166impl DatabaseManager {
167 pub fn new<P: AsRef<Path>>(base_dir: P) -> Result<Self, ConnectionError> {
179 let base = base_dir.as_ref();
180 debug!(base_dir = %base.display(), "initializing database manager");
181
182 let core_path = base.join("core.db");
183 let nests_path = base.join("nests.db");
184
185 let core = DbConnection::open(&core_path, DbType::Core)?;
186 let nests = DbConnection::open(&nests_path, DbType::Nest)?;
187
188 debug!("database manager initialized with core and nests databases");
189 Ok(Self {
190 core,
191 metadata: HashMap::new(),
192 nests,
193 })
194 }
195
196 pub fn add_metadata_db<P: AsRef<Path>>(
207 &mut self,
208 repo_name: &str,
209 path: P,
210 ) -> Result<(), ConnectionError> {
211 debug!(repo_name = repo_name, "adding metadata database");
212 let conn = DbConnection::open_metadata(path)?;
213 self.metadata.insert(repo_name.to_string(), conn);
214 trace!(repo_name = repo_name, "metadata database added to manager");
215 Ok(())
216 }
217
218 pub fn core(&mut self) -> &mut DbConnection {
220 &mut self.core
221 }
222
223 pub fn metadata(&mut self, repo_name: &str) -> Option<&mut DbConnection> {
227 self.metadata.get_mut(repo_name)
228 }
229
230 pub fn all_metadata(&mut self) -> impl Iterator<Item = (&String, &mut DbConnection)> {
232 self.metadata.iter_mut()
233 }
234
235 pub fn nests(&mut self) -> &mut DbConnection {
237 &mut self.nests
238 }
239
240 pub fn metadata_names(&self) -> impl Iterator<Item = &String> {
242 self.metadata.keys()
243 }
244
245 pub fn remove_metadata_db(&mut self, repo_name: &str) -> Option<DbConnection> {
247 debug!(repo_name = repo_name, "removing metadata database");
248 self.metadata.remove(repo_name)
249 }
250}