rs-zero 0.2.6

Rust-first microservice framework inspired by go-zero engineering practices
Documentation
#[cfg(any(feature = "db-sqlite", feature = "db-postgres", feature = "db-mysql"))]
use sqlx::Pool as SqlxPool;
#[cfg(feature = "db-mysql")]
use sqlx::{MySql, mysql::MySqlPoolOptions};
#[cfg(feature = "db-postgres")]
use sqlx::{Postgres, postgres::PgPoolOptions};
#[cfg(feature = "db-sqlite")]
use sqlx::{Sqlite, sqlite::SqlitePoolOptions};

use crate::db::{DatabaseConfig, DatabaseError, DatabaseKind, DatabaseResult};

/// SQLite database pool.
#[cfg(feature = "db-sqlite")]
pub type SqliteDatabasePool = SqlxPool<Sqlite>;

/// PostgreSQL database pool.
#[cfg(feature = "db-postgres")]
pub type PostgresDatabasePool = SqlxPool<Postgres>;

/// MySQL database pool.
#[cfg(feature = "db-mysql")]
pub type MySqlDatabasePool = SqlxPool<MySql>;

/// Database pool used by the enabled default adapter.
#[cfg(feature = "db-sqlite")]
pub type DatabasePool = SqliteDatabasePool;

/// Database pool used when PostgreSQL is the enabled adapter.
#[cfg(all(not(feature = "db-sqlite"), feature = "db-postgres"))]
pub type DatabasePool = PostgresDatabasePool;

/// Database pool used when MySQL is the only enabled adapter.
#[cfg(all(
    not(feature = "db-sqlite"),
    not(feature = "db-postgres"),
    feature = "db-mysql"
))]
pub type DatabasePool = MySqlDatabasePool;

/// Connects a SQLx pool for the default enabled adapter.
#[cfg(feature = "db-sqlite")]
pub async fn connect_pool(config: &DatabaseConfig) -> DatabaseResult<DatabasePool> {
    connect_sqlite_pool(config).await
}

/// Connects a SQLx pool for the default enabled adapter.
#[cfg(all(not(feature = "db-sqlite"), feature = "db-postgres"))]
pub async fn connect_pool(config: &DatabaseConfig) -> DatabaseResult<DatabasePool> {
    connect_postgres_pool(config).await
}

/// Connects a SQLx pool for the default enabled adapter.
#[cfg(all(
    not(feature = "db-sqlite"),
    not(feature = "db-postgres"),
    feature = "db-mysql"
))]
pub async fn connect_pool(config: &DatabaseConfig) -> DatabaseResult<DatabasePool> {
    connect_mysql_pool(config).await
}

/// Connects a SQLite SQLx pool.
#[cfg(feature = "db-sqlite")]
pub async fn connect_sqlite_pool(config: &DatabaseConfig) -> DatabaseResult<SqliteDatabasePool> {
    if config.kind != DatabaseKind::Sqlite {
        return Err(DatabaseError::Unsupported(
            "sqlite database kind required".to_string(),
        ));
    }
    Ok(SqlitePoolOptions::new()
        .max_connections(config.max_connections)
        .acquire_timeout(config.connect_timeout)
        .connect(&config.url)
        .await?)
}

/// Connects a PostgreSQL SQLx pool.
#[cfg(feature = "db-postgres")]
pub async fn connect_postgres_pool(
    config: &DatabaseConfig,
) -> DatabaseResult<PostgresDatabasePool> {
    if config.kind != DatabaseKind::Postgres {
        return Err(DatabaseError::Unsupported(
            "postgres database kind required".to_string(),
        ));
    }
    Ok(PgPoolOptions::new()
        .max_connections(config.max_connections)
        .acquire_timeout(config.connect_timeout)
        .connect(&config.url)
        .await?)
}

/// Connects a MySQL SQLx pool.
#[cfg(feature = "db-mysql")]
pub async fn connect_mysql_pool(config: &DatabaseConfig) -> DatabaseResult<MySqlDatabasePool> {
    if config.kind != DatabaseKind::Mysql {
        return Err(DatabaseError::Unsupported(
            "mysql database kind required".to_string(),
        ));
    }
    Ok(MySqlPoolOptions::new()
        .max_connections(config.max_connections)
        .acquire_timeout(config.connect_timeout)
        .connect(&config.url)
        .await?)
}

/// Runs a lightweight health check on the default pool type.
pub async fn health_check(pool: &DatabasePool) -> DatabaseResult<()> {
    sqlx::query("SELECT 1").execute(pool).await?;
    Ok(())
}

/// Runs a lightweight health check and records SQL metrics.
#[cfg(feature = "observability")]
pub async fn health_check_with_metrics(
    pool: &DatabasePool,
    metrics: &crate::observability::MetricsRegistry,
) -> DatabaseResult<()> {
    crate::observability::observe_sql_query(
        Some(metrics),
        default_database_kind_label(),
        "db",
        "health_check",
        "select",
        health_check(pool),
    )
    .await
}

/// Runs a lightweight PostgreSQL health check.
#[cfg(feature = "db-postgres")]
pub async fn health_check_postgres(pool: &PostgresDatabasePool) -> DatabaseResult<()> {
    sqlx::query("SELECT 1").execute(pool).await?;
    Ok(())
}

/// Runs a lightweight MySQL health check.
#[cfg(feature = "db-mysql")]
pub async fn health_check_mysql(pool: &MySqlDatabasePool) -> DatabaseResult<()> {
    sqlx::query("SELECT 1").execute(pool).await?;
    Ok(())
}

#[cfg(all(feature = "observability", feature = "db-sqlite"))]
fn default_database_kind_label() -> &'static str {
    "sqlite"
}

#[cfg(all(
    feature = "observability",
    not(feature = "db-sqlite"),
    feature = "db-postgres"
))]
fn default_database_kind_label() -> &'static str {
    "postgres"
}

#[cfg(all(
    feature = "observability",
    not(feature = "db-sqlite"),
    not(feature = "db-postgres"),
    feature = "db-mysql"
))]
fn default_database_kind_label() -> &'static str {
    "mysql"
}

#[cfg(test)]
mod tests {
    use crate::db::{DatabaseConfig, DatabaseKind};
    #[cfg(feature = "db-sqlite")]
    use crate::db::{connect_pool, health_check};

    #[cfg(feature = "db-sqlite")]
    #[tokio::test]
    async fn sqlite_pool_passes_health_check() {
        let pool = connect_pool(&DatabaseConfig::default())
            .await
            .expect("pool");
        health_check(&pool).await.expect("health");
    }

    #[test]
    fn postgres_config_uses_postgres_kind() {
        let config = DatabaseConfig::postgres("postgres://localhost/app");
        assert_eq!(config.kind, DatabaseKind::Postgres);
    }

    #[test]
    fn mysql_config_uses_mysql_kind() {
        let config = DatabaseConfig::mysql("mysql://localhost/app");
        assert_eq!(config.kind, DatabaseKind::Mysql);
    }
}