use std::{collections::HashMap, path::Path};
use diesel::{sql_query, Connection, ConnectionError, RunQueryDsl, SqliteConnection};
use tracing::{debug, trace};
use crate::migration::{apply_migrations, migrate_json_to_jsonb, DbType};
pub struct DbConnection {
conn: SqliteConnection,
}
impl DbConnection {
pub fn open<P: AsRef<Path>>(path: P, db_type: DbType) -> Result<Self, ConnectionError> {
let path_str = path.as_ref().to_string_lossy();
debug!(path = %path_str, db_type = ?db_type, "opening database connection");
let mut conn = SqliteConnection::establish(&path_str)?;
trace!("database connection established");
sql_query("PRAGMA journal_mode = WAL;")
.execute(&mut conn)
.map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
trace!("WAL journal mode enabled");
apply_migrations(&mut conn, &db_type)
.map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
trace!("migrations applied");
if matches!(db_type, DbType::Core) {
migrate_json_to_jsonb(&mut conn, db_type)
.map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
trace!("JSON to JSONB migration completed");
}
debug!(path = %path_str, "database opened successfully");
Ok(Self {
conn,
})
}
pub fn open_readonly<P: AsRef<Path>>(path: P) -> Result<Self, ConnectionError> {
let path_str = format!(
"file:{}?mode=ro&immutable=1",
path.as_ref().to_string_lossy()
);
debug!(path = %path_str, "opening database in read-only mode");
let conn = SqliteConnection::establish(&path_str)?;
trace!("read-only database connection established");
debug!(path = %path_str, "read-only database opened successfully");
Ok(Self {
conn,
})
}
pub fn open_without_migrations<P: AsRef<Path>>(path: P) -> Result<Self, ConnectionError> {
let path_str = path.as_ref().to_string_lossy();
debug!(path = %path_str, "opening database without migrations");
let mut conn = SqliteConnection::establish(&path_str)?;
trace!("database connection established");
sql_query("PRAGMA journal_mode = WAL;")
.execute(&mut conn)
.map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
trace!("WAL journal mode enabled");
debug!(path = %path_str, "database opened successfully");
Ok(Self {
conn,
})
}
pub fn open_metadata<P: AsRef<Path>>(path: P) -> Result<Self, ConnectionError> {
let path_str = path.as_ref().to_string_lossy();
debug!(path = %path_str, "opening metadata database");
let mut conn = SqliteConnection::establish(&path_str)?;
trace!("metadata database connection established");
sql_query("PRAGMA journal_mode = WAL;")
.execute(&mut conn)
.map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
trace!("WAL journal mode enabled");
migrate_json_to_jsonb(&mut conn, DbType::Metadata)
.map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
trace!("JSON to JSONB migration completed");
debug!(path = %path_str, "metadata database opened successfully");
Ok(Self {
conn,
})
}
pub fn conn(&mut self) -> &mut SqliteConnection {
&mut self.conn
}
}
impl std::ops::Deref for DbConnection {
type Target = SqliteConnection;
fn deref(&self) -> &Self::Target {
&self.conn
}
}
impl std::ops::DerefMut for DbConnection {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.conn
}
}
pub struct DatabaseManager {
core: DbConnection,
metadata: HashMap<String, DbConnection>,
}
impl DatabaseManager {
pub fn new<P: AsRef<Path>>(base_dir: P) -> Result<Self, ConnectionError> {
let base = base_dir.as_ref();
debug!(base_dir = %base.display(), "initializing database manager");
let core_path = base.join("core.db");
let core = DbConnection::open(&core_path, DbType::Core)?;
debug!("database manager initialized");
Ok(Self {
core,
metadata: HashMap::new(),
})
}
pub fn add_metadata_db<P: AsRef<Path>>(
&mut self,
repo_name: &str,
path: P,
) -> Result<(), ConnectionError> {
debug!(repo_name = repo_name, "adding metadata database");
let conn = DbConnection::open_metadata(path)?;
self.metadata.insert(repo_name.to_string(), conn);
trace!(repo_name = repo_name, "metadata database added to manager");
Ok(())
}
pub fn core(&mut self) -> &mut DbConnection {
&mut self.core
}
pub fn metadata(&mut self, repo_name: &str) -> Option<&mut DbConnection> {
self.metadata.get_mut(repo_name)
}
pub fn all_metadata(&mut self) -> impl Iterator<Item = (&String, &mut DbConnection)> {
self.metadata.iter_mut()
}
pub fn metadata_names(&self) -> impl Iterator<Item = &String> {
self.metadata.keys()
}
pub fn remove_metadata_db(&mut self, repo_name: &str) -> Option<DbConnection> {
debug!(repo_name = repo_name, "removing metadata database");
self.metadata.remove(repo_name)
}
}