1use std::{collections::HashMap, path::Path};
10
11use diesel::{sql_query, Connection, ConnectionError, RunQueryDsl, SqliteConnection};
12use tracing::{debug, trace};
13
14use crate::migration::{apply_migrations, migrate_json_to_jsonb, DbType};
15
16pub struct DbConnection {
18 conn: SqliteConnection,
19}
20
21impl DbConnection {
22 pub fn open<P: AsRef<Path>>(path: P, db_type: DbType) -> Result<Self, ConnectionError> {
33 let path_str = path.as_ref().to_string_lossy();
34 debug!(path = %path_str, db_type = ?db_type, "opening database connection");
35
36 let mut conn = SqliteConnection::establish(&path_str)?;
37 trace!("database connection established");
38
39 sql_query("PRAGMA journal_mode = WAL;")
40 .execute(&mut conn)
41 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
42 trace!("WAL journal mode enabled");
43
44 apply_migrations(&mut conn, &db_type)
45 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
46 trace!("migrations applied");
47
48 if matches!(db_type, DbType::Core) {
51 migrate_json_to_jsonb(&mut conn, db_type)
52 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
53 trace!("JSON to JSONB migration completed");
54 }
55
56 debug!(path = %path_str, "database opened successfully");
57 Ok(Self {
58 conn,
59 })
60 }
61
62 pub fn open_readonly<P: AsRef<Path>>(path: P) -> Result<Self, ConnectionError> {
70 let path_str = format!(
71 "file:{}?mode=ro&immutable=1",
72 path.as_ref().to_string_lossy()
73 );
74 debug!(path = %path_str, "opening database in read-only mode");
75
76 let conn = SqliteConnection::establish(&path_str)?;
77 trace!("read-only database connection established");
78
79 debug!(path = %path_str, "read-only database opened successfully");
80 Ok(Self {
81 conn,
82 })
83 }
84
85 pub fn open_without_migrations<P: AsRef<Path>>(path: P) -> Result<Self, ConnectionError> {
89 let path_str = path.as_ref().to_string_lossy();
90 debug!(path = %path_str, "opening database without migrations");
91
92 let mut conn = SqliteConnection::establish(&path_str)?;
93 trace!("database connection established");
94
95 sql_query("PRAGMA journal_mode = WAL;")
97 .execute(&mut conn)
98 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
99 trace!("WAL journal mode enabled");
100
101 debug!(path = %path_str, "database opened successfully");
102 Ok(Self {
103 conn,
104 })
105 }
106
107 pub fn open_metadata<P: AsRef<Path>>(path: P) -> Result<Self, ConnectionError> {
114 let path_str = path.as_ref().to_string_lossy();
115 debug!(path = %path_str, "opening metadata database");
116
117 let mut conn = SqliteConnection::establish(&path_str)?;
118 trace!("metadata database connection established");
119
120 sql_query("PRAGMA journal_mode = WAL;")
122 .execute(&mut conn)
123 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
124 trace!("WAL journal mode enabled");
125
126 migrate_json_to_jsonb(&mut conn, DbType::Metadata)
128 .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
129 trace!("JSON to JSONB migration completed");
130
131 debug!(path = %path_str, "metadata database opened successfully");
132 Ok(Self {
133 conn,
134 })
135 }
136
137 pub fn conn(&mut self) -> &mut SqliteConnection {
139 &mut self.conn
140 }
141}
142
143impl std::ops::Deref for DbConnection {
144 type Target = SqliteConnection;
145
146 fn deref(&self) -> &Self::Target {
147 &self.conn
148 }
149}
150
151impl std::ops::DerefMut for DbConnection {
152 fn deref_mut(&mut self) -> &mut Self::Target {
153 &mut self.conn
154 }
155}
156
157pub struct DatabaseManager {
179 core: DbConnection,
181 metadata: HashMap<String, DbConnection>,
183}
184
185impl DatabaseManager {
186 pub fn new<P: AsRef<Path>>(base_dir: P) -> Result<Self, ConnectionError> {
197 let base = base_dir.as_ref();
198 debug!(base_dir = %base.display(), "initializing database manager");
199
200 let core_path = base.join("core.db");
201
202 let core = DbConnection::open(&core_path, DbType::Core)?;
203
204 debug!("database manager initialized");
205 Ok(Self {
206 core,
207 metadata: HashMap::new(),
208 })
209 }
210
211 pub fn add_metadata_db<P: AsRef<Path>>(
222 &mut self,
223 repo_name: &str,
224 path: P,
225 ) -> Result<(), ConnectionError> {
226 debug!(repo_name = repo_name, "adding metadata database");
227 let conn = DbConnection::open_metadata(path)?;
228 self.metadata.insert(repo_name.to_string(), conn);
229 trace!(repo_name = repo_name, "metadata database added to manager");
230 Ok(())
231 }
232
233 pub fn core(&mut self) -> &mut DbConnection {
235 &mut self.core
236 }
237
238 pub fn metadata(&mut self, repo_name: &str) -> Option<&mut DbConnection> {
242 self.metadata.get_mut(repo_name)
243 }
244
245 pub fn all_metadata(&mut self) -> impl Iterator<Item = (&String, &mut DbConnection)> {
247 self.metadata.iter_mut()
248 }
249
250 pub fn metadata_names(&self) -> impl Iterator<Item = &String> {
252 self.metadata.keys()
253 }
254
255 pub fn remove_metadata_db(&mut self, repo_name: &str) -> Option<DbConnection> {
257 debug!(repo_name = repo_name, "removing metadata database");
258 self.metadata.remove(repo_name)
259 }
260}