Skip to main content

ferro_rs/database/
connection.rs

1//! Database connection management
2
3use sea_orm::{ConnectOptions, Database, DatabaseConnection};
4use std::sync::Arc;
5use std::time::Duration;
6
7use crate::database::config::DatabaseConfig;
8use crate::error::FrameworkError;
9
10/// Wrapper around SeaORM's DatabaseConnection
11///
12/// This provides a clonable, thread-safe connection that can be stored
13/// in the application container and shared across requests.
14///
15/// # Example
16///
17/// ```rust,ignore
18/// let conn = DbConnection::connect(&config).await?;
19///
20/// // Use with SeaORM queries
21/// let users = User::find().all(conn.inner()).await?;
22/// ```
23#[derive(Clone)]
24pub struct DbConnection {
25    inner: Arc<DatabaseConnection>,
26}
27
28impl DbConnection {
29    /// Create a new database connection from config
30    ///
31    /// This establishes a connection pool using the provided configuration.
32    /// For SQLite databases, this will automatically create the database file
33    /// if it doesn't exist.
34    pub async fn connect(config: &DatabaseConfig) -> Result<Self, FrameworkError> {
35        // For SQLite, ensure the database file can be created
36        let url = if config.url.starts_with("sqlite://") {
37            // Extract the file path from the URL
38            let path = config.url.trim_start_matches("sqlite://");
39            let path = path.trim_start_matches("./");
40
41            // Don't apply to in-memory databases
42            if path != ":memory:" && !path.starts_with(":memory:") {
43                // Create parent directories if needed
44                if let Some(parent) = std::path::Path::new(path).parent() {
45                    if !parent.as_os_str().is_empty() {
46                        std::fs::create_dir_all(parent).ok();
47                    }
48                }
49
50                // Touch the file to create it if it doesn't exist
51                if !std::path::Path::new(path).exists() {
52                    std::fs::File::create(path).ok();
53                }
54            }
55
56            // Use the file path format that SQLite prefers with create mode
57            format!("sqlite:{path}?mode=rwc")
58        } else {
59            config.url.clone()
60        };
61
62        let mut opt = ConnectOptions::new(&url);
63        opt.max_connections(config.max_connections)
64            .min_connections(config.min_connections)
65            .connect_timeout(Duration::from_secs(config.connect_timeout))
66            .sqlx_logging(config.logging);
67
68        let conn = Database::connect(opt)
69            .await
70            .map_err(|e| FrameworkError::database(e.to_string()))?;
71
72        Ok(Self {
73            inner: Arc::new(conn),
74        })
75    }
76
77    /// Get a reference to the underlying SeaORM connection
78    ///
79    /// Use this when you need to execute raw SeaORM queries.
80    ///
81    /// # Example
82    ///
83    /// ```rust,ignore
84    /// let conn = DB::connection()?;
85    /// let users = User::find()
86    ///     .filter(user::Column::Active.eq(true))
87    ///     .all(conn.inner())
88    ///     .await?;
89    /// ```
90    pub fn inner(&self) -> &DatabaseConnection {
91        &self.inner
92    }
93
94    /// Get a reference to the database connection (short alias for `inner()`)
95    ///
96    /// Use this when passing to SeaORM queries. This provides a cleaner API
97    /// than using the `Deref` implementation with `&*`.
98    ///
99    /// # Example
100    ///
101    /// ```rust,ignore
102    /// let users = User::find().all(self.db.conn()).await?;
103    /// ```
104    pub fn conn(&self) -> &DatabaseConnection {
105        &self.inner
106    }
107
108    /// Check if the connection is closed
109    pub fn is_closed(&self) -> bool {
110        // SeaORM doesn't expose this directly, but we can check via ping
111        false
112    }
113}
114
115impl AsRef<DatabaseConnection> for DbConnection {
116    fn as_ref(&self) -> &DatabaseConnection {
117        &self.inner
118    }
119}
120
121impl std::ops::Deref for DbConnection {
122    type Target = DatabaseConnection;
123
124    fn deref(&self) -> &Self::Target {
125        &self.inner
126    }
127}