Skip to main content

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, RawQuery},
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 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 Transaction<'a> {
76    type Exec<'c>
77        = &'c mut sqlx::AnyConnection
78    where
79        Self: 'c;
80
81    fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
82        &mut *self.tx
83    }
84}
85
86/// Implementation of Connection for a mutable reference to Transaction.
87impl<'a, 'b> Connection for &'a mut Transaction<'b> {
88    type Exec<'c>
89        = &'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>(
132        &mut self,
133    ) -> QueryBuilder<'a, T, &mut sqlx::Transaction<'a, sqlx::Any>> {
134        // Get active column names from the model
135        let active_columns = T::active_columns();
136        let mut columns: Vec<String> = Vec::with_capacity(active_columns.capacity());
137
138        // Convert column names to snake_case and strip 'r#' prefix if present
139        for col in active_columns {
140            columns.push(col.strip_prefix("r#").unwrap_or(col).to_snake_case());
141        }
142
143        // Create and return the query builder
144        QueryBuilder::new(&mut self.tx, self.driver, T::table_name(), T::columns(), columns)
145    }
146
147    /// Creates a raw SQL query builder attached to this transaction.
148    ///
149    /// Allows executing raw SQL queries that participate in the current transaction.
150    ///
151    /// # Arguments
152    ///
153    /// * `sql` - The raw SQL query string
154    ///
155    /// # Returns
156    ///
157    /// A `RawQuery` builder bound to this transaction.
158    ///
159    /// # Example
160    ///
161    /// ```rust,ignore
162    /// let mut tx = db.begin().await?;
163    ///
164    /// // Execute raw SQL inside transaction
165    /// tx.raw("INSERT INTO logs (msg) VALUES ($1)")
166    ///     .bind("Transaction started")
167    ///     .execute()
168    ///     .await?;
169    ///
170    /// tx.commit().await?;
171    /// ```
172    pub fn raw<'b>(&'b mut self, sql: &'b str) -> RawQuery<'b, &'b mut Transaction<'a>> {
173        RawQuery::new(self, sql)
174    }
175
176    // ========================================================================
177    // Transaction Control
178    // ========================================================================
179
180    /// Commits the transaction.
181    ///
182    /// Persists all changes made during the transaction to the database.
183    /// This consumes the `Transaction` instance.
184    ///
185    /// # Returns
186    ///
187    /// * `Ok(())` - Transaction committed successfully
188    /// * `Err(sqlx::Error)` - Database error during commit
189    pub async fn commit(self) -> Result<(), sqlx::Error> {
190        self.tx.commit().await
191    }
192
193    /// Rolls back the transaction.
194    ///
195    /// Reverts all changes made during the transaction. This happens automatically
196    /// if the `Transaction` is dropped without being committed, but this method
197    /// allows for explicit rollback.
198    ///
199    /// # Returns
200    ///
201    /// * `Ok(())` - Transaction rolled back successfully
202    /// * `Err(sqlx::Error)` - Database error during rollback
203    pub async fn rollback(self) -> Result<(), sqlx::Error> {
204        self.tx.rollback().await
205    }
206}