1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//! # sqlx-transaction-manager
//!
//! A type-safe transaction management wrapper for SQLx with automatic commit/rollback.
//!
//! ## Features
//!
//! - **Automatic Rollback**: Transactions automatically roll back on drop if not explicitly committed
//! - **Type-Safe**: Transaction boundaries are enforced at compile time
//! - **Ergonomic API**: Simple `with_transaction` function for common use cases
//! - **Nested Transactions**: Support for savepoints to simulate nested transactions
//! - **Zero Runtime Overhead**: Thin wrapper around SQLx's native transaction support
//!
//! ## Quick Start
//!
//! Add to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! sqlx = { version = "0.8", features = ["mysql", "runtime-tokio"] }
//! sqlx-transaction-manager = "0.1"
//! ```
//!
//! ## Examples
//!
//! ### Basic Transaction
//!
//! ```rust,no_run
//! use sqlx::MySqlPool;
//! use sqlx_transaction_manager::with_transaction;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! let pool = MySqlPool::connect("mysql://localhost/test").await?;
//!
//! with_transaction(&pool, |tx| {
//! Box::pin(async move {
//! sqlx::query("INSERT INTO users (name) VALUES (?)")
//! .bind("Alice")
//! .execute(tx.as_executor())
//! .await?;
//! Ok::<_, sqlx::Error>(())
//! })
//! }).await?;
//! # Ok(())
//! # }
//! ```
//!
//! ### Multiple Operations
//!
//! ```rust,no_run
//! use sqlx::MySqlPool;
//! use sqlx_transaction_manager::with_transaction;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! # let pool = MySqlPool::connect("mysql://localhost/test").await?;
//! let user_id = with_transaction(&pool, |tx| {
//! Box::pin(async move {
//! // Insert user
//! let result = sqlx::query("INSERT INTO users (name) VALUES (?)")
//! .bind("Bob")
//! .execute(tx.as_executor())
//! .await?;
//!
//! let user_id = result.last_insert_id() as i64;
//!
//! // Insert profile (same transaction)
//! sqlx::query("INSERT INTO profiles (user_id, bio) VALUES (?, ?)")
//! .bind(user_id)
//! .bind("Software Developer")
//! .execute(tx.as_executor())
//! .await?;
//!
//! // Both operations commit together
//! Ok::<_, sqlx::Error>(user_id)
//! })
//! }).await?;
//!
//! println!("Created user with ID: {}", user_id);
//! # Ok(())
//! # }
//! ```
//!
//! ### Using with sqlx-named-bind
//!
//! This library works seamlessly with `sqlx-named-bind`:
//!
//! ```rust,no_run
//! use sqlx::MySqlPool;
//! use sqlx_transaction_manager::with_transaction;
//! use sqlx_named_bind::PreparedQuery;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! # let pool = MySqlPool::connect("mysql://localhost/test").await?;
//! with_transaction(&pool, |tx| {
//! Box::pin(async move {
//! let name = "Charlie".to_string();
//! let age = 30;
//!
//! let mut query = PreparedQuery::new(
//! "INSERT INTO users (name, age) VALUES (:name, :age)",
//! |q, key| match key {
//! ":name" => q.bind(name.clone()),
//! ":age" => q.bind(age),
//! _ => q,
//! }
//! )?;
//!
//! query.execute(tx.as_executor()).await?;
//! Ok::<_, sqlx_named_bind::Error>(())
//! })
//! }).await?;
//! # Ok(())
//! # }
//! ```
//!
//! ### Nested Transactions (Savepoints)
//!
//! ```rust,no_run
//! use sqlx::MySqlPool;
//! use sqlx_transaction_manager::{with_transaction, with_nested_transaction};
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! # let pool = MySqlPool::connect("mysql://localhost/test").await?;
//! with_transaction(&pool, |tx| {
//! Box::pin(async move {
//! // Main transaction operations
//! sqlx::query("INSERT INTO users (name) VALUES (?)")
//! .bind("David")
//! .execute(tx.as_executor())
//! .await?;
//!
//! // Nested transaction with savepoint
//! let _ = with_nested_transaction(tx, |nested_tx| {
//! Box::pin(async move {
//! sqlx::query("INSERT INTO audit_log (action) VALUES (?)")
//! .bind("User created")
//! .execute(nested_tx.as_executor())
//! .await?;
//! Ok::<_, sqlx::Error>(())
//! })
//! }).await; // If this fails, only the audit log is rolled back
//!
//! Ok::<_, sqlx::Error>(())
//! })
//! }).await?;
//! # Ok(())
//! # }
//! ```
//!
//! ### Manual Transaction Control
//!
//! For more control, use `TransactionContext` directly:
//!
//! ```rust,no_run
//! use sqlx::MySqlPool;
//! use sqlx_transaction_manager::TransactionContext;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! # let pool = MySqlPool::connect("mysql://localhost/test").await?;
//! let mut tx = TransactionContext::begin(&pool).await?;
//!
//! sqlx::query("INSERT INTO users (name) VALUES (?)")
//! .bind("Eve")
//! .execute(tx.as_executor())
//! .await?;
//!
//! // Explicitly commit
//! tx.commit().await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Error Handling
//!
//! When an error occurs inside a transaction, it automatically rolls back:
//!
//! ```rust,no_run
//! use sqlx::MySqlPool;
//! use sqlx_transaction_manager::with_transaction;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! # let pool = MySqlPool::connect("mysql://localhost/test").await?;
//! let result = with_transaction(&pool, |tx| {
//! Box::pin(async move {
//! sqlx::query("INSERT INTO users (name) VALUES (?)")
//! .bind("Frank")
//! .execute(tx.as_executor())
//! .await?;
//!
//! // This will cause a rollback
//! return Err(sqlx::Error::RowNotFound);
//!
//! #[allow(unreachable_code)]
//! Ok::<_, sqlx::Error>(())
//! })
//! }).await;
//!
//! assert!(result.is_err());
//! // The INSERT was rolled back, "Frank" is not in the database
//! # Ok(())
//! # }
//! ```
//!
//! ## How It Works
//!
//! 1. **TransactionContext**: Wraps SQLx's `Transaction` and tracks its state
//! 2. **Automatic Cleanup**: Uncommitted transactions are rolled back on drop
//! 3. **Type Safety**: Consumed transactions can't be reused (enforced at compile time)
//! 4. **Executor Access**: Provides `&mut MySqlConnection` for use with SQLx queries
//!
//! ## Limitations
//!
//! - Currently only supports MySQL (PostgreSQL and SQLite support planned)
//! - Nested transactions use savepoints (MySQL limitation)
//! - Error type is `sqlx_transaction_manager::Error` (wraps `sqlx::Error`)
//!
//! ## License
//!
//! Licensed under either of Apache License, Version 2.0 or MIT license at your option.
pub use TransactionContext;
pub use ;
pub use ;
pub use ;
/// Convenience re-exports for common use cases