darkwing/database/connection.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
//! MySQL database connection management module.
//!
//! This module provides functionality for establishing and managing connections
//! to a MySQL database using connection pooling. It handles connection
//! initialization and pooling configuration using SQLx.
//!
//! The connection pool automatically handles connection lifecycle,
//! reconnection, and connection distribution across multiple requests.
use anyhow::{Context, Ok};
use sqlx::mysql::MySqlPoolOptions;
use sqlx::{MySql, Pool};
use tracing::info;
/// Type alias for the MySQL connection pool.
///
/// This pool manages multiple connections to the MySQL database,
/// handling connection lifecycle and distribution automatically.
pub type ConnectionPool = Pool<MySql>;
/// Represents a MySQL database connection manager.
///
/// The `Database` struct maintains a connection pool to MySQL and provides
/// methods for establishing connections with configurable parameters. It uses
/// SQLx's connection pooling to efficiently manage database connections.
#[derive(Debug, Clone)]
pub struct Database {
/// The connection pool for MySQL connections.
pub pool: ConnectionPool,
}
impl Database {
/// Establishes a new connection pool to MySQL with the specified
/// configuration.
///
/// This method initializes a new connection pool with the given parameters
/// and verifies that the connection can be established successfully.
///
/// # Arguments
///
/// * `connection_string` - The MySQL connection URL (e.g.,
/// "mysql://user:password@localhost:3306/database")
/// * `connection_pool_size` - Maximum number of connections in the pool
///
/// # Returns
///
/// Returns a `Result` containing the `Database` instance if successful, or an
/// error if the connection could not be established.
///
/// # Examples
///
/// ```rust,no_run
/// use darkwing::database::Database;
///
/// async fn example() -> anyhow::Result<()> {
/// let db = Database::connect(
/// "mysql://user:password@localhost:3306/database",
/// 5
/// ).await?;
/// Ok(())
/// }
/// ```
pub async fn connect(
connection_string: &str,
connection_pool_size: u32,
) -> anyhow::Result<Self> {
info!("Connecting to database...");
let pool = MySqlPoolOptions::new()
.max_connections(connection_pool_size)
.connect(connection_string)
.await
.context("error while initializing the database connection pool")?;
info!("Connected to database");
Ok(Self { pool })
}
}
#[cfg(test)]
mod tests {
use super::*;
use testcontainers::runners::AsyncRunner;
use testcontainers_modules::mysql::Mysql;
#[tokio::test]
async fn test_database_connection() -> anyhow::Result<()> {
let mysql_container = Mysql::default().start().await?;
let host = mysql_container.get_host().await?.to_string();
let port = mysql_container.get_host_port_ipv4(3306).await?;
let connection_string = format!("mysql://root:@{host}:{port}/test");
let db = Database::connect(&connection_string, 5).await?;
// Verify we can execute a simple query
sqlx::query("SELECT 1")
.execute(&db.pool)
.await
.context("Failed to execute test query")?;
Ok(())
}
#[tokio::test]
async fn test_invalid_connection() {
let result = Database::connect("mysql://invalid", 5).await;
assert!(result.is_err());
}
}