Skip to main content

zirv_db_sqlx/
lib.rs

1//! # zirv-db-sqlx
2//!
3//! A small, convenient wrapper around [sqlx](https://github.com/launchbadge/sqlx)
4//! that standardises three things across a service:
5//!
6//! - **Global connection pool** managed in a process-wide [`OnceLock`](std::sync::OnceLock),
7//!   initialized once with [`init_db_pool!`] and read anywhere with [`get_db_pool!`].
8//! - **Tunable pool settings** read from the `database.*` configuration namespace via
9//!   [`zirv-config`](https://docs.rs/zirv-config) (see [`db::PoolSettings`]).
10//! - **Transaction helpers** ([`start_transaction!`], [`commit_transaction!`],
11//!   [`rollback_transaction!`]) that remove the usual boilerplate.
12//!
13//! The crate also exposes [`close_db_pool`] for graceful shutdown.
14//!
15//! ## Choosing a database backend
16//!
17//! Enable **exactly one** of the `mysql` (default), `postgres`, or `sqlite` features,
18//! together with **exactly one** runtime/TLS feature (`runtime-tokio-native-tls`,
19//! the default, or `runtime-tokio-rustls`). For a non-default backend, disable
20//! default features:
21//!
22//! ```toml
23//! # PostgreSQL with rustls
24//! zirv-db-sqlx = { version = "0.3", default-features = false, features = ["postgres", "runtime-tokio-rustls"] }
25//! ```
26//!
27//! ## Example
28//!
29//! ```no_run
30//! use zirv_config::register_config;
31//! use zirv_db_sqlx::{init_db_pool, get_db_pool, close_db_pool};
32//!
33//! #[derive(serde::Serialize)]
34//! struct DatabaseConfig {
35//!     url: String,
36//!     max_connections: u32,
37//! }
38//!
39//! # async fn run() -> Result<(), sqlx::Error> {
40//! register_config!("database", DatabaseConfig {
41//!     url: "mysql://user:password@localhost/db".to_string(),
42//!     max_connections: 10,
43//! });
44//!
45//! init_db_pool!();
46//!
47//! let pool = get_db_pool!();
48//! // ... use `pool` with sqlx queries ...
49//!
50//! close_db_pool().await; // graceful shutdown
51//! # Ok(())
52//! # }
53//! ```
54
55pub mod db;
56
57pub use db::{Db, DbPool, PoolSettings, close_db_pool};
58
59// --- Compile-time feature validation -------------------------------------------------
60//
61// Exactly one database backend must be selected. These guards turn an otherwise
62// cryptic type or trait error into a clear, actionable message.
63
64#[cfg(not(any(feature = "mysql", feature = "postgres", feature = "sqlite")))]
65compile_error!(
66    "zirv-db-sqlx: no database backend selected. Enable exactly one of the \
67     `mysql`, `postgres`, or `sqlite` features."
68);
69
70#[cfg(any(
71    all(feature = "mysql", feature = "postgres"),
72    all(feature = "mysql", feature = "sqlite"),
73    all(feature = "postgres", feature = "sqlite"),
74))]
75compile_error!(
76    "zirv-db-sqlx: multiple database backends selected. Enable exactly ONE of the \
77     `mysql`, `postgres`, or `sqlite` features (use `default-features = false` for \
78     a non-default backend)."
79);
80
81/// Initializes the global database pool.
82///
83/// Thin wrapper over [`db::init_db_pool`] that awaits it. Call once, early in your
84/// application (typically in `main`), after registering the `database` config block.
85///
86/// # Example
87/// ```no_run
88/// use zirv_db_sqlx::init_db_pool;
89/// # async fn run() {
90/// init_db_pool!();
91/// # }
92/// ```
93#[macro_export]
94macro_rules! init_db_pool {
95    () => {
96        $crate::db::init_db_pool().await
97    };
98}
99
100/// Retrieves a reference to the global database pool.
101///
102/// Thin wrapper over [`db::get_db_pool`]. Panics if [`init_db_pool!`] has not run.
103///
104/// # Example
105/// ```no_run
106/// use zirv_db_sqlx::get_db_pool;
107/// # fn run() {
108/// let pool = get_db_pool!();
109/// // Use `pool` for database operations...
110/// # }
111/// ```
112#[macro_export]
113macro_rules! get_db_pool {
114    () => {
115        $crate::db::get_db_pool()
116    };
117}
118
119/// Begins a new transaction on the global pool.
120///
121/// On failure the error is logged with [`tracing`] and returned, so the calling
122/// function must return a `Result<_, sqlx::Error>`.
123///
124/// # Example
125/// ```no_run
126/// use zirv_db_sqlx::{start_transaction, commit_transaction};
127///
128/// async fn perform_db_operations() -> Result<(), sqlx::Error> {
129///     let mut tx = start_transaction!();
130///     // Execute operations within the transaction...
131///     commit_transaction!(tx);
132///     Ok(())
133/// }
134/// ```
135#[macro_export]
136macro_rules! start_transaction {
137    () => {
138        match $crate::db::get_db_pool().begin().await {
139            Ok(tx) => tx,
140            Err(e) => {
141                ::tracing::error!(error = ?e, "Failed to start transaction");
142                return Err(e);
143            }
144        }
145    };
146}
147
148/// Commits an active transaction, logging and returning the error on failure.
149///
150/// # Example
151/// ```no_run
152/// use zirv_db_sqlx::{start_transaction, commit_transaction};
153///
154/// async fn perform_db_operations() -> Result<(), sqlx::Error> {
155///     let mut tx = start_transaction!();
156///     // Execute operations within the transaction...
157///     commit_transaction!(tx);
158///     Ok(())
159/// }
160/// ```
161#[macro_export]
162macro_rules! commit_transaction {
163    ($tx:expr) => {
164        match $tx.commit().await {
165            Ok(_) => (),
166            Err(e) => {
167                ::tracing::error!(error = ?e, "Failed to commit transaction");
168                return Err(e);
169            }
170        }
171    };
172}
173
174/// Rolls back an active transaction, logging and returning the error on failure.
175///
176/// # Example
177/// ```no_run
178/// use zirv_db_sqlx::{start_transaction, rollback_transaction};
179///
180/// async fn perform_db_operations() -> Result<(), sqlx::Error> {
181///     let mut tx = start_transaction!();
182///     // Execute operations within the transaction...
183///     rollback_transaction!(tx);
184///     Ok(())
185/// }
186/// ```
187#[macro_export]
188macro_rules! rollback_transaction {
189    ($tx:expr) => {
190        match $tx.rollback().await {
191            Ok(_) => (),
192            Err(e) => {
193                ::tracing::error!(error = ?e, "Failed to rollback transaction");
194                return Err(e);
195            }
196        }
197    };
198}