bottle_orm/
transaction.rs

1//! # Transaction Module
2//!
3//! This module provides the transaction management functionality for Bottle ORM.
4//! It allows executing multiple database operations atomically, ensuring data consistency.
5//!
6//! ## Features
7//!
8//! - **Atomic Operations**: Group multiple queries into a single unit of work
9//! - **Automatic Rollback**: Transactions are automatically rolled back if dropped without commit
10//! - **Driver Agnostic**: Works consistently across PostgreSQL, MySQL, and SQLite
11//! - **Fluent API**: Integrated with `QueryBuilder` for seamless usage
12//!
13//! ## Example Usage
14//!
15//! ```rust,ignore
16//! use bottle_orm::Database;
17//!
18//! let mut tx = db.begin().await?;
19//!
20//! // Operations within transaction
21//! tx.model::<User>().insert(&user).await?;
22//! tx.model::<Post>().insert(&post).await?;
23//!
24//! // Commit changes
25//! tx.commit().await?;
26//! ```
27
28// ============================================================================
29// External Crate Imports
30// ============================================================================
31
32use heck::ToSnakeCase;
33
34// ============================================================================
35// Internal Crate Imports
36// ============================================================================
37
38use crate::{
39    any_struct::AnyImpl,
40    database::{Connection, Drivers},
41    Model, QueryBuilder,
42};
43use sqlx::{any::AnyRow, FromRow};
44
45// ============================================================================
46// Transaction Struct
47// ============================================================================
48
49/// A wrapper around a SQLx transaction.
50///
51/// Provides a way to execute multiple queries atomically. If any query fails,
52/// the transaction can be rolled back. If all succeed, it can be committed.
53///
54/// # Type Parameters
55///
56/// * `'a` - The lifetime of the database connection source
57///
58/// # Fields
59///
60/// * `tx` - The underlying SQLx transaction
61/// * `driver` - The database driver type (for query syntax handling)
62#[derive(Debug)]
63pub struct Transaction<'a> {
64    pub(crate) tx: sqlx::Transaction<'a, sqlx::Any>,
65    pub(crate) driver: Drivers,
66}
67
68// ============================================================================
69// Connection Implementation
70// ============================================================================
71
72/// Implementation of Connection for a Transaction.
73///
74/// Allows the `QueryBuilder` to use a transaction for executing queries.
75/// Supports generic borrow lifetimes to allow multiple operations within
76/// the same transaction scope.
77impl<'a> Connection for Transaction<'a> {
78    type Exec<'c> = &'c mut sqlx::AnyConnection
79    where
80        Self: 'c;
81
82    fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
83        &mut *self.tx
84    }
85}
86
87/// Implementation of Connection for a mutable reference to Transaction.
88impl<'a, 'b> Connection for &'a mut Transaction<'b> {
89    type Exec<'c> = &'c mut sqlx::AnyConnection
90    where
91        Self: 'c;
92
93    fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
94        (**self).executor()
95    }
96}
97
98// ============================================================================
99// Transaction Implementation
100// ============================================================================
101
102impl<'a> Transaction<'a> {
103    // ========================================================================
104    // Query Building
105    // ========================================================================
106
107    /// Starts building a query within this transaction.
108    ///
109    /// This method creates a new `QueryBuilder` that will execute its queries
110    /// as part of this transaction.
111    ///
112    /// # Type Parameters
113    ///
114    /// * `T` - The Model type to query.
115    ///
116    /// # Returns
117    ///
118    /// A new `QueryBuilder` instance bound to this transaction.
119    ///
120    /// # Example
121    ///
122    /// ```rust,ignore
123    /// let mut tx = db.begin().await?;
124    ///
125    /// // These operations are part of the transaction
126    /// tx.model::<User>().insert(&user).await?;
127    /// tx.model::<Post>().insert(&post).await?;
128    ///
129    /// tx.commit().await?;
130    /// ```
131    pub fn model<T: Model + Send + Sync + Unpin>(&mut self) -> QueryBuilder<'a, T, &mut sqlx::Transaction<'a, sqlx::Any>> {
132        // Get active column names from the model
133        let active_columns = T::active_columns();
134        let mut columns: Vec<String> = Vec::with_capacity(active_columns.capacity());
135
136        // Convert column names to snake_case and strip 'r#' prefix if present
137        for col in active_columns {
138            columns.push(col.strip_prefix("r#").unwrap_or(col).to_snake_case());
139        }
140
141        // Create and return the query builder
142        QueryBuilder::new(&mut self.tx, self.driver, T::table_name(), T::columns(), columns)
143    }
144
145    // ========================================================================
146    // Transaction Control
147    // ========================================================================
148
149    /// Commits the transaction.
150    ///
151    /// Persists all changes made during the transaction to the database.
152    /// This consumes the `Transaction` instance.
153    ///
154    /// # Returns
155    ///
156    /// * `Ok(())` - Transaction committed successfully
157    /// * `Err(sqlx::Error)` - Database error during commit
158    pub async fn commit(self) -> Result<(), sqlx::Error> {
159        self.tx.commit().await
160    }
161
162    /// Rolls back the transaction.
163    ///
164    /// Reverts all changes made during the transaction. This happens automatically
165    /// if the `Transaction` is dropped without being committed, but this method
166    /// allows for explicit rollback.
167    ///
168    /// # Returns
169    ///
170    /// * `Ok(())` - Transaction rolled back successfully
171    /// * `Err(sqlx::Error)` - Database error during rollback
172    pub async fn rollback(self) -> Result<(), sqlx::Error> {
173        self.tx.rollback().await
174    }
175}