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}