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}