sqlx_core/transaction.rs
1use std::fmt::{self, Debug, Formatter};
2use std::future::{self, Future};
3use std::ops::{Deref, DerefMut};
4
5use futures_core::future::BoxFuture;
6
7use crate::database::Database;
8use crate::error::Error;
9use crate::pool::MaybePoolConnection;
10use crate::sql_str::{AssertSqlSafe, SqlSafeStr, SqlStr};
11
12/// Generic management of database transactions.
13///
14/// This trait should not be used, except when implementing [`Connection`].
15pub trait TransactionManager {
16 type Database: Database;
17
18 /// Begin a new transaction or establish a savepoint within the active transaction.
19 ///
20 /// If this is a new transaction, `statement` may be used instead of the
21 /// default "BEGIN" statement.
22 ///
23 /// If we are already inside a transaction and `statement.is_some()`, then
24 /// `Error::InvalidSavePoint` is returned without running any statements.
25 fn begin(
26 conn: &mut <Self::Database as Database>::Connection,
27 statement: Option<SqlStr>,
28 ) -> impl Future<Output = Result<(), Error>> + Send + '_;
29
30 /// Commit the active transaction or release the most recent savepoint.
31 fn commit(
32 conn: &mut <Self::Database as Database>::Connection,
33 ) -> impl Future<Output = Result<(), Error>> + Send + '_;
34
35 /// Abort the active transaction or restore from the most recent savepoint.
36 fn rollback(
37 conn: &mut <Self::Database as Database>::Connection,
38 ) -> impl Future<Output = Result<(), Error>> + Send + '_;
39
40 /// Starts to abort the active transaction or restore from the most recent snapshot.
41 fn start_rollback(conn: &mut <Self::Database as Database>::Connection);
42
43 /// Returns the current transaction depth.
44 ///
45 /// Transaction depth indicates the level of nested transactions:
46 /// - Level 0: No active transaction.
47 /// - Level 1: A transaction is active.
48 /// - Level 2 or higher: A transaction is active and one or more SAVEPOINTs have been created within it.
49 fn get_transaction_depth(conn: &<Self::Database as Database>::Connection) -> usize;
50}
51
52/// An in-progress database transaction or savepoint.
53///
54/// A transaction starts with a call to [`Pool::begin`] or [`Connection::begin`].
55///
56/// A transaction should end with a call to [`commit`] or [`rollback`]. If neither are called
57/// before the transaction goes out-of-scope, [`rollback`] is called. In other
58/// words, [`rollback`] is called on `drop` if the transaction is still in-progress.
59///
60/// A savepoint is a special mark inside a transaction that allows all commands that are
61/// executed after it was established to be rolled back, restoring the transaction state to
62/// what it was at the time of the savepoint.
63///
64/// A transaction can be used as an [`Executor`] when performing queries:
65/// ```rust,no_run
66/// # use sqlx_core::acquire::Acquire;
67/// # async fn example() -> sqlx::Result<()> {
68/// # let id = 1;
69/// # let mut conn: sqlx::PgConnection = unimplemented!();
70/// let mut tx = conn.begin().await?;
71///
72/// let result = sqlx::query("DELETE FROM \"testcases\" WHERE id = $1")
73/// .bind(id)
74/// .execute(&mut *tx)
75/// .await?
76/// .rows_affected();
77///
78/// tx.commit().await
79/// # }
80/// ```
81/// [`Executor`]: crate::executor::Executor
82/// [`Connection::begin`]: crate::connection::Connection::begin()
83/// [`Pool::begin`]: crate::pool::Pool::begin()
84/// [`commit`]: Self::commit()
85/// [`rollback`]: Self::rollback()
86pub struct Transaction<'c, DB>
87where
88 DB: Database,
89{
90 connection: MaybePoolConnection<'c, DB>,
91 open: bool,
92}
93
94impl<'c, DB> Transaction<'c, DB>
95where
96 DB: Database,
97{
98 #[doc(hidden)]
99 pub fn begin(
100 conn: impl Into<MaybePoolConnection<'c, DB>>,
101 statement: Option<SqlStr>,
102 ) -> BoxFuture<'c, Result<Self, Error>> {
103 let conn = conn.into();
104
105 Box::pin(async move {
106 let mut tx = Self {
107 connection: conn,
108
109 // If the call to `begin` fails or doesn't complete we want to attempt a rollback in case the transaction was started.
110 open: true,
111 };
112
113 DB::TransactionManager::begin(&mut tx.connection, statement).await?;
114
115 Ok(tx)
116 })
117 }
118
119 /// Commits this transaction or savepoint.
120 pub async fn commit(mut self) -> Result<(), Error> {
121 DB::TransactionManager::commit(&mut self.connection).await?;
122 self.open = false;
123
124 Ok(())
125 }
126
127 /// Aborts this transaction or savepoint.
128 pub async fn rollback(mut self) -> Result<(), Error> {
129 DB::TransactionManager::rollback(&mut self.connection).await?;
130 self.open = false;
131
132 Ok(())
133 }
134}
135
136// NOTE: fails to compile due to lack of lazy normalization
137// impl<'c, 't, DB: Database> crate::executor::Executor<'t>
138// for &'t mut crate::transaction::Transaction<'c, DB>
139// where
140// &'c mut DB::Connection: Executor<'c, Database = DB>,
141// {
142// type Database = DB;
143//
144//
145//
146// fn fetch_many<'e, 'q: 'e, E: 'q>(
147// self,
148// query: E,
149// ) -> futures_core::stream::BoxStream<
150// 'e,
151// Result<
152// crate::Either<<DB as crate::database::Database>::QueryResult, DB::Row>,
153// crate::error::Error,
154// >,
155// >
156// where
157// 't: 'e,
158// E: crate::executor::Execute<'q, Self::Database>,
159// {
160// (&mut **self).fetch_many(query)
161// }
162//
163// fn fetch_optional<'e, 'q: 'e, E: 'q>(
164// self,
165// query: E,
166// ) -> futures_core::future::BoxFuture<'e, Result<Option<DB::Row>, crate::error::Error>>
167// where
168// 't: 'e,
169// E: crate::executor::Execute<'q, Self::Database>,
170// {
171// (&mut **self).fetch_optional(query)
172// }
173//
174// fn prepare_with<'e, 'q: 'e>(
175// self,
176// sql: &'q str,
177// parameters: &'e [<Self::Database as crate::database::Database>::TypeInfo],
178// ) -> futures_core::future::BoxFuture<
179// 'e,
180// Result<
181// <Self::Database as crate::database::Database>::Statement<'q>,
182// crate::error::Error,
183// >,
184// >
185// where
186// 't: 'e,
187// {
188// (&mut **self).prepare_with(sql, parameters)
189// }
190//
191// #[doc(hidden)]
192// fn describe<'e, 'q: 'e>(
193// self,
194// query: &'q str,
195// ) -> futures_core::future::BoxFuture<
196// 'e,
197// Result<crate::describe::Describe<Self::Database>, crate::error::Error>,
198// >
199// where
200// 't: 'e,
201// {
202// (&mut **self).describe(query)
203// }
204// }
205
206impl<DB> Debug for Transaction<'_, DB>
207where
208 DB: Database,
209{
210 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
211 // TODO: Show the full type <..<..<..
212 f.debug_struct("Transaction").finish()
213 }
214}
215
216impl<DB> Deref for Transaction<'_, DB>
217where
218 DB: Database,
219{
220 type Target = DB::Connection;
221
222 #[inline]
223 fn deref(&self) -> &Self::Target {
224 &self.connection
225 }
226}
227
228impl<DB> DerefMut for Transaction<'_, DB>
229where
230 DB: Database,
231{
232 #[inline]
233 fn deref_mut(&mut self) -> &mut Self::Target {
234 &mut self.connection
235 }
236}
237
238// Implement `AsMut<DB::Connection>` so `Transaction` can be given to a
239// `PgAdvisoryLockGuard`.
240//
241// See: https://github.com/launchbadge/sqlx/issues/2520
242impl<DB: Database> AsMut<DB::Connection> for Transaction<'_, DB> {
243 fn as_mut(&mut self) -> &mut DB::Connection {
244 &mut self.connection
245 }
246}
247
248impl<'t, DB: Database> crate::acquire::Acquire<'t> for &'t mut Transaction<'_, DB> {
249 type Database = DB;
250
251 type Connection = &'t mut <DB as Database>::Connection;
252
253 #[inline]
254 fn acquire(self) -> BoxFuture<'t, Result<Self::Connection, Error>> {
255 Box::pin(future::ready(Ok(&mut **self)))
256 }
257
258 #[inline]
259 fn begin(self) -> BoxFuture<'t, Result<Transaction<'t, DB>, Error>> {
260 Transaction::begin(&mut **self, None)
261 }
262}
263
264impl<DB> Drop for Transaction<'_, DB>
265where
266 DB: Database,
267{
268 fn drop(&mut self) {
269 if self.open {
270 // starts a rollback operation
271
272 // what this does depends on the database but generally this means we queue a rollback
273 // operation that will happen on the next asynchronous invocation of the underlying
274 // connection (including if the connection is returned to a pool)
275
276 DB::TransactionManager::start_rollback(&mut self.connection);
277 }
278 }
279}
280
281pub fn begin_ansi_transaction_sql(depth: usize) -> SqlStr {
282 if depth == 0 {
283 "BEGIN".into_sql_str()
284 } else {
285 AssertSqlSafe(format!("SAVEPOINT _sqlx_savepoint_{depth}")).into_sql_str()
286 }
287}
288
289pub fn commit_ansi_transaction_sql(depth: usize) -> SqlStr {
290 if depth == 1 {
291 "COMMIT".into_sql_str()
292 } else {
293 AssertSqlSafe(format!("RELEASE SAVEPOINT _sqlx_savepoint_{}", depth - 1)).into_sql_str()
294 }
295}
296
297pub fn rollback_ansi_transaction_sql(depth: usize) -> SqlStr {
298 if depth == 1 {
299 "ROLLBACK".into_sql_str()
300 } else {
301 AssertSqlSafe(format!(
302 "ROLLBACK TO SAVEPOINT _sqlx_savepoint_{}",
303 depth - 1
304 ))
305 .into_sql_str()
306 }
307}