Skip to main content

metis_core/dal/database/
mod.rs

1pub mod configuration_repository;
2pub mod models;
3pub mod repository;
4pub mod schema;
5
6use diesel::prelude::*;
7use diesel::sqlite::SqliteConnection;
8use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
9
10pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("src/dal/database/migrations");
11
12/// Database connection and migration management
13pub struct Database {
14    connection_string: String,
15}
16
17/// Configure SQLite connection for better concurrency
18///
19/// Sets pragmas to reduce "database is locked" errors when multiple
20/// connections access the database simultaneously.
21fn configure_connection(connection: &mut SqliteConnection) -> Result<(), diesel::result::Error> {
22    // Wait up to 5 seconds when encountering a lock instead of failing immediately
23    diesel::sql_query("PRAGMA busy_timeout = 5000").execute(connection)?;
24
25    // Use Write-Ahead Logging for better concurrent read/write performance
26    // WAL allows readers and writers to operate simultaneously
27    diesel::sql_query("PRAGMA journal_mode = WAL").execute(connection)?;
28
29    // Synchronous NORMAL is safe with WAL and faster than FULL
30    diesel::sql_query("PRAGMA synchronous = NORMAL").execute(connection)?;
31
32    Ok(())
33}
34
35impl Database {
36    /// Create a new database connection and run migrations
37    ///
38    /// # Arguments
39    /// * `connection_string` - SQLite connection string (e.g., ":memory:", "database.db", "file:database.db?mode=rw")
40    pub fn new(connection_string: &str) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
41        // Run migrations once to ensure the database is set up
42        let mut connection = SqliteConnection::establish(connection_string)?;
43        configure_connection(&mut connection)?;
44        connection.run_pending_migrations(MIGRATIONS)?;
45
46        Ok(Self {
47            connection_string: connection_string.to_string(),
48        })
49    }
50
51    /// Get a new connection to the database
52    pub fn get_connection(
53        &self,
54    ) -> Result<SqliteConnection, Box<dyn std::error::Error + Send + Sync>> {
55        let mut connection = SqliteConnection::establish(&self.connection_string)?;
56        configure_connection(&mut connection)?;
57
58        // For in-memory databases, we need to run migrations on each connection
59        // since each connection is a separate database
60        if self.connection_string == ":memory:" {
61            connection.run_pending_migrations(MIGRATIONS)?;
62        }
63
64        Ok(connection)
65    }
66
67    /// Get a document repository with a new connection
68    pub fn repository(
69        &self,
70    ) -> Result<repository::DocumentRepository, Box<dyn std::error::Error + Send + Sync>> {
71        let connection = self.get_connection()?;
72        Ok(repository::DocumentRepository::new(connection))
73    }
74
75    /// Get a document repository (consumes the database) - kept for compatibility
76    pub fn into_repository(self) -> repository::DocumentRepository {
77        let connection = self.get_connection().expect("Failed to get connection");
78        repository::DocumentRepository::new(connection)
79    }
80
81    /// Get a configuration repository with a new connection
82    pub fn configuration_repository(
83        &self,
84    ) -> Result<
85        configuration_repository::ConfigurationRepository,
86        Box<dyn std::error::Error + Send + Sync>,
87    > {
88        let connection = self.get_connection()?;
89        Ok(configuration_repository::ConfigurationRepository::new(
90            connection,
91        ))
92    }
93}