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    database::{Connection, Drivers},
40    Model, QueryBuilder,
41};
42
43// ============================================================================
44// Transaction Struct
45// ============================================================================
46
47/// A wrapper around a SQLx transaction.
48///
49/// Provides a way to execute multiple queries atomically. If any query fails,
50/// the transaction can be rolled back. If all succeed, it can be committed.
51///
52/// # Type Parameters
53///
54/// * `'a` - The lifetime of the database connection source
55///
56/// # Fields
57///
58/// * `tx` - The underlying SQLx transaction
59/// * `driver` - The database driver type (for query syntax handling)
60#[derive(Debug)]
61pub struct Transaction<'a> {
62    pub(crate) tx: sqlx::Transaction<'a, sqlx::Any>,
63    pub(crate) driver: Drivers,
64}
65
66// ============================================================================
67// Connection Implementation
68// ============================================================================
69
70/// Implementation of Connection for a mutable reference to a Transaction.
71///
72/// Allows the `QueryBuilder` to use a transaction for executing queries.
73/// Supports generic borrow lifetimes to allow multiple operations within
74/// the same transaction scope.
75impl<'a> Connection for &mut Transaction<'a> {
76    type Exec<'c> = &'c mut sqlx::AnyConnection
77    where
78        Self: 'c;
79
80    fn driver(&self) -> Drivers {
81        self.driver
82    }
83
84    fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
85        &mut *self.tx
86    }
87}
88
89// ============================================================================
90// Transaction Implementation
91// ============================================================================
92
93impl<'a> Transaction<'a> {
94    // ========================================================================
95    // Query Building
96    // ========================================================================
97
98    /// Starts building a query within this transaction.
99    ///
100    /// This method creates a new `QueryBuilder` that will execute its queries
101    /// as part of this transaction.
102    ///
103    /// # Type Parameters
104    ///
105    /// * `T` - The Model type to query.
106    ///
107    /// # Returns
108    ///
109    /// A new `QueryBuilder` instance bound to this transaction.
110    ///
111    /// # Example
112    ///
113    /// ```rust,ignore
114    /// let mut tx = db.begin().await?;
115    ///
116    /// // These operations are part of the transaction
117    /// tx.model::<User>().insert(&user).await?;
118    /// tx.model::<Post>().insert(&post).await?;
119    ///
120    /// tx.commit().await?;
121    /// ```
122    pub fn model<T: Model + Send + Sync + Unpin>(&mut self) -> QueryBuilder<'a, T, &mut Self> {
123        // Get active column names from the model
124        let active_columns = T::active_columns();
125        let mut columns: Vec<String> = Vec::with_capacity(active_columns.capacity());
126
127        // Convert column names to snake_case and strip 'r#' prefix if present
128        for col in active_columns {
129            columns.push(col.strip_prefix("r#").unwrap_or(col).to_snake_case());
130        }
131
132        // Create and return the query builder
133        QueryBuilder::new(self, T::table_name(), T::columns(), columns)
134    }
135
136    // ========================================================================
137    // Transaction Control
138    // ========================================================================
139
140    /// Commits the transaction.
141    ///
142    /// Persists all changes made during the transaction to the database.
143    /// This consumes the `Transaction` instance.
144    ///
145    /// # Returns
146    ///
147    /// * `Ok(())` - Transaction committed successfully
148    /// * `Err(sqlx::Error)` - Database error during commit
149    pub async fn commit(self) -> Result<(), sqlx::Error> {
150        self.tx.commit().await
151    }
152
153    /// Rolls back the transaction.
154    ///
155    /// Reverts all changes made during the transaction. This happens automatically
156    /// if the `Transaction` is dropped without being committed, but this method
157    /// allows for explicit rollback.
158    ///
159    /// # Returns
160    ///
161    /// * `Ok(())` - Transaction rolled back successfully
162    /// * `Err(sqlx::Error)` - Database error during rollback
163    pub async fn rollback(self) -> Result<(), sqlx::Error> {
164        self.tx.rollback().await
165    }
166}