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}