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}