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());
  }
}