Skip to main content

rust_ef_mysql/
provider.rs

1use crate::sql_generator::MySqlSqlGenerator;
2use crate::tls::MySqlTlsMode;
3use async_trait::async_trait;
4use rust_ef::error::{EFError, EFResult};
5#[cfg(feature = "tracing")]
6use rust_ef::provider::IAsyncConnection;
7use rust_ef::provider::{IDatabaseProvider, ISqlGenerator};
8
9pub struct MySqlProvider {
10    pool: sqlx::MySqlPool,
11    #[cfg(feature = "tracing")]
12    slow_query_threshold_ms: std::sync::atomic::AtomicU64,
13}
14
15impl MySqlProvider {
16    /// Creates a provider with TLS required (secure-by-default, v1.6+).
17    ///
18    /// Overrides any `ssl-mode` in the connection string. For plaintext
19    /// connections (local dev only), use [`MySqlProvider::new_insecure`].
20    pub async fn new(connection_string: &str) -> EFResult<Self> {
21        Self::new_with_tls(connection_string, MySqlTlsMode::Required).await
22    }
23
24    /// Creates a provider with TLS disabled (plaintext, local dev only).
25    pub async fn new_insecure(connection_string: &str) -> EFResult<Self> {
26        Self::new_with_tls(connection_string, MySqlTlsMode::Disabled).await
27    }
28
29    pub fn from_pool(pool: sqlx::MySqlPool) -> Self {
30        Self {
31            pool,
32            #[cfg(feature = "tracing")]
33            slow_query_threshold_ms: std::sync::atomic::AtomicU64::new(0),
34        }
35    }
36
37    /// Creates a lazy provider with TLS required (secure-by-default, v1.6+).
38    pub fn new_lazy(connection_string: &str) -> EFResult<Self> {
39        Self::new_lazy_with_tls(connection_string, MySqlTlsMode::Required)
40    }
41
42    /// Creates a lazy provider with TLS disabled (plaintext, local dev only).
43    pub fn new_lazy_insecure(connection_string: &str) -> EFResult<Self> {
44        Self::new_lazy_with_tls(connection_string, MySqlTlsMode::Disabled)
45    }
46
47    /// Creates a provider with explicit TLS configuration.
48    ///
49    /// The TLS mode overrides any `ssl-mode` in the connection string.
50    /// For CA certificate verification (`VerifyCa` / `VerifyIdentity`),
51    /// provide the CA certificate via the connection string's `ssl-ca`
52    /// parameter.
53    ///
54    /// For the secure-by-default convenience constructor, use
55    /// [`MySqlProvider::new`]. For plaintext (local dev), use
56    /// [`MySqlProvider::new_insecure`].
57    pub async fn new_with_tls(connection_string: &str, tls: MySqlTlsMode) -> EFResult<Self> {
58        let mut options: sqlx::mysql::MySqlConnectOptions = connection_string
59            .parse()
60            .map_err(|e| EFError::connection(format!("MySQL URL parse: {}", e)))?;
61        options = options.ssl_mode(tls.into());
62        let pool = sqlx::MySqlPool::connect_with(options)
63            .await
64            .map_err(|e| EFError::connection(format!("MySQL connection failed: {}", e)))?;
65        Ok(Self {
66            pool,
67            #[cfg(feature = "tracing")]
68            slow_query_threshold_ms: std::sync::atomic::AtomicU64::new(0),
69        })
70    }
71
72    /// Creates a lazy provider (no immediate connection) with explicit TLS.
73    ///
74    /// See [`MySqlProvider::new_with_tls`] for TLS mode details.
75    pub fn new_lazy_with_tls(connection_string: &str, tls: MySqlTlsMode) -> EFResult<Self> {
76        let mut options: sqlx::mysql::MySqlConnectOptions = connection_string
77            .parse()
78            .map_err(|e| EFError::connection(format!("MySQL URL parse: {}", e)))?;
79        options = options.ssl_mode(tls.into());
80        let pool = sqlx::MySqlPool::connect_lazy_with(options);
81        Ok(Self {
82            pool,
83            #[cfg(feature = "tracing")]
84            slow_query_threshold_ms: std::sync::atomic::AtomicU64::new(0),
85        })
86    }
87}
88
89#[async_trait]
90impl IDatabaseProvider for MySqlProvider {
91    fn sql_generator(&self) -> &'static dyn ISqlGenerator {
92        &MySqlSqlGenerator
93    }
94
95    async fn get_connection(&self) -> EFResult<Box<dyn rust_ef::provider::IAsyncConnection>> {
96        let _guard = rust_ef::observability::PoolAcquireGuard::new("MySQL");
97        let conn = self
98            .pool
99            .acquire()
100            .await
101            .map_err(|e| EFError::connection(format!("Pool acquire failed: {}", e)))?;
102        #[cfg_attr(not(feature = "tracing"), allow(unused_mut))]
103        let mut conn = Box::new(crate::connection::MySqlConnection::new(conn));
104        #[cfg(feature = "tracing")]
105        {
106            let ms = self
107                .slow_query_threshold_ms
108                .load(std::sync::atomic::Ordering::Relaxed);
109            if ms > 0 {
110                conn.set_slow_query_threshold(std::time::Duration::from_millis(ms));
111            }
112        }
113        Ok(conn)
114    }
115
116    async fn execute_migration_command(&self, sql: &str) -> EFResult<()> {
117        sqlx::query(sql)
118            .execute(&self.pool)
119            .await
120            .map_err(|e| EFError::migration(format!("Migration execution failed: {}", e)))?;
121        Ok(())
122    }
123
124    fn name(&self) -> &str {
125        "MySQL"
126    }
127
128    fn migration_dialect(&self) -> rust_ef::migration::MigrationDialect {
129        rust_ef::migration::MigrationDialect::MySql
130    }
131
132    #[cfg(feature = "tracing")]
133    fn set_slow_query_threshold(&self, threshold: std::time::Duration) {
134        self.slow_query_threshold_ms.store(
135            threshold.as_millis() as u64,
136            std::sync::atomic::Ordering::Relaxed,
137        );
138    }
139}
140
141impl std::fmt::Debug for MySqlProvider {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        f.debug_struct("MySqlProvider")
144            .field("name", &self.name())
145            .finish()
146    }
147}