zirv-db-sqlx 0.3.0

A convenient wrapper around sqlx: global connection-pool management, tunable pool settings, graceful shutdown, and transaction helpers for MySQL, PostgreSQL, and SQLite.
Documentation
//! # zirv-db-sqlx
//!
//! A small, convenient wrapper around [sqlx](https://github.com/launchbadge/sqlx)
//! that standardises three things across a service:
//!
//! - **Global connection pool** managed in a process-wide [`OnceLock`](std::sync::OnceLock),
//!   initialized once with [`init_db_pool!`] and read anywhere with [`get_db_pool!`].
//! - **Tunable pool settings** read from the `database.*` configuration namespace via
//!   [`zirv-config`](https://docs.rs/zirv-config) (see [`db::PoolSettings`]).
//! - **Transaction helpers** ([`start_transaction!`], [`commit_transaction!`],
//!   [`rollback_transaction!`]) that remove the usual boilerplate.
//!
//! The crate also exposes [`close_db_pool`] for graceful shutdown.
//!
//! ## Choosing a database backend
//!
//! Enable **exactly one** of the `mysql` (default), `postgres`, or `sqlite` features,
//! together with **exactly one** runtime/TLS feature (`runtime-tokio-native-tls`,
//! the default, or `runtime-tokio-rustls`). For a non-default backend, disable
//! default features:
//!
//! ```toml
//! # PostgreSQL with rustls
//! zirv-db-sqlx = { version = "0.3", default-features = false, features = ["postgres", "runtime-tokio-rustls"] }
//! ```
//!
//! ## Example
//!
//! ```no_run
//! use zirv_config::register_config;
//! use zirv_db_sqlx::{init_db_pool, get_db_pool, close_db_pool};
//!
//! #[derive(serde::Serialize)]
//! struct DatabaseConfig {
//!     url: String,
//!     max_connections: u32,
//! }
//!
//! # async fn run() -> Result<(), sqlx::Error> {
//! register_config!("database", DatabaseConfig {
//!     url: "mysql://user:password@localhost/db".to_string(),
//!     max_connections: 10,
//! });
//!
//! init_db_pool!();
//!
//! let pool = get_db_pool!();
//! // ... use `pool` with sqlx queries ...
//!
//! close_db_pool().await; // graceful shutdown
//! # Ok(())
//! # }
//! ```

pub mod db;

pub use db::{Db, DbPool, PoolSettings, close_db_pool};

// --- Compile-time feature validation -------------------------------------------------
//
// Exactly one database backend must be selected. These guards turn an otherwise
// cryptic type or trait error into a clear, actionable message.

#[cfg(not(any(feature = "mysql", feature = "postgres", feature = "sqlite")))]
compile_error!(
    "zirv-db-sqlx: no database backend selected. Enable exactly one of the \
     `mysql`, `postgres`, or `sqlite` features."
);

#[cfg(any(
    all(feature = "mysql", feature = "postgres"),
    all(feature = "mysql", feature = "sqlite"),
    all(feature = "postgres", feature = "sqlite"),
))]
compile_error!(
    "zirv-db-sqlx: multiple database backends selected. Enable exactly ONE of the \
     `mysql`, `postgres`, or `sqlite` features (use `default-features = false` for \
     a non-default backend)."
);

/// Initializes the global database pool.
///
/// Thin wrapper over [`db::init_db_pool`] that awaits it. Call once, early in your
/// application (typically in `main`), after registering the `database` config block.
///
/// # Example
/// ```no_run
/// use zirv_db_sqlx::init_db_pool;
/// # async fn run() {
/// init_db_pool!();
/// # }
/// ```
#[macro_export]
macro_rules! init_db_pool {
    () => {
        $crate::db::init_db_pool().await
    };
}

/// Retrieves a reference to the global database pool.
///
/// Thin wrapper over [`db::get_db_pool`]. Panics if [`init_db_pool!`] has not run.
///
/// # Example
/// ```no_run
/// use zirv_db_sqlx::get_db_pool;
/// # fn run() {
/// let pool = get_db_pool!();
/// // Use `pool` for database operations...
/// # }
/// ```
#[macro_export]
macro_rules! get_db_pool {
    () => {
        $crate::db::get_db_pool()
    };
}

/// Begins a new transaction on the global pool.
///
/// On failure the error is logged with [`tracing`] and returned, so the calling
/// function must return a `Result<_, sqlx::Error>`.
///
/// # Example
/// ```no_run
/// use zirv_db_sqlx::{start_transaction, commit_transaction};
///
/// async fn perform_db_operations() -> Result<(), sqlx::Error> {
///     let mut tx = start_transaction!();
///     // Execute operations within the transaction...
///     commit_transaction!(tx);
///     Ok(())
/// }
/// ```
#[macro_export]
macro_rules! start_transaction {
    () => {
        match $crate::db::get_db_pool().begin().await {
            Ok(tx) => tx,
            Err(e) => {
                ::tracing::error!(error = ?e, "Failed to start transaction");
                return Err(e);
            }
        }
    };
}

/// Commits an active transaction, logging and returning the error on failure.
///
/// # Example
/// ```no_run
/// use zirv_db_sqlx::{start_transaction, commit_transaction};
///
/// async fn perform_db_operations() -> Result<(), sqlx::Error> {
///     let mut tx = start_transaction!();
///     // Execute operations within the transaction...
///     commit_transaction!(tx);
///     Ok(())
/// }
/// ```
#[macro_export]
macro_rules! commit_transaction {
    ($tx:expr) => {
        match $tx.commit().await {
            Ok(_) => (),
            Err(e) => {
                ::tracing::error!(error = ?e, "Failed to commit transaction");
                return Err(e);
            }
        }
    };
}

/// Rolls back an active transaction, logging and returning the error on failure.
///
/// # Example
/// ```no_run
/// use zirv_db_sqlx::{start_transaction, rollback_transaction};
///
/// async fn perform_db_operations() -> Result<(), sqlx::Error> {
///     let mut tx = start_transaction!();
///     // Execute operations within the transaction...
///     rollback_transaction!(tx);
///     Ok(())
/// }
/// ```
#[macro_export]
macro_rules! rollback_transaction {
    ($tx:expr) => {
        match $tx.rollback().await {
            Ok(_) => (),
            Err(e) => {
                ::tracing::error!(error = ?e, "Failed to rollback transaction");
                return Err(e);
            }
        }
    };
}