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}